├── .github └── FUNDING.yml ├── src ├── ruby_bindings │ ├── float.rs │ ├── range.rs │ ├── exception.rs │ ├── hash.rs │ ├── symbol.rs │ ├── gc.rs │ ├── vm.rs │ ├── data.rs │ ├── object.rs │ ├── int.rs │ ├── array.rs │ ├── mod.rs │ ├── mixin.rs │ └── string.rs ├── mixin │ ├── class │ │ ├── inheritance.rs │ │ └── classify.rs │ ├── module.rs │ └── method.rs ├── prelude.rs ├── range │ ├── into_bounds.rs │ └── mod.rs ├── object │ ├── non_null.rs │ ├── ty.rs │ ├── rosy.rs │ └── any.rs ├── num │ ├── mod.rs │ ├── integer │ │ └── pack.rs │ └── float.rs ├── meta.rs ├── util.rs ├── lib.rs ├── rosy.rs ├── protected.rs ├── vm │ ├── instr_seq.rs │ ├── mod.rs │ └── eval.rs ├── gc.rs ├── hash.rs ├── symbol.rs └── string │ └── encoding.rs ├── scripts └── travis │ ├── print_ruby_config.sh │ └── install_ruby.sh ├── Cargo.toml ├── .travis.yml ├── .gitignore ├── CHANGELOG.md └── LICENSE.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: oceanpkg 4 | -------------------------------------------------------------------------------- /src/ruby_bindings/float.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | extern "C" { 4 | // VALUE rb_float_new(double d) 5 | pub fn rb_float_new(d: f64) -> VALUE; 6 | // double rb_float_value(VALUE v) 7 | pub fn rb_float_value(v: VALUE) -> f64; 8 | } 9 | -------------------------------------------------------------------------------- /scripts/travis/print_ruby_config.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then 4 | RUBY="rvm $ROSY_RUBY_VERSION do ruby" 5 | else 6 | RUBY="ruby" 7 | fi 8 | 9 | $RUBY -e "require 'pp'; pp RbConfig::CONFIG" 10 | -------------------------------------------------------------------------------- /src/ruby_bindings/range.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | extern "C" { 4 | // VALUE rb_range_new(VALUE beg, VALUE end, int exclude_end) 5 | pub fn rb_range_new(beg: VALUE, end: VALUE, exclude_end: c_int) -> VALUE; 6 | // int rb_range_values(VALUE range, VALUE *begp, VALUE *endp, int *exclp) 7 | pub fn rb_range_values(range: VALUE, begp: *mut VALUE, endp: *mut VALUE, exclp: *mut c_int) -> c_int; 8 | } 9 | -------------------------------------------------------------------------------- /src/ruby_bindings/exception.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | extern "C" { 4 | // VALUE rb_errinfo(void) 5 | pub fn rb_errinfo() -> VALUE; 6 | // void rb_set_errinfo(VALUE err) 7 | pub fn rb_set_errinfo(err: VALUE); 8 | 9 | // NORETURN(void rb_exc_raise(VALUE mesg)) 10 | pub fn rb_exc_raise(mesg: VALUE) -> !; 11 | // VALUE rb_protect(VALUE (* proc) (VALUE), VALUE data, int *pstate) 12 | pub fn rb_protect( 13 | proc: Option VALUE>, 14 | data: VALUE, 15 | pstate: *mut c_int, 16 | ) -> VALUE; 17 | } 18 | -------------------------------------------------------------------------------- /scripts/travis/install_ruby.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function error() { 4 | >&2 echo "$@" 5 | exit 1 6 | } 7 | 8 | # `rvm` is not available on Windows 9 | [[ "$TRAVIS_OS_NAME" != "windows" ]] || exit 0 10 | 11 | [[ -n "$ROSY_RUBY_VERSION" ]] || error "Specify Ruby version via 'ROSY_RUBY_VERSION'" 12 | 13 | if [[ "$FEATURES" == *"static"* ]]; then 14 | echo "Setting up Ruby $ROSY_RUBY_VERSION for static linking..." 15 | CONFIGURE_OPTS="--disable-shared" 16 | else 17 | echo "Setting up Ruby $ROSY_RUBY_VERSION for shared linking..." 18 | CONFIGURE_OPTS="--enable-shared" 19 | fi 20 | 21 | rvm install "$ROSY_RUBY_VERSION" --no-docs "$CONFIGURE_OPTS" 22 | -------------------------------------------------------------------------------- /src/ruby_bindings/hash.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | extern "C" { 4 | // VALUE rb_hash_aref(VALUE hash, VALUE key) 5 | pub fn rb_hash_aref(hash: VALUE, key: VALUE) -> VALUE; 6 | // VALUE rb_hash_aset(VALUE hash, VALUE key, VALUE val) 7 | pub fn rb_hash_aset(hash: VALUE, key: VALUE, val: VALUE) -> VALUE; 8 | // void rb_hash_bulk_insert_into_st_table(long argc, const VALUE *argv, VALUE hash) 9 | pub fn rb_hash_bulk_insert_into_st_table(argc: c_long, argv: *const VALUE, hash: VALUE); 10 | // VALUE rb_hash_clear(VALUE hash) 11 | pub fn rb_hash_clear(hash: VALUE) -> VALUE; 12 | // VALUE rb_hash_delete(VALUE hash, VALUE key) 13 | pub fn rb_hash_delete(hash: VALUE, key: VALUE) -> VALUE; 14 | // VALUE rb_hash_new(void) 15 | pub fn rb_hash_new() -> VALUE; 16 | // size_t rb_hash_size_num(VALUE hash) 17 | pub fn rb_hash_size_num(hash: VALUE) -> usize; 18 | // VALUE rb_hash_dup(VALUE hash) 19 | pub fn rb_hash_dup(hash: VALUE) -> VALUE; 20 | } 21 | -------------------------------------------------------------------------------- /src/ruby_bindings/symbol.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | prelude::*, 3 | rb_encoding, 4 | }; 5 | 6 | pub type ID = usize; 7 | 8 | extern "C" { 9 | // ID rb_intern_str(VALUE str) 10 | pub fn rb_intern_str(str: VALUE) -> ID; 11 | // ID rb_sym2id(VALUE sym) 12 | pub fn rb_sym2id(sym: VALUE) -> ID; 13 | // int rb_enc_symname2_p(const char *name, long len, rb_encoding *enc) 14 | pub fn rb_enc_symname2_p(name: *const c_char, len: c_long, enc: *mut rb_encoding) -> c_int; 15 | // ID rb_intern3(const char *name, long len, rb_encoding *enc) 16 | pub fn rb_intern3(name: *const c_char, len: c_long, enc: *mut rb_encoding) -> ID; 17 | // VALUE rb_id2sym(ID x) 18 | pub fn rb_id2sym(x: ID) -> VALUE; 19 | // const char * rb_id2name(ID id) 20 | pub fn rb_id2name(id: ID) -> *const c_char; 21 | // VALUE rb_sym_all_symbols(void) 22 | pub fn rb_sym_all_symbols() -> VALUE; 23 | // VALUE rb_f_global_variables(void) 24 | pub fn rb_f_global_variables() -> VALUE; 25 | } 26 | -------------------------------------------------------------------------------- /src/mixin/class/inheritance.rs: -------------------------------------------------------------------------------- 1 | /// The [`inheritance`](struct.Class.html#method.inheritance) relationship 2 | /// between two classes. 3 | #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] 4 | pub enum Inheritance { 5 | /// There is no relationship between the two classes. 6 | None, 7 | /// The first class inherits or is the same as the second; `A < B`. 8 | SubEq, 9 | /// The second class inherits the first; `B < A`. 10 | Super, 11 | } 12 | 13 | impl Inheritance { 14 | /// Returns whether there's no relationship between the classes. 15 | #[inline] 16 | pub fn is_none(self) -> bool { 17 | self == Inheritance::None 18 | } 19 | 20 | /// Returns whether the first class inherits or is the same as the second. 21 | #[inline] 22 | pub fn is_sub_eq(self) -> bool { 23 | self == Inheritance::SubEq 24 | } 25 | 26 | /// Returns whether the second class inherits the first. 27 | #[inline] 28 | pub fn is_super(self) -> bool { 29 | self == Inheritance::Super 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Types and traits that are commonly used within this library. 2 | //! 3 | //! This module is intended to be glob imported via `use rosy::prelude::*` when 4 | //! primarily working with Rosy types. This allows for having access to _almost_ 5 | //! everything one may need. 6 | //! 7 | //! **Note:** These items are all already available at the top crate level. If 8 | //! only certain items are required, importing from the prelude directly is 9 | //! unnecessary. 10 | //! 11 | //! **Important:** Rosy's [`String`][rb] type **will conflict** with Rust's 12 | //! built-in [`String`][rs] type when imported as-is into the same module. 13 | //! 14 | //! [rb]: string/struct.String.html 15 | //! [rs]: https://doc.rust-lang.org/std/string/struct.String.html 16 | 17 | // This should match the import in `lib.rs` verbatim (literally copy + paste) 18 | #[doc(no_inline)] 19 | pub use crate::{ 20 | array::Array, 21 | exception::{AnyException, Exception}, 22 | hash::Hash, 23 | mixin::{Mixin, Class, Module}, 24 | num::{Float, Integer}, 25 | object::{AnyObject, Object, RosyObject}, 26 | range::Range, 27 | Result, 28 | rosy::Rosy, 29 | string::String, 30 | symbol::{Symbol, SymbolId}, 31 | }; 32 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rosy" 3 | version = "0.0.9" 4 | authors = ["Nikolai Vazquez"] 5 | edition = "2018" 6 | description = "Ruby bindings for Rust." 7 | license = "MIT OR Apache-2.0" 8 | readme = "README.md" 9 | homepage = "https://github.com/oceanpkg/rosy" 10 | repository = "https://github.com/oceanpkg/rosy" 11 | documentation = "https://docs.rs/rosy" 12 | keywords = ["ruby"] 13 | categories = ["api-bindings"] 14 | links = "ruby" 15 | build = "build/mod.rs" 16 | include = ["Cargo.toml", "**/*.rs", "README.md", "CHANGELOG.md", "LICENSE*"] 17 | 18 | [lib] 19 | path = "src/lib.rs" 20 | 21 | [dev-dependencies] 22 | static_assertions = "0.3.0" 23 | 24 | [build-dependencies] 25 | aloxide = { version = "0.0.8", default-features = false } 26 | 27 | # Used to enable `cfg(nightly)` 28 | version_check = { version = "0.1", optional = true } 29 | 30 | [features] 31 | default = ["static"] 32 | download = ["aloxide/download"] 33 | static = [] 34 | 35 | # Conditionally enable functionality 36 | ruby_2_6 = [] 37 | 38 | # Meant for getting docs to generate on docs.rs 39 | _skip_linking = [] 40 | 41 | [package.metadata.docs.rs] 42 | features = ["ruby_2_6", "version_check", "_skip_linking"] 43 | 44 | [badges] 45 | maintenance = { status = "actively-developed" } 46 | -------------------------------------------------------------------------------- /src/ruby_bindings/gc.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | extern "C" { 4 | // void rb_gc_adjust_memory_usage(ssize_t diff) 5 | pub fn rb_gc_adjust_memory_usage(diff: isize); 6 | // size_t rb_gc_count(void) 7 | pub fn rb_gc_count() -> usize; 8 | // VALUE rb_gc_disable(void) 9 | pub fn rb_gc_disable() -> VALUE; 10 | // VALUE rb_gc_enable(void) 11 | pub fn rb_gc_enable() -> VALUE; 12 | // void rb_gc_force_recycle(VALUE obj) 13 | pub fn rb_gc_force_recycle(obj: VALUE); 14 | // VALUE rb_gc_latest_gc_info(VALUE key) 15 | pub fn rb_gc_latest_gc_info(key: VALUE) -> VALUE; 16 | // VALUE rb_gc_start(void) 17 | pub fn rb_gc_start() -> VALUE; 18 | // size_t rb_gc_stat(VALUE key) 19 | pub fn rb_gc_stat(key: VALUE) -> usize; 20 | 21 | // void rb_gc_mark(VALUE ptr) 22 | pub fn rb_gc_mark(ptr: VALUE); 23 | // void rb_gc_mark_maybe(VALUE obj) 24 | pub fn rb_gc_mark_maybe(obj: VALUE); 25 | 26 | // void rb_gc_register_address(VALUE *addr) 27 | pub fn rb_gc_register_address(addr: *mut VALUE); 28 | // void rb_gc_register_mark_object(VALUE obj) 29 | pub fn rb_gc_register_mark_object(obj: VALUE); 30 | // void rb_gc_unregister_address(VALUE *addr) 31 | pub fn rb_gc_unregister_address(addr: *mut VALUE); 32 | } 33 | -------------------------------------------------------------------------------- /src/range/into_bounds.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{ 2 | Bound, 3 | Range, 4 | RangeInclusive, 5 | RangeFrom, 6 | }; 7 | 8 | /// A type that consists of a start (inclusive) and end bound. 9 | pub trait IntoBounds { 10 | /// Returns the start (inclusive) and end bounds of `self`. 11 | fn into_bounds(self) -> (S, Bound); 12 | } 13 | 14 | impl IntoBounds for (S, Bound) { 15 | #[inline] 16 | fn into_bounds(self) -> (S, Bound) { 17 | self 18 | } 19 | } 20 | 21 | impl IntoBounds for (S, E) { 22 | #[inline] 23 | fn into_bounds(self) -> (S, Bound) { 24 | (self.0, Bound::Excluded(self.1)) 25 | } 26 | } 27 | 28 | impl IntoBounds for Range { 29 | #[inline] 30 | fn into_bounds(self) -> (A, Bound) { 31 | (self.start, Bound::Excluded(self.end)) 32 | } 33 | } 34 | 35 | impl IntoBounds for RangeInclusive { 36 | #[inline] 37 | fn into_bounds(self) -> (A, Bound) { 38 | let (start, end) = self.into_inner(); 39 | (start, Bound::Included(end)) 40 | } 41 | } 42 | 43 | impl IntoBounds for RangeFrom { 44 | #[inline] 45 | fn into_bounds(self) -> (A, Bound) { 46 | (self.start, Bound::Unbounded) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/mixin/class/classify.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | prelude::*, 3 | string::Encoding, 4 | vm::InstrSeq, 5 | }; 6 | 7 | /// A type that can be instantiated from a typed `Class` instance. 8 | pub trait Classify: Object { 9 | /// Returns the typed class that can be used to get an instance of `self`. 10 | fn class() -> Class; 11 | } 12 | 13 | macro_rules! impl_trait { 14 | ($($t:ty, $c:ident ;)+) => { $( 15 | impl Classify for $t { 16 | #[inline] 17 | fn class() -> Class { 18 | unsafe { Class::cast_unchecked(Class::$c()) } 19 | } 20 | } 21 | )+ }; 22 | } 23 | 24 | impl Classify for Array { 25 | #[inline] 26 | fn class() -> Class { 27 | unsafe { Class::cast_unchecked(Class::array()) } 28 | } 29 | } 30 | 31 | impl Classify for Hash { 32 | #[inline] 33 | fn class() -> Class { 34 | unsafe { Class::cast_unchecked(Class::hash()) } 35 | } 36 | } 37 | 38 | impl_trait! { 39 | AnyObject, object; 40 | Class, class; 41 | Module, module; 42 | Integer, integer; 43 | String, string; 44 | Symbol, symbol; 45 | Encoding, encoding; 46 | AnyException, exception; 47 | InstrSeq, instr_seq; 48 | } 49 | -------------------------------------------------------------------------------- /src/ruby_bindings/vm.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | extern "C" { 4 | // int ruby_cleanup(volatile int ex) 5 | pub fn ruby_cleanup(ex: c_int) -> c_int; 6 | // void ruby_init_loadpath(void) 7 | pub fn ruby_init_loadpath(); 8 | // int ruby_setup(void) 9 | pub fn ruby_setup() -> c_int; 10 | 11 | // int rb_safe_level(void) 12 | pub fn rb_safe_level() -> c_int; 13 | // void rb_set_safe_level(int level) 14 | pub fn rb_set_safe_level(level: c_int); 15 | 16 | // VALUE rb_require_safe(VALUE fname, int safe) 17 | pub fn rb_require_safe(fname: VALUE, safe: c_int) -> VALUE; 18 | 19 | // void rb_load(VALUE fname, int wrap) 20 | pub fn rb_load(fname: VALUE, wrap: c_int); 21 | // void rb_load_protect(VALUE fname, int wrap, int *pstate) 22 | pub fn rb_load_protect(fname: VALUE, wrap: c_int, pstate: *mut c_int); 23 | 24 | // VALUE rb_eval_string(const char *str) 25 | pub fn rb_eval_string(str: *const c_char) -> VALUE; 26 | // VALUE rb_eval_string_protect(const char *str, int *pstate) 27 | pub fn rb_eval_string_protect(str: *const c_char, pstate: *mut c_int) -> VALUE; 28 | // VALUE rb_eval_string_wrap(const char *str, int *pstate) 29 | pub fn rb_eval_string_wrap(str: *const c_char, pstate: *mut c_int) -> VALUE; 30 | 31 | // VALUE rb_make_backtrace(void) 32 | pub fn rb_make_backtrace() -> VALUE; 33 | } 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 1.34.0 3 | 4 | os: 5 | - linux 6 | - osx 7 | 8 | matrix: 9 | allow_failures: 10 | - os: windows 11 | include: 12 | # Tests the use of the `doc_cfg` nightly feature 13 | - os: linux 14 | rust: nightly 15 | env: 16 | - ROSY_RUBY_VERSION="2.6.3" FEATURES="ruby_2_6,version_check" 17 | script: 18 | - cargo doc --all --no-deps --no-default-features --features="$FEATURES" 19 | - os: windows 20 | env: 21 | - ROSY_RUBY: ruby 22 | - os: windows 23 | env: 24 | - ROSY_RUBY: ruby 25 | - FEATURES: "static" 26 | 27 | env: 28 | global: 29 | - RUSTFLAGS: "-Dwarnings" 30 | - RUST_BACKTRACE: "full" 31 | - ROSY_RUBY: rvm 32 | matrix: 33 | - ROSY_RUBY_VERSION="2.6.3" FEATURES="ruby_2_6" 34 | - ROSY_RUBY_VERSION="2.6.3" FEATURES="ruby_2_6,static" 35 | - ROSY_RUBY_VERSION="2.5.5" 36 | - ROSY_RUBY_VERSION="2.5.5" FEATURES="static" 37 | - ROSY_RUBY_VERSION="2.4.6" 38 | - ROSY_RUBY_VERSION="2.4.6" FEATURES="static" 39 | 40 | cache: 41 | directories: 42 | - $HOME/.cargo 43 | - $HOME/.rvm 44 | 45 | install: 46 | - ./scripts/travis/install_ruby.sh 47 | before_script: 48 | - cargo -Vv 49 | - rustc -Vv 50 | - ./scripts/travis/print_ruby_config.sh 51 | script: 52 | # Normal tests share the same address space while doc tests don't 53 | - cargo test tests --no-default-features --features="$FEATURES" -- --test-threads=1 54 | - cargo test --doc --no-default-features --features="$FEATURES" 55 | -------------------------------------------------------------------------------- /src/object/non_null.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::c_void, 3 | fmt, 4 | marker::PhantomData, 5 | num::NonZeroUsize, 6 | }; 7 | use crate::{ 8 | AnyObject, 9 | ruby, 10 | }; 11 | 12 | // An `AnyObject` instance that doesn't use 0 (reserved for `false`). This type 13 | // is used as a memory optimization for objects wrapped in `Option`. 14 | #[derive(Clone, Copy)] 15 | #[repr(transparent)] 16 | pub struct NonNullObject { 17 | raw: NonZeroUsize, 18 | // !Send + !Sync 19 | _marker: PhantomData<*const c_void>, 20 | } 21 | 22 | impl From for AnyObject { 23 | #[inline] 24 | fn from(obj: NonNullObject) -> Self { 25 | unsafe { AnyObject::from_raw(obj.raw.into()) } 26 | } 27 | } 28 | 29 | impl AsRef for NonNullObject { 30 | #[inline] 31 | fn as_ref(&self) -> &AnyObject { 32 | unsafe { &*(self as *const Self as *const AnyObject) } 33 | } 34 | } 35 | 36 | impl fmt::Debug for NonNullObject { 37 | #[inline] 38 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 39 | AnyObject::from(*self).fmt(f) 40 | } 41 | } 42 | 43 | impl NonNullObject { 44 | #[inline] 45 | pub const unsafe fn from_raw(raw: ruby::VALUE) -> Self { 46 | NonNullObject { 47 | raw: NonZeroUsize::new_unchecked(raw), 48 | _marker: PhantomData, 49 | } 50 | } 51 | 52 | #[inline] 53 | pub const fn raw(self) -> usize { 54 | self.raw.get() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ruby_bindings/data.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | prelude::*, 3 | RBasic, 4 | fl_type, 5 | }; 6 | 7 | pub const RUBY_TYPED_FREE_IMMEDIATELY: usize = 1; 8 | pub const RUBY_FL_WB_PROTECTED: usize = fl_type::FL_WB_PROTECTED; 9 | pub const RUBY_TYPED_PROMOTED1: usize = fl_type::FL_PROMOTED1; 10 | 11 | #[repr(C)] 12 | #[derive(Clone, Copy)] 13 | pub struct RData { 14 | pub basic: RBasic, 15 | pub dmark: Option, 16 | pub dfree: Option, 17 | pub data: *mut c_void, 18 | } 19 | 20 | #[repr(C)] 21 | #[derive(Clone, Copy)] 22 | pub struct RTypedData { 23 | pub basic: RBasic, 24 | pub type_: *const rb_data_type_t, 25 | pub typed_flag: VALUE, // 1 or not 26 | pub data: *mut c_void, 27 | } 28 | 29 | #[repr(C)] 30 | #[derive(Clone, Copy)] 31 | pub struct rb_data_type_t { 32 | pub wrap_struct_name: *const c_char, 33 | pub function: rb_data_type_t_function, 34 | pub parent: *const rb_data_type_t, 35 | pub data: *mut c_void, 36 | pub flags: VALUE, 37 | } 38 | 39 | #[repr(C)] 40 | #[derive(Clone, Copy)] 41 | pub struct rb_data_type_t_function { 42 | pub dmark: Option, 43 | pub dfree: Option, 44 | pub dsize: Option usize>, 45 | pub reserved: [*mut c_void; 2], 46 | } 47 | 48 | extern "C" { 49 | // VALUE rb_data_typed_object_wrap(VALUE klass, void *datap, const rb_data_type_t *type) 50 | pub fn rb_data_typed_object_wrap(klass: VALUE, datap: *mut c_void, ty: *const rb_data_type_t) -> VALUE; 51 | } 52 | -------------------------------------------------------------------------------- /src/ruby_bindings/object.rs: -------------------------------------------------------------------------------- 1 | use std::ptr; 2 | use super::prelude::*; 3 | 4 | #[repr(C)] 5 | #[derive(Clone, Copy)] 6 | pub struct RBasic { 7 | pub flags: VALUE, 8 | pub klass: VALUE, 9 | } 10 | 11 | impl RBasic { 12 | // Used to ensure the read doesn't get optimized out 13 | #[inline] 14 | pub fn volatile_flags(&self) -> VALUE { 15 | unsafe { ptr::read_volatile(&self.flags) } 16 | } 17 | } 18 | 19 | extern "C" { 20 | // int rb_eql(VALUE obj1, VALUE obj2) 21 | pub fn rb_eql(obj1: VALUE, obj2: VALUE) -> c_int; 22 | 23 | // VALUE rb_funcallv(VALUE recv, ID mid, int argc, const VALUE *argv); 24 | pub fn rb_funcallv(recv: VALUE, mid: ID, argc: c_int, argv: *const VALUE) -> VALUE; 25 | // VALUE rb_funcallv_public(VALUE recv, ID mid, int argc, const VALUE *argv) 26 | pub fn rb_funcallv_public(recv: VALUE, mid: ID, argc: c_int, argv: *const VALUE) -> VALUE; 27 | 28 | // VALUE rb_inspect(VALUE obj) 29 | pub fn rb_inspect(obj: VALUE) -> VALUE; 30 | // VALUE rb_obj_as_string(VALUE obj) 31 | pub fn rb_obj_as_string(obj: VALUE) -> VALUE; 32 | 33 | // VALUE rb_obj_class(VALUE obj) 34 | pub fn rb_obj_class(obj: VALUE) -> VALUE; 35 | // VALUE rb_obj_id(VALUE obj) 36 | pub fn rb_obj_id(obj: VALUE) -> VALUE; 37 | 38 | // VALUE rb_obj_freeze(VALUE obj) 39 | pub fn rb_obj_freeze(obj: VALUE) -> VALUE; 40 | // VALUE rb_obj_frozen_p(VALUE obj) 41 | pub fn rb_obj_frozen_p(obj: VALUE) -> VALUE; 42 | 43 | // VALUE rb_singleton_class(VALUE obj) 44 | pub fn rb_singleton_class(obj: VALUE) -> VALUE; 45 | 46 | // VALUE rb_obj_instance_eval(int argc, const VALUE *argv, VALUE self) 47 | pub fn rb_obj_instance_eval(argc: c_int, argv: *const VALUE, this: VALUE) -> VALUE; 48 | } 49 | -------------------------------------------------------------------------------- /src/num/mod.rs: -------------------------------------------------------------------------------- 1 | //! Ruby numbers. 2 | 3 | use std::cmp::Ordering; 4 | use crate::{ 5 | prelude::*, 6 | ruby, 7 | }; 8 | 9 | mod float; 10 | mod integer; 11 | 12 | pub use self::{ 13 | float::*, 14 | integer::*, 15 | }; 16 | 17 | impl PartialEq for Float { 18 | #[inline] 19 | fn eq(&self, other: &Integer) -> bool { 20 | other == self 21 | } 22 | } 23 | 24 | impl PartialOrd for Float { 25 | #[inline] 26 | fn partial_cmp(&self, other: &Integer) -> Option { 27 | Some(other.partial_cmp(self)?.reverse()) 28 | } 29 | } 30 | 31 | impl PartialOrd for Integer { 32 | #[inline] 33 | fn partial_cmp(&self, other: &Float) -> Option { 34 | let val = unsafe { ruby::rb_big_cmp(self.raw(), other.raw()) }; 35 | if val != crate::util::NIL_VALUE { 36 | Some(crate::util::value_to_fixnum(val).cmp(&0)) 37 | } else { 38 | None 39 | } 40 | } 41 | } 42 | 43 | macro_rules! forward_int_cmp { 44 | ($($t:ty)+) => { $( 45 | impl PartialEq<$t> for AnyObject { 46 | #[inline] 47 | fn eq(&self, other: &$t) -> bool { 48 | if let Some(integer) = self.to_integer() { 49 | integer == *other 50 | } else if let Some(float) = self.to_float() { 51 | float == Integer::from(*other) 52 | } else { 53 | false 54 | } 55 | } 56 | } 57 | 58 | impl PartialEq for $t { 59 | #[inline] 60 | fn eq(&self, other: &AnyObject) -> bool { 61 | other == self 62 | } 63 | } 64 | 65 | impl PartialOrd<$t> for AnyObject { 66 | #[inline] 67 | fn partial_cmp(&self, other: &$t) -> Option { 68 | if let Some(integer) = self.to_integer() { 69 | integer.partial_cmp(other) 70 | } else if let Some(float) = self.to_float() { 71 | float.partial_cmp(&Integer::from(*other)) 72 | } else { 73 | None 74 | } 75 | } 76 | } 77 | 78 | impl PartialOrd for $t { 79 | #[inline] 80 | fn partial_cmp(&self, other: &AnyObject) -> Option { 81 | Some(other.partial_cmp(self)?.reverse()) 82 | } 83 | } 84 | )+ } 85 | } 86 | 87 | forward_int_cmp! { 88 | usize u128 u64 u32 u16 u8 89 | isize i128 i64 i32 i16 i8 90 | } 91 | -------------------------------------------------------------------------------- /src/meta.rs: -------------------------------------------------------------------------------- 1 | //! Metadata for Ruby. 2 | 3 | use std::{ 4 | ffi::CStr, 5 | str, 6 | }; 7 | use crate::ruby; 8 | 9 | #[inline] 10 | unsafe fn convert_c_str<'a>(s: &'a CStr, debug_message: &str) -> &'a str { 11 | let bytes = s.to_bytes(); 12 | if cfg!(debug_assertions) { 13 | str::from_utf8(bytes).expect(debug_message) 14 | } else { 15 | str::from_utf8_unchecked(bytes) 16 | } 17 | } 18 | 19 | /// Ruby's API version. 20 | /// 21 | /// Note that this may differ from the result of `version_str`. 22 | #[inline] 23 | pub fn api_version() -> (u16, u16, u16) { 24 | let [major, minor, teeny] = unsafe { ruby::ruby_api_version }; 25 | (major as u16, minor as u16, teeny as u16) 26 | } 27 | 28 | /// Ruby's version info as a UTF-8 string. 29 | #[inline] 30 | pub fn version_str<'a>() -> &'a str { 31 | unsafe { convert_c_str(version_c_str(), "Ruby version is not UTF-8") } 32 | } 33 | 34 | /// Ruby's version info as a C string. 35 | #[inline] 36 | pub fn version_c_str<'a>() -> &'a CStr { 37 | unsafe { CStr::from_ptr(ruby::ruby_version.as_ptr()) } 38 | } 39 | 40 | /// Ruby's release date info as a UTF-8 string. 41 | #[inline] 42 | pub fn release_date_str<'a>() -> &'a str { 43 | unsafe { 44 | convert_c_str(release_date_c_str(), "Ruby release date is not UTF-8") 45 | } 46 | } 47 | 48 | /// Ruby's release date info as a C string. 49 | #[inline] 50 | pub fn release_date_c_str<'a>() -> &'a CStr { 51 | unsafe { CStr::from_ptr(ruby::ruby_release_date.as_ptr()) } 52 | } 53 | 54 | /// Ruby's platform info as a UTF-8 string. 55 | #[inline] 56 | pub fn platform_str<'a>() -> &'a str { 57 | unsafe { convert_c_str(platform_c_str(), "Ruby platform is not UTF-8") } 58 | } 59 | 60 | /// Ruby's platform info as a C string. 61 | #[inline] 62 | pub fn platform_c_str<'a>() -> &'a CStr { 63 | unsafe { CStr::from_ptr(ruby::ruby_platform.as_ptr()) } 64 | } 65 | 66 | /// Ruby's description info as a UTF-8 string. 67 | #[inline] 68 | pub fn description_str<'a>() -> &'a str { 69 | unsafe { 70 | convert_c_str(description_c_str(), "Ruby description is not UTF-8") 71 | } 72 | } 73 | 74 | /// Ruby's description info as a C string. 75 | #[inline] 76 | pub fn description_c_str<'a>() -> &'a CStr { 77 | unsafe { CStr::from_ptr(ruby::ruby_description.as_ptr()) } 78 | } 79 | 80 | /// Ruby's copyright info as a UTF-8 string. 81 | #[inline] 82 | pub fn copyright_str<'a>() -> &'a str { 83 | unsafe { convert_c_str(copyright_c_str(), "Ruby copyright is not UTF-8") } 84 | } 85 | 86 | /// Ruby's copyright info as a C string. 87 | #[inline] 88 | pub fn copyright_c_str<'a>() -> &'a CStr { 89 | unsafe { CStr::from_ptr(ruby::ruby_copyright.as_ptr()) } 90 | } 91 | 92 | /// Ruby's engine info as a UTF-8 string. 93 | #[inline] 94 | pub fn engine_str<'a>() -> &'a str { 95 | unsafe { convert_c_str(engine_c_str(), "Ruby engine is not UTF-8") } 96 | } 97 | 98 | /// Ruby's engine info as a C string. 99 | #[inline] 100 | pub fn engine_c_str<'a>() -> &'a CStr { 101 | unsafe { CStr::from_ptr(ruby::ruby_engine.as_ptr()) } 102 | } 103 | -------------------------------------------------------------------------------- /src/ruby_bindings/int.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | pub mod integer_flags { 4 | use super::*; 5 | 6 | pub const PACK_MSWORD_FIRST: c_int = 0x01; 7 | pub const PACK_LSWORD_FIRST: c_int = 0x02; 8 | pub const PACK_MSBYTE_FIRST: c_int = 0x10; 9 | pub const PACK_LSBYTE_FIRST: c_int = 0x20; 10 | pub const PACK_NATIVE_BYTE_ORDER: c_int = 0x40; 11 | pub const PACK_2COMP: c_int = 0x80; // 2's compliment 12 | pub const PACK_FORCE_GENERIC_IMPLEMENTATION: c_int = 0x400; 13 | 14 | // For rb_integer_unpack: 15 | pub const PACK_FORCE_BIGNUM: c_int = 0x100; 16 | pub const PACK_NEGATIVE: c_int = 0x200; 17 | 18 | // Combinations 19 | pub const PACK_LITTLE_ENDIAN: c_int = PACK_LSWORD_FIRST | PACK_LSBYTE_FIRST; 20 | pub const PACK_BIG_ENDIAN: c_int = PACK_MSWORD_FIRST | PACK_MSBYTE_FIRST; 21 | } 22 | 23 | extern "C" { 24 | // VALUE rb_uint2inum(uintptr_t n) 25 | pub fn rb_uint2inum(n: usize) -> VALUE; 26 | // VALUE rb_int2inum(intptr_t n) 27 | pub fn rb_int2inum(n: isize) -> VALUE; 28 | 29 | // double rb_big2dbl(VALUE x) 30 | pub fn rb_big2dbl(x: VALUE) -> f64; 31 | // VALUE rb_big2str(VALUE x, int base) 32 | pub fn rb_big2str(x: VALUE, base: c_int) -> VALUE; 33 | 34 | // int rb_bigzero_p(VALUE x) 35 | pub fn rb_bigzero_p(x: VALUE) -> c_int; 36 | 37 | // int rb_integer_pack(VALUE val, void *words, size_t numwords, size_t wordsize, size_t nails, int flags) 38 | pub fn rb_integer_pack( 39 | val: VALUE, 40 | words: *mut c_void, 41 | numwords: usize, 42 | wordsize: usize, 43 | nails: usize, 44 | flags: c_int, 45 | ) -> c_int; 46 | 47 | // VALUE rb_integer_unpack(const void *words, size_t numwords, size_t wordsize, size_t nails, int flags) 48 | pub fn rb_integer_unpack( 49 | words: *const c_void, 50 | numwords: usize, 51 | wordsize: usize, 52 | nails: usize, 53 | flags: c_int, 54 | ) -> VALUE; 55 | 56 | // VALUE rb_big_eq(VALUE x, VALUE y) 57 | pub fn rb_big_eq(x: VALUE, y: VALUE) -> VALUE; 58 | // int rb_big_sign(VALUE x) 59 | pub fn rb_big_sign(x: VALUE) -> c_int; 60 | 61 | // int rb_absint_singlebit_p(VALUE val) 62 | pub fn rb_absint_singlebit_p(val: VALUE) -> c_int; 63 | 64 | // size_t rb_absint_size(VALUE val, int *nlz_bits_ret) 65 | pub fn rb_absint_size(val: VALUE, nlz_bits_ret: *mut c_int) -> usize; 66 | 67 | // size_t rb_absint_numwords(VALUE val, size_t word_numbits, size_t *nlz_bits_ret) 68 | pub fn rb_absint_numwords( 69 | val: VALUE, 70 | word_numbits: usize, 71 | nlz_bits_ret: *mut usize, 72 | ) -> usize; 73 | 74 | // VALUE rb_big_cmp(VALUE x, VALUE y); 75 | pub fn rb_big_cmp(x: VALUE, y: VALUE) -> VALUE; 76 | // VALUE rb_big_and(VALUE x, VALUE y); 77 | pub fn rb_big_and(x: VALUE, y: VALUE) -> VALUE; 78 | // VALUE rb_big_or(VALUE x, VALUE y); 79 | pub fn rb_big_or(x: VALUE, y: VALUE) -> VALUE; 80 | // VALUE rb_big_xor(VALUE x, VALUE y); 81 | pub fn rb_big_xor(x: VALUE, y: VALUE) -> VALUE; 82 | } 83 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | object::Ty, 3 | ruby::{ 4 | self, 5 | RBasic, 6 | VALUE, 7 | value_type, 8 | special_consts::*, 9 | } 10 | }; 11 | 12 | pub const NIL_VALUE: VALUE = Qnil; 13 | pub const TRUE_VALUE: VALUE = Qtrue; 14 | pub const FALSE_VALUE: VALUE = Qfalse; 15 | pub const UNDEF_VALUE: VALUE = Qundef; 16 | 17 | pub const MAX_VALUE: VALUE = !0; 18 | pub const MAX_VALUE_SHIFTED: VALUE = MAX_VALUE << SPECIAL_SHIFT; 19 | 20 | #[inline] 21 | pub fn matches_ruby_size_align() -> bool { 22 | use std::mem::{align_of, size_of}; 23 | size_of::() == size_of::() && 24 | align_of::() == align_of::() 25 | } 26 | 27 | #[inline] 28 | pub const fn test_value(v: VALUE) -> bool { 29 | v & !NIL_VALUE != 0 30 | } 31 | 32 | pub fn value_is_ty(v: VALUE, ty: Ty) -> bool { 33 | match ty { 34 | Ty::FIXNUM => value_is_fixnum(v), 35 | Ty::FLOAT => value_is_float(v), 36 | Ty::SYMBOL => value_is_sym(v), 37 | Ty::TRUE => v == TRUE_VALUE, 38 | Ty::FALSE => v == FALSE_VALUE, 39 | Ty::NIL => v == NIL_VALUE, 40 | Ty::UNDEF => v == UNDEF_VALUE, 41 | _ => if let Some(t) = value_built_in_ty(v) { 42 | t == ty 43 | } else { 44 | false 45 | } 46 | } 47 | } 48 | 49 | pub fn value_ty(v: VALUE) -> Ty { 50 | match v { 51 | NIL_VALUE => Ty::NIL, 52 | TRUE_VALUE => Ty::TRUE, 53 | FALSE_VALUE => Ty::FALSE, 54 | UNDEF_VALUE => Ty::UNDEF, 55 | _ => if value_is_sym(v) { 56 | Ty::SYMBOL 57 | } else if value_is_float(v) { 58 | Ty::FLOAT 59 | } else if value_is_fixnum(v) { 60 | Ty::FIXNUM 61 | } else if let Some(built_in) = value_built_in_ty(v) { 62 | built_in 63 | } else { 64 | Ty::_UNKNOWN 65 | } 66 | } 67 | } 68 | 69 | #[inline] 70 | pub unsafe fn value_flags(v: VALUE) -> VALUE { 71 | (*(v as *const RBasic)).flags 72 | } 73 | 74 | #[inline] 75 | pub unsafe fn value_built_in_ty_unchecked(v: VALUE) -> Ty { 76 | std::mem::transmute((value_flags(v) & value_type::MASK as VALUE) as u32) 77 | } 78 | 79 | #[inline] 80 | pub fn value_built_in_ty(v: VALUE) -> Option { 81 | if value_is_special_const(v) { 82 | None 83 | } else { 84 | unsafe { Some(value_built_in_ty_unchecked(v)) } 85 | } 86 | } 87 | 88 | #[inline] 89 | pub fn value_is_built_in_ty(v: VALUE, ty: Ty) -> bool { 90 | value_built_in_ty(v) == Some(ty) 91 | } 92 | 93 | #[inline] 94 | pub const fn value_flag(v: VALUE) -> VALUE { 95 | v & !MAX_VALUE_SHIFTED 96 | } 97 | 98 | #[inline] 99 | pub const fn fixnum_to_value(i: isize) -> VALUE { 100 | ((i as VALUE) << 1) | FIXNUM_FLAG 101 | } 102 | 103 | #[inline] 104 | pub const fn value_to_fixnum(v: VALUE) -> isize { 105 | ((v & !FIXNUM_FLAG) as isize) >> 1 106 | } 107 | 108 | #[inline] 109 | pub const fn value_is_fixnum(v: VALUE) -> bool { 110 | v & FIXNUM_FLAG != 0 111 | } 112 | 113 | #[inline] 114 | pub fn value_is_float(v: VALUE) -> bool { 115 | ruby::rb_flonum_p(v) || value_is_built_in_ty(v, Ty::FLOAT) 116 | } 117 | 118 | #[inline] 119 | pub const fn value_is_immediate(v: VALUE) -> bool { 120 | v & IMMEDIATE_MASK != 0 121 | } 122 | 123 | #[inline] 124 | pub fn value_is_special_const(v: VALUE) -> bool { 125 | value_is_immediate(v) || !test_value(v) 126 | } 127 | 128 | #[inline] 129 | pub const fn value_is_static_sym(v: VALUE) -> bool { 130 | value_flag(v) == SYMBOL_FLAG 131 | } 132 | 133 | #[inline] 134 | pub fn value_is_dyn_sym(v: VALUE) -> bool { 135 | value_is_built_in_ty(v, Ty::SYMBOL) 136 | } 137 | 138 | #[inline] 139 | pub fn value_is_sym(v: VALUE) -> bool { 140 | value_is_static_sym(v) || value_is_dyn_sym(v) 141 | } 142 | 143 | #[inline] 144 | pub fn value_is_class(v: VALUE) -> bool { 145 | value_is_built_in_ty(v, Ty::CLASS) 146 | } 147 | 148 | #[inline] 149 | pub fn value_is_module(v: VALUE) -> bool { 150 | value_is_built_in_ty(v, Ty::MODULE) 151 | } 152 | 153 | pub trait Sealed {} 154 | -------------------------------------------------------------------------------- /src/ruby_bindings/array.rs: -------------------------------------------------------------------------------- 1 | use std::ptr; 2 | use super::{ 3 | prelude::*, 4 | RBasic, 5 | fl_type, 6 | }; 7 | 8 | #[repr(C)] 9 | #[derive(Clone, Copy)] 10 | pub struct RArray { 11 | pub basic: RBasic, 12 | pub as_: RArrayAs, 13 | } 14 | 15 | impl RArray { 16 | #[inline] 17 | fn embed_len(&self) -> usize { 18 | use rarray_flags::*; 19 | 20 | const MASK: usize = EMBED_LEN_MASK >> EMBED_LEN_SHIFT; 21 | MASK & (self.basic.volatile_flags() >> EMBED_LEN_SHIFT) 22 | } 23 | 24 | #[inline] 25 | fn is_embedded(&self) -> bool { 26 | self.basic.volatile_flags() & rarray_flags::EMBED_FLAG != 0 27 | } 28 | 29 | #[inline] 30 | pub fn len(&self) -> usize { 31 | if self.is_embedded() { 32 | self.embed_len() 33 | } else { 34 | unsafe { ptr::read_volatile(&self.as_.heap.len) as usize } 35 | } 36 | } 37 | 38 | #[inline] 39 | pub fn start(&self) -> *const VALUE { 40 | if self.is_embedded() { 41 | unsafe { self.as_.ary.as_ptr() } 42 | } else { 43 | unsafe { ptr::read_volatile(&self.as_.heap.ptr) } 44 | } 45 | } 46 | 47 | #[inline] 48 | pub fn start_mut(&mut self) -> *mut VALUE { 49 | if self.is_embedded() { 50 | unsafe { self.as_.ary.as_mut_ptr() } 51 | } else { 52 | unsafe { ptr::read_volatile(&self.as_.heap.ptr) as *mut VALUE } 53 | } 54 | } 55 | } 56 | 57 | #[repr(C)] 58 | #[derive(Clone, Copy)] 59 | pub union RArrayAs { 60 | pub heap: RArrayHeap, 61 | pub ary: [VALUE; rarray_flags::EMBED_LEN_MAX], 62 | } 63 | 64 | #[repr(C)] 65 | #[derive(Clone, Copy)] 66 | pub struct RArrayHeap { 67 | pub len: c_long, 68 | pub aux: RArrayHeapAux, 69 | pub ptr: *const VALUE, 70 | } 71 | 72 | #[repr(C)] 73 | #[derive(Clone, Copy)] 74 | pub union RArrayHeapAux { 75 | pub capa: c_long, 76 | pub shared: VALUE, 77 | } 78 | 79 | pub mod rarray_flags { 80 | use super::fl_type::*; 81 | 82 | pub const EMBED_LEN_MAX: usize = 3; 83 | pub const EMBED_FLAG: usize = FL_USER_1; 84 | pub const EMBED_LEN_MASK: usize = FL_USER_4 | FL_USER_3; 85 | pub const EMBED_LEN_SHIFT: usize = FL_USHIFT + 3; 86 | } 87 | 88 | extern "C" { 89 | // void rb_ary_modify(VALUE ary) 90 | pub fn rb_ary_modify(ary: VALUE); 91 | // VALUE rb_ary_dup(VALUE ary) 92 | pub fn rb_ary_dup(ary: VALUE) -> VALUE; 93 | // VALUE rb_ary_cat(VALUE ary, const VALUE *argv, long len) 94 | pub fn rb_ary_cat(ary: VALUE, argv: *const VALUE, len: c_long) -> VALUE; 95 | // VALUE rb_ary_clear(VALUE ary) 96 | pub fn rb_ary_clear(ary: VALUE); 97 | // VALUE rb_ary_cmp(VALUE ary1, VALUE ary2) 98 | pub fn rb_ary_cmp(ary1: VALUE, ary2: VALUE) -> VALUE; 99 | // VALUE rb_ary_delete(VALUE ary, VALUE item) 100 | pub fn rb_ary_delete(ary: VALUE, item: VALUE) -> VALUE; 101 | // VALUE rb_ary_includes(VALUE ary, VALUE item) 102 | pub fn rb_ary_includes(ary: VALUE, item: VALUE) -> VALUE; 103 | // VALUE rb_ary_join(VALUE ary, VALUE sep) 104 | pub fn rb_ary_join(ary: VALUE, sep: VALUE) -> VALUE; 105 | // VALUE rb_ary_new(void) 106 | pub fn rb_ary_new() -> VALUE; 107 | // VALUE rb_ary_new_capa(long capa) 108 | pub fn rb_ary_new_capa(capa: c_long) -> VALUE; 109 | // VALUE rb_ary_new_from_values(long n, const VALUE *elts) 110 | pub fn rb_ary_new_from_values(n: c_long, elts: *const VALUE) -> VALUE; 111 | // VALUE rb_ary_plus(VALUE x, VALUE y) 112 | pub fn rb_ary_plus(x: VALUE, y: VALUE) -> VALUE; 113 | // VALUE rb_ary_pop(VALUE ary) 114 | pub fn rb_ary_pop(ary: VALUE) -> VALUE; 115 | // VALUE rb_ary_push(VALUE ary, VALUE item) 116 | pub fn rb_ary_push(ary: VALUE, item: VALUE) -> VALUE; 117 | // VALUE rb_ary_reverse(VALUE ary) 118 | pub fn rb_ary_reverse(ary: VALUE) -> VALUE; 119 | // VALUE rb_ary_sort(VALUE ary) 120 | pub fn rb_ary_sort(ary: VALUE) -> VALUE; 121 | // VALUE rb_ary_sort_bang(VALUE ary) 122 | pub fn rb_ary_sort_bang(ary: VALUE) -> VALUE; 123 | // VALUE rb_ary_subseq(VALUE ary, long beg, long len) 124 | pub fn rb_ary_subseq(ary: VALUE, beg: c_long, len: c_long) -> VALUE; 125 | } 126 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![Banner](https://raw.githubusercontent.com/oceanpkg/rosy/assets/banner.png)](https://github.com/oceanpkg/rosy) 2 | //! 3 | //! This crate provides high-level bindings to the [Ruby] virtual machine. 4 | //! 5 | //! # Installation 6 | //! 7 | //! This crate is available [on crates.io][crate] and can be used by adding the 8 | //! following to your project's [`Cargo.toml`]: 9 | //! 10 | //! ```toml 11 | //! [dependencies] 12 | //! rosy = "0.0.9" 13 | //! ``` 14 | //! 15 | //! Rosy has functionality that is only available for certain Ruby versions. The 16 | //! following features can currently be enabled: 17 | //! 18 | //! - `ruby_2_6` 19 | //! 20 | //! For example: 21 | //! 22 | //! ```toml 23 | //! [dependencies.rosy] 24 | //! version = "0.0.9" 25 | //! features = ["ruby_2_6"] 26 | //! ``` 27 | //! 28 | //! Finally add this to your crate root (`main.rs` or `lib.rs`): 29 | //! 30 | //! ``` 31 | //! extern crate rosy; 32 | //! ``` 33 | //! 34 | //! # Initialization 35 | //! 36 | //! The Ruby virtual machine is initialized via [`vm::init`]: 37 | //! 38 | //! ``` 39 | //! rosy::vm::init().expect("Failed to initialize Ruby"); 40 | //! ``` 41 | //! 42 | //! This should be called 43 | //! once by the thread expected to be associated with Ruby. All mutations to 44 | //! Ruby objects from there on are only safe from that same thread since the VM 45 | //! is not known to be thread-safe. 46 | //! 47 | //! # Cleaning Up 48 | //! 49 | //! When done with the Ruby VM, one should call [`vm::destroy`], which will 50 | //! return a status code appropriate for exiting the program. 51 | //! 52 | //! ``` 53 | //! # rosy::vm::init().unwrap(); 54 | //! if let Err(code) = unsafe { rosy::vm::destroy() } { 55 | //! code.exit_process(); 56 | //! } 57 | //! ``` 58 | //! 59 | //! # Catching Ruby Exceptions 60 | //! 61 | //! With Rosy, your Rust code can be [`protected`](fn.protected.html) from Ruby 62 | //! exceptions when calling unchecked functions that may throw. 63 | //! 64 | //! Not catching an exception from Rust will result in a segmentation fault at 65 | //! best. As a result, every function that throws an exception is annotated as 66 | //! [`unsafe`] in Rust-land. If a function is found to not uphold this 67 | //! invariant, please report it at [issue #4][issue4] or file a pull request to 68 | //! fix this. 69 | //! 70 | //! ``` 71 | //! # rosy::vm::init().unwrap(); 72 | //! use rosy::{Object, String}; 73 | //! 74 | //! let string = String::from("hello\r\n"); 75 | //! 76 | //! rosy::protected(|| unsafe { 77 | //! string.call("chomp!"); 78 | //! }).unwrap(); 79 | //! 80 | //! assert_eq!(string.len(), 5); 81 | //! ``` 82 | //! 83 | //! [`Cargo.toml`]: https://doc.rust-lang.org/cargo/reference/manifest.html 84 | //! [crate]: https://crates.io/crates/rosy 85 | //! [Ruby]: https://www.ruby-lang.org 86 | //! [`vm::init`]: vm/fn.init.html 87 | //! [`vm::destroy`]: vm/fn.destroy.html 88 | //! [`unsafe`]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html 89 | //! [issue4]: https://github.com/oceanpkg/rosy/issues/4 90 | 91 | #![cfg_attr(nightly, feature(doc_cfg))] 92 | #![deny(missing_docs)] 93 | #![cfg_attr(all(test, nightly), feature(test))] 94 | 95 | #![doc(html_logo_url = "https://raw.githubusercontent.com/oceanpkg/rosy/assets/icon.svg?sanitize=true")] 96 | 97 | #[cfg(all(test, nightly))] 98 | extern crate test; 99 | 100 | include!(env!("ROSY_RUBY_VERSION_CONST")); 101 | 102 | #[path = "ruby_bindings/mod.rs"] 103 | mod ruby; 104 | 105 | mod rosy; 106 | mod protected; 107 | mod util; 108 | pub mod array; 109 | pub mod exception; 110 | pub mod gc; 111 | pub mod hash; 112 | pub mod meta; 113 | pub mod mixin; 114 | pub mod num; 115 | pub mod object; 116 | pub mod prelude; 117 | pub mod range; 118 | pub mod string; 119 | pub mod symbol; 120 | pub mod vm; 121 | 122 | #[doc(inline)] 123 | pub use protected::*; 124 | 125 | #[doc(inline)] // prelude 126 | pub use self::{ 127 | array::Array, 128 | exception::{AnyException, Exception}, 129 | hash::Hash, 130 | mixin::{Mixin, Class, Module}, 131 | num::{Float, Integer}, 132 | object::{AnyObject, Object, RosyObject}, 133 | range::Range, 134 | rosy::Rosy, 135 | string::String, 136 | symbol::{Symbol, SymbolId}, 137 | }; 138 | 139 | /// A simplified form of 140 | /// [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) for 141 | /// when exceptions are caught. 142 | pub type Result = std::result::Result; 143 | -------------------------------------------------------------------------------- /src/object/ty.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use crate::ruby::value_type; 3 | 4 | /// A Ruby virtual type. 5 | #[derive(Clone, Copy, PartialEq, Eq, Hash)] 6 | pub struct Ty(i32); 7 | 8 | impl From for Ty { 9 | #[inline] 10 | fn from(ty: value_type) -> Self { 11 | Ty(ty as i32) 12 | } 13 | } 14 | 15 | impl fmt::Debug for Ty { 16 | #[inline] 17 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 18 | let name = self.name().unwrap_or("Unknown"); 19 | f.debug_tuple("Ty").field(&name).finish() 20 | } 21 | } 22 | 23 | impl Ty { 24 | pub(crate) const _UNKNOWN: Ty = Ty(value_type::_Unknown as i32); 25 | 26 | /// None type. 27 | pub const NONE: Ty = Ty(value_type::NONE as i32); 28 | /// Object type. 29 | pub const OBJECT: Ty = Ty(value_type::OBJECT as i32); 30 | /// Class type. 31 | pub const CLASS: Ty = Ty(value_type::CLASS as i32); 32 | /// Module type. 33 | pub const MODULE: Ty = Ty(value_type::MODULE as i32); 34 | /// Float type. 35 | pub const FLOAT: Ty = Ty(value_type::FLOAT as i32); 36 | /// String type. 37 | pub const STRING: Ty = Ty(value_type::STRING as i32); 38 | /// Regexp type. 39 | pub const REGEXP: Ty = Ty(value_type::REGEXP as i32); 40 | /// Array type. 41 | pub const ARRAY: Ty = Ty(value_type::ARRAY as i32); 42 | /// Hash type. 43 | pub const HASH: Ty = Ty(value_type::HASH as i32); 44 | /// Struct type. 45 | pub const STRUCT: Ty = Ty(value_type::STRUCT as i32); 46 | /// Bignum type. 47 | pub const BIGNUM: Ty = Ty(value_type::BIGNUM as i32); 48 | /// File type. 49 | pub const FILE: Ty = Ty(value_type::FILE as i32); 50 | /// Data type. 51 | pub const DATA: Ty = Ty(value_type::DATA as i32); 52 | /// Match type. 53 | pub const MATCH: Ty = Ty(value_type::MATCH as i32); 54 | /// Complex type. 55 | pub const COMPLEX: Ty = Ty(value_type::COMPLEX as i32); 56 | /// Rational type. 57 | pub const RATIONAL: Ty = Ty(value_type::RATIONAL as i32); 58 | /// Nil type. 59 | pub const NIL: Ty = Ty(value_type::NIL as i32); 60 | /// True type. 61 | pub const TRUE: Ty = Ty(value_type::TRUE as i32); 62 | /// False type. 63 | pub const FALSE: Ty = Ty(value_type::FALSE as i32); 64 | /// Symbol type. 65 | pub const SYMBOL: Ty = Ty(value_type::SYMBOL as i32); 66 | /// Fixnum type. 67 | pub const FIXNUM: Ty = Ty(value_type::FIXNUM as i32); 68 | /// Undef type. 69 | pub const UNDEF: Ty = Ty(value_type::UNDEF as i32); 70 | /// IMemo type. 71 | pub const IMEMO: Ty = Ty(value_type::IMEMO as i32); 72 | /// Node type. 73 | pub const NODE: Ty = Ty(value_type::NODE as i32); 74 | /// IClass type. 75 | pub const ICLASS: Ty = Ty(value_type::ICLASS as i32); 76 | /// Zombie type. 77 | pub const ZOMBIE: Ty = Ty(value_type::ZOMBIE as i32); 78 | 79 | /// Returns the numerical identifier for the type. 80 | #[inline] 81 | pub const fn id(self) -> u32 { 82 | self.0 as u32 83 | } 84 | 85 | /// Returns a name describing the type. 86 | #[inline] 87 | pub fn name<'a>(self) -> Option<&'a str> { 88 | match self { 89 | Ty::NONE => Some("None"), 90 | Ty::OBJECT => Some("Object"), 91 | Ty::CLASS => Some("Class"), 92 | Ty::MODULE => Some("Module"), 93 | Ty::FLOAT => Some("Float"), 94 | Ty::STRING => Some("String"), 95 | Ty::REGEXP => Some("Regexp"), 96 | Ty::ARRAY => Some("Array"), 97 | Ty::HASH => Some("Hash"), 98 | Ty::STRUCT => Some("Struct"), 99 | Ty::BIGNUM => Some("Bignum"), 100 | Ty::FILE => Some("File"), 101 | Ty::DATA => Some("Data"), 102 | Ty::MATCH => Some("Match"), 103 | Ty::COMPLEX => Some("Complex"), 104 | Ty::RATIONAL => Some("Rational"), 105 | Ty::NIL => Some("Nil"), 106 | Ty::TRUE => Some("True"), 107 | Ty::FALSE => Some("False"), 108 | Ty::SYMBOL => Some("Symbol"), 109 | Ty::FIXNUM => Some("Fixnum"), 110 | Ty::UNDEF => Some("Undef"), 111 | Ty::IMEMO => Some("IMemo"), 112 | Ty::NODE => Some("Node"), 113 | Ty::ICLASS => Some("IClass"), 114 | Ty::ZOMBIE => Some("Zombie"), 115 | _ => None, 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/rust,macos,linux,windows,visualstudiocode,intellij+all 3 | # Edit at https://www.gitignore.io/?templates=rust,macos,linux,windows,visualstudiocode,intellij+all 4 | 5 | ### Intellij+all ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # Generated files 17 | .idea/**/contentModel.xml 18 | 19 | # Sensitive or high-churn files 20 | .idea/**/dataSources/ 21 | .idea/**/dataSources.ids 22 | .idea/**/dataSources.local.xml 23 | .idea/**/sqlDataSources.xml 24 | .idea/**/dynamic.xml 25 | .idea/**/uiDesigner.xml 26 | .idea/**/dbnavigator.xml 27 | 28 | # Gradle 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | 32 | # Gradle and Maven with auto-import 33 | # When using Gradle or Maven with auto-import, you should exclude module files, 34 | # since they will be recreated, and may cause churn. Uncomment if using 35 | # auto-import. 36 | # .idea/modules.xml 37 | # .idea/*.iml 38 | # .idea/modules 39 | 40 | # CMake 41 | cmake-build-*/ 42 | 43 | # Mongo Explorer plugin 44 | .idea/**/mongoSettings.xml 45 | 46 | # File-based project format 47 | *.iws 48 | 49 | # IntelliJ 50 | out/ 51 | 52 | # mpeltonen/sbt-idea plugin 53 | .idea_modules/ 54 | 55 | # JIRA plugin 56 | atlassian-ide-plugin.xml 57 | 58 | # Cursive Clojure plugin 59 | .idea/replstate.xml 60 | 61 | # Crashlytics plugin (for Android Studio and IntelliJ) 62 | com_crashlytics_export_strings.xml 63 | crashlytics.properties 64 | crashlytics-build.properties 65 | fabric.properties 66 | 67 | # Editor-based Rest Client 68 | .idea/httpRequests 69 | 70 | # Android studio 3.1+ serialized cache file 71 | .idea/caches/build_file_checksums.ser 72 | 73 | # JetBrains templates 74 | **___jb_tmp___ 75 | 76 | ### Intellij+all Patch ### 77 | # Ignores the whole .idea folder and all .iml files 78 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 79 | 80 | .idea/ 81 | 82 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 83 | 84 | *.iml 85 | modules.xml 86 | .idea/misc.xml 87 | *.ipr 88 | 89 | # Sonarlint plugin 90 | .idea/sonarlint 91 | 92 | ### Linux ### 93 | *~ 94 | 95 | # temporary files which can be created if a process still has a handle open of a deleted file 96 | .fuse_hidden* 97 | 98 | # KDE directory preferences 99 | .directory 100 | 101 | # Linux trash folder which might appear on any partition or disk 102 | .Trash-* 103 | 104 | # .nfs files are created when an open file is removed but is still being accessed 105 | .nfs* 106 | 107 | ### macOS ### 108 | # General 109 | .DS_Store 110 | .AppleDouble 111 | .LSOverride 112 | 113 | # Icon must end with two \r 114 | Icon 115 | 116 | # Thumbnails 117 | ._* 118 | 119 | # Files that might appear in the root of a volume 120 | .DocumentRevisions-V100 121 | .fseventsd 122 | .Spotlight-V100 123 | .TemporaryItems 124 | .Trashes 125 | .VolumeIcon.icns 126 | .com.apple.timemachine.donotpresent 127 | 128 | # Directories potentially created on remote AFP share 129 | .AppleDB 130 | .AppleDesktop 131 | Network Trash Folder 132 | Temporary Items 133 | .apdisk 134 | 135 | ### Rust ### 136 | # Generated by Cargo 137 | # will have compiled files and executables 138 | /target/ 139 | 140 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 141 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 142 | Cargo.lock 143 | 144 | # These are backup files generated by rustfmt 145 | **/*.rs.bk 146 | 147 | ### VisualStudioCode ### 148 | .vscode/* 149 | !.vscode/settings.json 150 | !.vscode/tasks.json 151 | !.vscode/launch.json 152 | !.vscode/extensions.json 153 | 154 | ### VisualStudioCode Patch ### 155 | # Ignore all local history of files 156 | .history 157 | 158 | ### Windows ### 159 | # Windows thumbnail cache files 160 | Thumbs.db 161 | ehthumbs.db 162 | ehthumbs_vista.db 163 | 164 | # Dump file 165 | *.stackdump 166 | 167 | # Folder config file 168 | [Dd]esktop.ini 169 | 170 | # Recycle Bin used on file shares 171 | $RECYCLE.BIN/ 172 | 173 | # Windows Installer files 174 | *.cab 175 | *.msi 176 | *.msix 177 | *.msm 178 | *.msp 179 | 180 | # Windows shortcuts 181 | *.lnk 182 | 183 | # End of https://www.gitignore.io/api/rust,macos,linux,windows,visualstudiocode,intellij+all 184 | -------------------------------------------------------------------------------- /src/object/rosy.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::c_void, 3 | fmt, 4 | marker::PhantomData, 5 | ptr, 6 | }; 7 | use crate::{ 8 | object::NonNullObject, 9 | prelude::*, 10 | ruby::{self, rb_data_type_t, rb_data_type_t_function}, 11 | }; 12 | 13 | /// An instance of a Ruby object that wraps around Rust data. 14 | /// 15 | /// See the documentation for `Rosy` for more information. 16 | #[repr(transparent)] 17 | pub struct RosyObject { 18 | inner: NonNullObject, 19 | _marker: PhantomData, 20 | } 21 | 22 | #[cfg(test)] 23 | mod assertions { 24 | use std::mem::align_of; 25 | use static_assertions::*; 26 | use super::*; 27 | 28 | #[repr(align(512))] 29 | struct AbsoluteUnit(u128, u128, u128, u128); 30 | 31 | type AbsoluteObject = RosyObject; 32 | 33 | assert_eq_size!(size; AbsoluteObject, AnyObject); 34 | const_assert_eq!(align; 35 | align_of::(), 36 | align_of::(), 37 | align_of::(), 38 | ); 39 | } 40 | 41 | impl Clone for RosyObject { 42 | #[inline] 43 | fn clone(&self) -> Self { *self } 44 | } 45 | 46 | impl Copy for RosyObject {} 47 | 48 | impl AsRef for RosyObject { 49 | #[inline] 50 | fn as_ref(&self) -> &AnyObject { 51 | self.inner.as_ref() 52 | } 53 | } 54 | 55 | impl From> for AnyObject { 56 | #[inline] 57 | fn from(obj: RosyObject) -> Self { 58 | obj.inner.into() 59 | } 60 | } 61 | 62 | impl PartialEq for RosyObject { 63 | #[inline] 64 | fn eq(&self, obj: &AnyObject) -> bool { 65 | self.as_any_object() == obj 66 | } 67 | } 68 | 69 | unsafe impl Object for RosyObject { 70 | #[inline] 71 | fn unique_id() -> Option { 72 | R::unique_object_id() 73 | } 74 | 75 | #[inline] 76 | fn cast(obj: A) -> Option { 77 | R::cast(obj) 78 | } 79 | 80 | #[inline] 81 | fn class(self) -> Class { 82 | unsafe { Class::from_raw((*self.r_typed_data()).basic.klass) } 83 | } 84 | } 85 | 86 | impl From> for RosyObject { 87 | #[inline] 88 | fn from(rosy: Box) -> Self { 89 | let rosy = Box::into_raw(rosy) as *mut c_void; 90 | let ty = RosyObject::::data_type(); 91 | let class = R::class().raw(); 92 | unsafe { 93 | Self::from_raw(ruby::rb_data_typed_object_wrap(class, rosy, ty)) 94 | } 95 | } 96 | } 97 | 98 | impl From for RosyObject { 99 | #[inline] 100 | fn from(rosy: R) -> Self { 101 | Box::new(rosy).into() 102 | } 103 | } 104 | 105 | impl fmt::Debug for RosyObject { 106 | #[inline] 107 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 108 | self.as_any_object().fmt(f) 109 | } 110 | } 111 | 112 | impl fmt::Display for RosyObject { 113 | #[inline] 114 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 115 | self.as_any_object().fmt(f) 116 | } 117 | } 118 | 119 | impl RosyObject { 120 | #[inline] 121 | pub(crate) fn data_type() -> &'static rb_data_type_t { 122 | unsafe extern "C" fn dmark(rosy: *mut c_void) { 123 | (&mut *(rosy as *mut R)).mark(); 124 | } 125 | unsafe extern "C" fn dfree(rosy: *mut c_void) { 126 | Box::from_raw(rosy as *mut R).free(); 127 | } 128 | unsafe extern "C" fn dsize(rosy: *const c_void) -> usize { 129 | (&*(rosy as *const R)).size() 130 | } 131 | &rb_data_type_t { 132 | wrap_struct_name: R::ID, 133 | function: rb_data_type_t_function { 134 | dmark: Some(dmark::), 135 | dfree: Some(dfree::), 136 | dsize: Some(dsize::), 137 | reserved: [ptr::null_mut(); 2], 138 | }, 139 | parent: ptr::null(), 140 | data: ptr::null_mut(), 141 | flags: ruby::RUBY_TYPED_FREE_IMMEDIATELY, 142 | } 143 | } 144 | 145 | #[inline] 146 | fn r_typed_data(self) -> *mut ruby::RTypedData { 147 | self.raw() as *mut ruby::RTypedData 148 | } 149 | 150 | #[inline] 151 | fn data(self) -> *mut R { 152 | unsafe { (*self.r_typed_data()).data as *mut R } 153 | } 154 | 155 | /// Returns a reference to the inner `Rosy` value. 156 | #[inline] 157 | pub fn as_data(&self) -> &R { 158 | unsafe { &*self.data() } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/num/integer/pack.rs: -------------------------------------------------------------------------------- 1 | //! [`Integer`](../struct.Integer.html) (un)packing. 2 | 3 | use std::os::raw::c_int; 4 | use crate::ruby; 5 | 6 | /// A type whose bytes can be directly used as a word when (un)packing an 7 | /// [`Integer`](../struct.Integer.html). 8 | pub unsafe trait Word: Copy { 9 | /// Whether the type is a signed integer. 10 | const IS_SIGNED: bool; 11 | 12 | /// `Self` instantiated as 0. 13 | const ZERO: Self; 14 | } 15 | 16 | macro_rules! impl_word { 17 | ($signed:expr => $($t:ty)+) => { $( 18 | unsafe impl Word for $t { 19 | const IS_SIGNED: bool = $signed; 20 | 21 | const ZERO: Self = 0; 22 | } 23 | )+ } 24 | } 25 | 26 | impl_word! { false => usize u128 u64 u32 u16 u8 } 27 | impl_word! { true => isize i128 i64 i32 i16 i8 } 28 | 29 | /// Options to use when (un)packing. 30 | #[derive(Clone, Copy, Debug)] 31 | pub struct Options { 32 | pub(super) byte_order: Order, 33 | pub(super) word_order: Order, 34 | pub(super) is_negative: bool, 35 | } 36 | 37 | impl Default for Options { 38 | #[inline] 39 | fn default() -> Self { 40 | Options { 41 | word_order: Order::Least, 42 | 43 | #[cfg(target_endian = "little")] 44 | byte_order: Order::Least, 45 | 46 | #[cfg(target_endian = "big")] 47 | byte_order: Order::Most, 48 | 49 | is_negative: false, 50 | } 51 | } 52 | } 53 | 54 | impl Options { 55 | #[inline] 56 | pub(super) fn flags(self) -> c_int { 57 | use ruby::integer_flags::*; 58 | 59 | let byte_order = match self.byte_order { 60 | Order::Least => PACK_LSBYTE_FIRST, 61 | Order::Most => PACK_MSBYTE_FIRST, 62 | }; 63 | let word_order = match self.word_order { 64 | Order::Least => PACK_LSWORD_FIRST, 65 | Order::Most => PACK_MSWORD_FIRST, 66 | }; 67 | 68 | word_order | byte_order 69 | } 70 | 71 | /// Returns a new instance for big-endian byte order. 72 | #[inline] 73 | pub fn big_endian() -> Self { 74 | Self::default().byte_order(Order::Most) 75 | } 76 | 77 | /// Returns a new instance for little-endian byte order. 78 | #[inline] 79 | pub fn little_endian() -> Self { 80 | Self::default().byte_order(Order::Least) 81 | } 82 | 83 | /// Sets the [endianness](https://en.wikipedia.org/wiki/Endianness) for each 84 | /// word. 85 | /// 86 | /// The default is the platform's native byte order: 87 | #[cfg_attr(target_endian = "little", doc = "**little-endian**.")] 88 | #[cfg_attr(target_endian = "big", doc = "**big-endian**.")] 89 | #[inline] 90 | #[must_use] 91 | pub fn byte_order(mut self, order: Order) -> Self { 92 | self.byte_order = order; 93 | self 94 | } 95 | 96 | /// Sets the order in which words should be packed. 97 | /// 98 | /// The default is least-significant first. 99 | #[inline] 100 | #[must_use] 101 | pub fn word_order(mut self, order: Order) -> Self { 102 | self.word_order = order; 103 | self 104 | } 105 | 106 | /// Makes the `Integer` instance negative. This is only used when unpacking. 107 | #[inline] 108 | pub fn is_negative(mut self) -> Self { 109 | self.is_negative = true; 110 | self 111 | } 112 | } 113 | 114 | /// An order for arranging words and the bytes of those words when calling 115 | /// [`pack_using`](../struct.Integer.html#method.pack_using). 116 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 117 | pub enum Order { 118 | /// Least-significant first. 119 | Least, 120 | /// Most-significant first. 121 | Most, 122 | } 123 | 124 | /// The sign of an [`Integer`](../struct.Integer.html) value returned after 125 | /// [`pack`](../struct.Integer.html#method.pack)ing one into a buffer. 126 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 127 | pub enum Sign { 128 | /// Packing resulted in a value equal to 0. 129 | Zero, 130 | /// Packing resulted in a positive value. 131 | Positive { 132 | /// An overflow occurred when packing an 133 | /// [`Integer`](../struct.Integer.html) into a buffer. 134 | did_overflow: bool, 135 | }, 136 | /// Packing resulted in a negative value. 137 | Negative { 138 | /// An overflow occurred when packing an 139 | /// [`Integer`](../struct.Integer.html) into a buffer. 140 | did_overflow: bool, 141 | }, 142 | } 143 | 144 | impl Sign { 145 | /// Returns whether an overflow occurred when packing an 146 | /// [`Integer`](../struct.Integer.html) into a buffer. 147 | #[inline] 148 | pub fn did_overflow(&self) -> bool { 149 | use Sign::*; 150 | match *self { 151 | Zero => false, 152 | Positive { did_overflow } | 153 | Negative { did_overflow } => did_overflow, 154 | } 155 | } 156 | 157 | /// Returns whether the sign is negative. 158 | #[inline] 159 | pub fn is_negative(&self) -> bool { 160 | if let Sign::Negative { .. } = *self { 161 | true 162 | } else { 163 | false 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/ruby_bindings/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code, non_upper_case_globals, non_snake_case)] 2 | 3 | mod prelude { 4 | pub use std::{ 5 | ffi::c_void, 6 | os::raw::{c_char, c_int, c_uint, c_long}, 7 | }; 8 | pub use super::{VALUE, ID}; 9 | } 10 | 11 | mod array; 12 | mod data; 13 | mod exception; 14 | mod float; 15 | mod gc; 16 | mod hash; 17 | mod int; 18 | mod mixin; 19 | mod object; 20 | mod range; 21 | mod string; 22 | mod symbol; 23 | mod vm; 24 | 25 | // `USE_FLONUM` is defined by `SIZEOF_VALUE >= SIZEOF_DOUBLE` 26 | #[cfg(not(target_pointer_width = "32"))] 27 | mod USE_FLONUM { // true 28 | use super::VALUE; 29 | 30 | #[inline] 31 | pub fn rb_flonum_p(x: VALUE) -> bool { 32 | (x & special_consts::FLONUM_MASK) == special_consts::FLONUM_FLAG 33 | } 34 | 35 | pub mod special_consts { 36 | pub const Qfalse: usize = 0x00; // ...0000 0000 37 | pub const Qtrue: usize = 0x14; // ...0001 0100 38 | pub const Qnil: usize = 0x08; // ...0000 1000 39 | pub const Qundef: usize = 0x34; // ...0011 0100 40 | 41 | pub const IMMEDIATE_MASK: usize = 0x07; 42 | pub const FIXNUM_FLAG: usize = 0x01; // ...xxxx xxx1 43 | pub const FLONUM_MASK: usize = 0x03; 44 | pub const FLONUM_FLAG: usize = 0x02; // ...xxxx xx10 45 | pub const SYMBOL_FLAG: usize = 0x0c; // ...0000 1100 46 | 47 | pub const SPECIAL_SHIFT: usize = 8; 48 | } 49 | } 50 | 51 | #[cfg(target_pointer_width = "32")] 52 | mod USE_FLONUM { // false 53 | use super::VALUE; 54 | 55 | #[inline] 56 | pub fn rb_flonum_p(x: VALUE) -> bool { 57 | false 58 | } 59 | 60 | pub mod special_consts { 61 | pub const Qfalse: usize = 0; // ...0000 0000 62 | pub const Qtrue: usize = 2; // ...0000 0010 63 | pub const Qnil: usize = 4; // ...0000 0100 64 | pub const Qundef: usize = 6; // ...0000 0110 65 | 66 | pub const IMMEDIATE_MASK: usize = 0x03; 67 | pub const FIXNUM_FLAG: usize = 0x01; // ...xxxx xxx1 68 | pub const FLONUM_MASK: usize = 0x00; // any values ANDed with FLONUM_MASK cannot be FLONUM_FLAG 69 | pub const FLONUM_FLAG: usize = 0x02; 70 | pub const SYMBOL_FLAG: usize = 0x0e; // ...0000 1110 71 | 72 | pub const SPECIAL_SHIFT: usize = 8; 73 | } 74 | } 75 | 76 | pub mod fl_type { 77 | pub const FL_WB_PROTECTED: usize = 1 << 5; 78 | pub const FL_PROMOTED0: usize = 1 << 5; 79 | pub const FL_PROMOTED1: usize = 1 << 6; 80 | pub const FL_PROMOTED: usize = FL_PROMOTED0 | FL_PROMOTED1; 81 | pub const FL_FINALIZE: usize = 1 << 7; 82 | pub const FL_TAINT: usize = 1 << 8; 83 | pub const FL_UNTRUSTED: usize = FL_TAINT; 84 | pub const FL_EXIVAR: usize = 1 << 10; 85 | pub const FL_FREEZE: usize = 1 << 11; 86 | 87 | pub const FL_USHIFT: usize = 12; 88 | 89 | const fn fl_user(n: usize) -> usize { 90 | 1 << (FL_USHIFT + n) 91 | } 92 | 93 | pub const FL_USER_0: usize = fl_user(0); 94 | pub const FL_USER_1: usize = fl_user(1); 95 | pub const FL_USER_2: usize = fl_user(2); 96 | pub const FL_USER_3: usize = fl_user(3); 97 | pub const FL_USER_4: usize = fl_user(4); 98 | pub const FL_USER_5: usize = fl_user(5); 99 | pub const FL_USER_6: usize = fl_user(6); 100 | pub const FL_USER_7: usize = fl_user(7); 101 | pub const FL_USER_8: usize = fl_user(8); 102 | pub const FL_USER_9: usize = fl_user(9); 103 | pub const FL_USER_10: usize = fl_user(10); 104 | pub const FL_USER_11: usize = fl_user(11); 105 | pub const FL_USER_12: usize = fl_user(12); 106 | pub const FL_USER_13: usize = fl_user(13); 107 | pub const FL_USER_14: usize = fl_user(14); 108 | pub const FL_USER_15: usize = fl_user(15); 109 | pub const FL_USER_16: usize = fl_user(16); 110 | pub const FL_USER_17: usize = fl_user(17); 111 | pub const FL_USER_18: usize = fl_user(18); 112 | } 113 | 114 | pub use self::{ 115 | array::*, 116 | data::*, 117 | exception::*, 118 | float::*, 119 | gc::*, 120 | hash::*, 121 | int::*, 122 | mixin::*, 123 | object::*, 124 | range::*, 125 | string::*, 126 | symbol::*, 127 | vm::*, 128 | USE_FLONUM::*, 129 | }; 130 | 131 | type OpaqueFn = Option; 132 | 133 | pub type VALUE = usize; 134 | 135 | #[allow(non_camel_case_types)] 136 | #[repr(C)] 137 | #[derive(Clone, Copy, PartialEq, Eq)] 138 | pub enum value_type { 139 | NONE = 0x00, 140 | 141 | OBJECT = 0x01, 142 | CLASS = 0x02, 143 | MODULE = 0x03, 144 | FLOAT = 0x04, 145 | STRING = 0x05, 146 | REGEXP = 0x06, 147 | ARRAY = 0x07, 148 | HASH = 0x08, 149 | STRUCT = 0x09, 150 | BIGNUM = 0x0a, 151 | FILE = 0x0b, 152 | DATA = 0x0c, 153 | MATCH = 0x0d, 154 | COMPLEX = 0x0e, 155 | RATIONAL = 0x0f, 156 | 157 | NIL = 0x11, 158 | TRUE = 0x12, 159 | FALSE = 0x13, 160 | SYMBOL = 0x14, 161 | FIXNUM = 0x15, 162 | UNDEF = 0x16, 163 | 164 | IMEMO = 0x1a, 165 | NODE = 0x1b, 166 | ICLASS = 0x1c, 167 | ZOMBIE = 0x1d, 168 | 169 | MASK = 0x1f, 170 | 171 | // Defined here to ensure that no other values conflict 172 | _Unknown = !0, 173 | } 174 | 175 | extern "C" { 176 | pub static ruby_api_version: [prelude::c_int; 3]; 177 | pub static ruby_version: [prelude::c_char; 0]; 178 | pub static ruby_release_date: [prelude::c_char; 0]; 179 | pub static ruby_platform: [prelude::c_char; 0]; 180 | pub static ruby_patchlevel: [prelude::c_char; 0]; 181 | pub static ruby_description: [prelude::c_char; 0]; 182 | pub static ruby_copyright: [prelude::c_char; 0]; 183 | pub static ruby_engine: [prelude::c_char; 0]; 184 | } 185 | -------------------------------------------------------------------------------- /src/rosy.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | mem, 3 | os::raw::c_char, 4 | }; 5 | use crate::prelude::*; 6 | 7 | /// A Rust type that can be used as a object. 8 | /// 9 | /// # Examples 10 | /// 11 | /// Implementing this trait allows for wrapping Rust data in a 12 | /// [`RosyObject`](object/struct.RosyObject.html). 13 | /// 14 | /// ``` 15 | /// # rosy::vm::init().unwrap(); 16 | /// use std::os::raw::c_char; 17 | /// use rosy::{Rosy, RosyObject, Class}; 18 | /// 19 | /// struct Flag<'a> { 20 | /// did_free: &'a mut bool 21 | /// } 22 | /// 23 | /// unsafe impl Rosy for Flag<'_> { 24 | /// const ID: *const c_char = "rosy_flag\0".as_ptr() as _; 25 | /// 26 | /// fn class() -> Class { 27 | /// Class::get_or_def("RosyFlag").unwrap() 28 | /// } 29 | /// 30 | /// fn mark(&self) {} 31 | /// 32 | /// fn free(self: Box) { 33 | /// *self.did_free = true; 34 | /// } 35 | /// } 36 | /// 37 | /// let mut did_free = false; 38 | /// let obj = RosyObject::from(Flag { did_free: &mut did_free }); 39 | /// 40 | /// unsafe { rosy::vm::destroy() }; 41 | /// 42 | /// assert!(did_free); 43 | /// ``` 44 | pub unsafe trait Rosy: Sized { 45 | /// A C string of the unique identifier of the type. 46 | /// 47 | /// Note that the type is not 48 | /// [`CStr`](https://doc.rust-lang.org/std/ffi/struct.CStr.html). This is 49 | /// because creating a constant instance can only be done on nightly. 50 | const ID: *const c_char; 51 | 52 | /// A unique identifier for `RosyObject` to facilitate casting. 53 | /// 54 | /// # Safety 55 | /// 56 | /// This value _must_ be unique. Rosy's built-in objects use identifiers 57 | /// that are very close to `u128::max_value()`, so those are easy to avoid. 58 | #[inline] 59 | fn unique_object_id() -> Option { 60 | None 61 | } 62 | 63 | /// Returns the class defined for this type. 64 | /// 65 | /// The default is `RustObject`, however other implementors of this trait 66 | /// should consider using a different class to define methods or properties 67 | /// on. 68 | #[inline] 69 | fn class() -> Class { 70 | Class::rust_object() 71 | } 72 | 73 | /// Attempts to create a `RosyObject` instance by casting `obj`. 74 | /// 75 | /// This could be implemented by checking against [`class`](#method.class) 76 | /// but care must be taken to ensure that all instances of this type's class 77 | /// refer to Rust data of type `Self`. 78 | /// 79 | /// The default implementation checks the 80 | /// [`unique_object_id`](#method.unique_object_id) of `Self` against the 81 | /// `unique_id` of `A`. 82 | #[inline] 83 | #[allow(unused_variables)] 84 | fn cast(obj: A) -> Option> { 85 | if A::unique_id() == Self::unique_object_id() { 86 | unsafe { Some(RosyObject::cast_unchecked(obj)) } 87 | } else { 88 | None 89 | } 90 | } 91 | 92 | /// Called during Ruby's mark phase of garbage collection to determine which 93 | /// Ruby references in `self` are live and should not be swept. 94 | /// 95 | /// # Safety 96 | /// 97 | /// This method is called during garbage collection and it is required that: 98 | /// - _All_ live Ruby objects are properly marked 99 | /// - No new Ruby objects are allocated 100 | fn mark(&self); 101 | 102 | /// Runs destructors and frees `self`. 103 | /// 104 | /// # Safety 105 | /// 106 | /// The implementor must ensure that no new Ruby objects are allocated. 107 | #[inline] 108 | fn free(self: Box) { 109 | drop(self); 110 | } 111 | 112 | /// Returns the estimated memory consumption of `self` in bytes. 113 | #[inline] 114 | fn size(&self) -> usize { 115 | mem::size_of_val(self) 116 | } 117 | } 118 | 119 | unsafe impl Rosy for &[R] { 120 | const ID: *const c_char = b"rust_slice\0".as_ptr() as _; 121 | 122 | #[inline] 123 | fn mark(&self) { 124 | self.iter().for_each(Rosy::mark); 125 | } 126 | 127 | #[inline] 128 | fn size(&self) -> usize { 129 | self.iter().fold(0, |cur, r| cur + r.size()) 130 | } 131 | } 132 | 133 | unsafe impl Rosy for &mut [R] { 134 | const ID: *const c_char = b"rust_mut_slice\0".as_ptr() as _; 135 | 136 | #[inline] 137 | fn mark(&self) { 138 | self.iter().for_each(Rosy::mark); 139 | } 140 | 141 | #[inline] 142 | fn size(&self) -> usize { 143 | self.iter().fold(0, |cur, r| cur + r.size()) 144 | } 145 | } 146 | 147 | unsafe impl Rosy for Vec { 148 | const ID: *const c_char = b"rust_vec\0".as_ptr() as _; 149 | 150 | #[inline] 151 | fn unique_object_id() -> Option { 152 | let inner = R::unique_object_id()?; 153 | let base = u128::from_le_bytes(*b"built-in Vec "); 154 | Some(inner.rotate_right(1) ^ base) 155 | } 156 | 157 | #[inline] 158 | fn mark(&self) { 159 | self.iter().for_each(Rosy::mark); 160 | } 161 | 162 | #[inline] 163 | fn size(&self) -> usize { 164 | self.iter().fold(0, |cur, r| cur + r.size()) 165 | } 166 | } 167 | 168 | unsafe impl Rosy for &str { 169 | const ID: *const c_char = b"rust_str\0".as_ptr() as _; 170 | 171 | #[inline] 172 | fn mark(&self) {} 173 | 174 | #[inline] 175 | fn size(&self) -> usize { 176 | mem::size_of_val(*self) 177 | } 178 | } 179 | 180 | unsafe impl Rosy for std::string::String { 181 | const ID: *const c_char = b"rust_string\0".as_ptr() as _; 182 | 183 | #[inline] 184 | fn unique_object_id() -> Option { 185 | Some((!0) - 0xff) 186 | } 187 | 188 | #[inline] 189 | fn mark(&self) {} 190 | 191 | #[inline] 192 | fn size(&self) -> usize { 193 | self.as_str().size() 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/num/float.rs: -------------------------------------------------------------------------------- 1 | //! Ruby floating-point numbers. 2 | 3 | use std::{ 4 | cmp::Ordering, 5 | fmt, 6 | ops::{Add, Sub, Mul, Div, Rem}, 7 | }; 8 | use crate::{ 9 | prelude::*, 10 | object::{NonNullObject, Ty}, 11 | ruby, 12 | }; 13 | 14 | /// An instance of Ruby's `Float` class. 15 | #[derive(Clone, Copy, Debug)] 16 | #[repr(transparent)] 17 | pub struct Float(NonNullObject); 18 | 19 | impl AsRef for Float { 20 | #[inline] 21 | fn as_ref(&self) -> &AnyObject { self.0.as_ref() } 22 | } 23 | 24 | impl From for AnyObject { 25 | #[inline] 26 | fn from(obj: Float) -> Self { obj.0.into() } 27 | } 28 | 29 | impl PartialEq for Float { 30 | #[inline] 31 | fn eq(&self, other: &Float) -> bool { 32 | self.to_f64() == other.to_f64() 33 | } 34 | } 35 | 36 | impl PartialEq for Float { 37 | #[inline] 38 | fn eq(&self, other: &AnyObject) -> bool { 39 | if let Some(other) = other.to_float() { 40 | *self == other 41 | } else { 42 | false 43 | } 44 | } 45 | } 46 | 47 | impl PartialEq for Float { 48 | #[inline] 49 | fn eq(&self, other: &f64) -> bool { 50 | self.to_f64() == *other 51 | } 52 | } 53 | 54 | impl PartialEq for Float { 55 | #[inline] 56 | fn eq(&self, other: &f32) -> bool { 57 | self.to_f64() == (*other as f64) 58 | } 59 | } 60 | 61 | impl PartialEq for f64 { 62 | #[inline] 63 | fn eq(&self, other: &Float) -> bool { 64 | *self == other.to_f64() 65 | } 66 | } 67 | 68 | impl PartialEq for f32 { 69 | #[inline] 70 | fn eq(&self, other: &Float) -> bool { 71 | (*self as f64) == other.to_f64() 72 | } 73 | } 74 | 75 | impl PartialOrd for Float { 76 | #[inline] 77 | fn partial_cmp(&self, other: &Float) -> Option { 78 | self.to_f64().partial_cmp(&other.to_f64()) 79 | } 80 | } 81 | 82 | impl PartialOrd for Float { 83 | #[inline] 84 | fn partial_cmp(&self, other: &f64) -> Option { 85 | self.to_f64().partial_cmp(other) 86 | } 87 | } 88 | 89 | impl PartialOrd for Float { 90 | #[inline] 91 | fn partial_cmp(&self, other: &f32) -> Option { 92 | self.partial_cmp(&(*other as f64)) 93 | } 94 | } 95 | 96 | impl PartialOrd for f64 { 97 | #[inline] 98 | fn partial_cmp(&self, other: &Float) -> Option { 99 | self.partial_cmp(&other.to_f64()) 100 | } 101 | } 102 | 103 | impl PartialOrd for f32 { 104 | #[inline] 105 | fn partial_cmp(&self, other: &Float) -> Option { 106 | Some(other.partial_cmp(self)?.reverse()) 107 | } 108 | } 109 | 110 | unsafe impl Object for Float { 111 | #[inline] 112 | fn unique_id() -> Option { 113 | Some(!(Ty::FLOAT.id() as u128)) 114 | } 115 | 116 | #[inline] 117 | fn cast(object: A) -> Option { 118 | let object = object.into_any_object(); 119 | if A::unique_id() == Self::unique_id() || object.is_float() { 120 | unsafe { Some(Self::cast_unchecked(object)) } 121 | } else { 122 | None 123 | } 124 | } 125 | 126 | #[inline] 127 | fn ty(self) -> Ty { 128 | Ty::FLOAT 129 | } 130 | 131 | #[inline] 132 | fn is_ty(self, ty: Ty) -> bool { 133 | self.ty() == ty 134 | } 135 | } 136 | 137 | impl From for Float { 138 | #[inline] 139 | fn from(f: f64) -> Self { 140 | unsafe { Self::from_raw(ruby::rb_float_new(f)) } 141 | } 142 | } 143 | 144 | impl From for Float { 145 | #[inline] 146 | fn from(f: f32) -> Self { 147 | (f as f64).into() 148 | } 149 | } 150 | 151 | impl From for AnyObject { 152 | #[inline] 153 | fn from(f: f64) -> Self { 154 | Float::from(f).into() 155 | } 156 | } 157 | 158 | impl From for AnyObject { 159 | #[inline] 160 | fn from(f: f32) -> Self { 161 | Float::from(f).into() 162 | } 163 | } 164 | 165 | impl From for f64 { 166 | #[inline] 167 | fn from(f: Float) -> Self { 168 | f.to_f64() 169 | } 170 | } 171 | 172 | macro_rules! forward_from_int { 173 | ($($i:ty)+) => { $( 174 | impl From<$i> for Float { 175 | #[inline] 176 | fn from(i: $i) -> Self { 177 | f64::from(i).into() 178 | } 179 | } 180 | )+ } 181 | } 182 | 183 | forward_from_int! { u32 i32 u16 i16 u8 i8 } 184 | 185 | macro_rules! impl_ops { 186 | ($($op:ident, $op_f:ident;)+) => { $( 187 | impl $op for Float { 188 | type Output = Self; 189 | 190 | #[inline] 191 | fn $op_f(self, rhs: Float) -> Self { 192 | self.$op_f(rhs.to_f64()) 193 | } 194 | } 195 | 196 | impl $op for Float { 197 | type Output = Self; 198 | 199 | #[inline] 200 | fn $op_f(self, rhs: f64) -> Self { 201 | self.to_f64().$op_f(rhs).into() 202 | } 203 | } 204 | 205 | impl $op for f64 { 206 | type Output = Float; 207 | 208 | #[inline] 209 | fn $op_f(self, rhs: Float) -> Float { 210 | self.$op_f(rhs.to_f64()).into() 211 | } 212 | } 213 | )+ } 214 | } 215 | 216 | impl_ops! { 217 | Add, add; 218 | Sub, sub; 219 | Mul, mul; 220 | Div, div; 221 | Rem, rem; 222 | } 223 | 224 | impl fmt::Display for Float { 225 | #[inline] 226 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 227 | self.as_any_object().fmt(f) 228 | } 229 | } 230 | 231 | impl Float { 232 | /// Performs a lossless conversion of `self` into an `f64`. 233 | #[inline] 234 | pub fn to_f64(self) -> f64 { 235 | unsafe { ruby::rb_float_value(self.raw()) } 236 | } 237 | 238 | /// Performs a lossy conversion of `self` into an `f32`. 239 | #[inline] 240 | pub fn to_f32(self) -> f32 { 241 | self.to_f64() as f32 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/protected.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | mem::{self, ManuallyDrop}, 3 | panic, 4 | ptr, 5 | thread::Result, 6 | }; 7 | use crate::{ 8 | AnyException, 9 | AnyObject, 10 | Object, 11 | ruby, 12 | }; 13 | 14 | /// Calls `f` and returns its output or an exception if one is raised in `f`. 15 | /// 16 | /// # Examples 17 | /// 18 | /// This is great for calling methods that may not exist: 19 | /// 20 | /// ``` 21 | /// # rosy::vm::init(); 22 | /// use rosy::{Object, String, protected}; 23 | /// 24 | /// let string = String::from("¡Hola!"); 25 | /// let result = protected(|| unsafe { string.call("likes_pie?") }); 26 | /// 27 | /// assert!(result.is_err()); 28 | /// ``` 29 | /// 30 | /// Calls can even be nested like so: 31 | /// 32 | /// ``` 33 | /// # rosy::vm::init(); 34 | /// use rosy::{Object, String, protected}; 35 | /// 36 | /// let string = String::from("Hiii!!!"); 37 | /// 38 | /// let outer = protected(|| { 39 | /// protected(|| unsafe { 40 | /// string.call("likes_pie?") 41 | /// }).unwrap_err(); 42 | /// string 43 | /// }); 44 | /// 45 | /// assert_eq!(outer.unwrap(), string); 46 | /// ``` 47 | pub fn protected(f: F) -> crate::Result 48 | where F: FnOnce() -> O 49 | { 50 | unsafe extern "C" fn wrapper(ctx: ruby::VALUE) -> ruby::VALUE 51 | where F: FnOnce() -> O 52 | { 53 | let (f, out) = &mut *(ctx as *mut (Option, &mut Result)); 54 | 55 | // Get the `F` out of `Option` to call by-value, which is required by 56 | // the `FnOnce` trait 57 | let f = f.take().unwrap_or_else(|| std::hint::unreachable_unchecked()); 58 | 59 | ptr::write(*out, panic::catch_unwind(panic::AssertUnwindSafe(f))); 60 | 61 | AnyObject::nil().raw() 62 | } 63 | unsafe { 64 | // These shenanigans allow us to pass in a pointer to `f`, with a 65 | // pointer to its uninitialized output, into `rb_protect` to make them 66 | // accessible from `wrapper` 67 | let mut out = ManuallyDrop::new(mem::uninitialized::>()); 68 | let mut ctx = (Some(f), &mut *out); 69 | let ctx = &mut ctx as *mut (Option, &mut _) as ruby::VALUE; 70 | 71 | let mut err = 1; 72 | ruby::rb_protect(Some(wrapper::), ctx, &mut err); 73 | match err { 74 | 0 => match ManuallyDrop::into_inner(out) { 75 | Ok(out) => Ok(out), 76 | Err(panic_info) => panic::resume_unwind(panic_info), 77 | }, 78 | _ => Err(AnyException::_take_current()), 79 | } 80 | } 81 | } 82 | 83 | /// Calls the non-panicking function `f` and returns its output or an exception 84 | /// if one is raised in `f`. 85 | /// 86 | /// See [`protected`](fn.protected.html) for usage information. 87 | /// 88 | /// This function is allowed to perform certain optimizations what wouldn't be 89 | /// possible if it needed to take panics into consideration. This can result in 90 | /// a large reduction of instructions emitted. 91 | /// 92 | /// # Safety 93 | /// 94 | /// Because `f` is called within the context of a foreign C function, panicking 95 | /// will cause undefined behavior. 96 | pub unsafe fn protected_no_panic(f: F) -> crate::Result 97 | where F: FnOnce() -> O 98 | { 99 | if crate::util::matches_ruby_size_align::() { 100 | return protected_no_panic_size_opt(f); 101 | } 102 | 103 | unsafe extern "C" fn wrapper(ctx: ruby::VALUE) -> ruby::VALUE 104 | where F: FnOnce() -> O 105 | { 106 | let (f, out) = &mut *(ctx as *mut (Option, &mut O)); 107 | 108 | // Get the `F` out of `Option` to call by-value, which is required by 109 | // the `FnOnce` trait 110 | let f = f.take().unwrap_or_else(|| std::hint::unreachable_unchecked()); 111 | 112 | ptr::write(*out, f()); 113 | 114 | AnyObject::nil().raw() 115 | } 116 | 117 | let mut out = ManuallyDrop::new(mem::uninitialized::()); 118 | let mut ctx = (Some(f), &mut *out); 119 | let ctx = &mut ctx as *mut (Option, &mut O) as ruby::VALUE; 120 | 121 | let mut err = 0; 122 | ruby::rb_protect(Some(wrapper::), ctx, &mut err); 123 | match err { 124 | 0 => Ok(ManuallyDrop::into_inner(out)), 125 | _ => Err(AnyException::_take_current()), 126 | } 127 | } 128 | 129 | // A version of `protected_no_panic` that makes use of the size and layout of 130 | // `O` matching that of `ruby::VALUE`. This slightly reduces the number of 131 | // emitted instructions and may even remove the need for stack-allocating `ctx`. 132 | #[inline] 133 | unsafe fn protected_no_panic_size_opt(f: F) -> crate::Result 134 | where F: FnOnce() -> O 135 | { 136 | unsafe extern "C" fn wrapper(ctx: ruby::VALUE) -> ruby::VALUE 137 | where F: FnOnce() -> O 138 | { 139 | let f: &mut Option = &mut *(ctx as *mut Option); 140 | 141 | // Get the `F` out of `Option` to call by-value, which is required by 142 | // the `FnOnce` trait 143 | let f = f.take().unwrap_or_else(|| std::hint::unreachable_unchecked()); 144 | 145 | let value = ManuallyDrop::new(f()); 146 | ptr::read(&value as *const ManuallyDrop as *const ruby::VALUE) 147 | } 148 | 149 | let mut ctx = Some(f); 150 | let ctx = &mut ctx as *mut Option as ruby::VALUE; 151 | 152 | let mut err = 0; 153 | let val = ruby::rb_protect(Some(wrapper::), ctx, &mut err); 154 | match err { 155 | 0 => Ok(ptr::read(&val as *const ruby::VALUE as *const O)), 156 | _ => Err(AnyException::_take_current()), 157 | } 158 | } 159 | 160 | #[cfg(test)] 161 | mod tests { 162 | use super::*; 163 | 164 | #[test] 165 | fn panic() { 166 | crate::vm::init().unwrap(); 167 | 168 | struct DropAndPanic; 169 | 170 | impl Drop for DropAndPanic { 171 | fn drop(&mut self) { 172 | panic!("This was never instantiated and shouldn't be dropped"); 173 | } 174 | } 175 | 176 | let message = "panic happened"; 177 | 178 | panic::catch_unwind(|| { 179 | protected(|| -> DropAndPanic { panic!("{}", message); }).unwrap(); 180 | }).unwrap_err(); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/mixin/module.rs: -------------------------------------------------------------------------------- 1 | //! Ruby modules. 2 | 3 | use std::fmt; 4 | use crate::{ 5 | mixin::DefMixinError, 6 | object::{NonNullObject, Ty}, 7 | prelude::*, 8 | ruby, 9 | }; 10 | 11 | /// An instance of Ruby's `Module` type. 12 | #[derive(Clone, Copy, Debug)] 13 | #[repr(transparent)] 14 | pub struct Module(NonNullObject); 15 | 16 | impl AsRef for Module { 17 | #[inline] 18 | fn as_ref(&self) -> &AnyObject { self.0.as_ref() } 19 | } 20 | 21 | impl From for AnyObject { 22 | #[inline] 23 | fn from(object: Module) -> AnyObject { object.0.into() } 24 | } 25 | 26 | unsafe impl Object for Module { 27 | #[inline] 28 | fn unique_id() -> Option { 29 | Some(!(Ty::MODULE.id() as u128)) 30 | } 31 | 32 | #[inline] 33 | fn cast(obj: A) -> Option { 34 | if obj.is_ty(Ty::MODULE) { 35 | unsafe { Some(Self::cast_unchecked(obj)) } 36 | } else { 37 | None 38 | } 39 | } 40 | 41 | #[inline] 42 | fn ty(self) -> Ty { Ty::MODULE } 43 | 44 | #[inline] 45 | fn is_ty(self, ty: Ty) -> bool { ty == Ty::MODULE } 46 | } 47 | 48 | impl crate::util::Sealed for Module {} 49 | 50 | impl fmt::Display for Module { 51 | #[inline] 52 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 53 | self.as_any_object().fmt(f) 54 | } 55 | } 56 | 57 | impl PartialEq for Module { 58 | #[inline] 59 | fn eq(&self, other: &O) -> bool { 60 | self.raw() == other.raw() 61 | } 62 | } 63 | 64 | impl Eq for Module {} 65 | 66 | impl Module { 67 | pub(crate) fn _def_under( 68 | m: impl Mixin, 69 | name: SymbolId, 70 | ) -> Result { 71 | if let Some(err) = DefMixinError::_get(m, name) { 72 | return Err(err); 73 | } else if m.is_frozen() { 74 | return Err(DefMixinError::_frozen(m)); 75 | } 76 | unsafe { 77 | let raw = ruby::rb_define_module_id_under(m.raw(), name.raw()); 78 | Ok(Self::from_raw(raw)) 79 | } 80 | } 81 | 82 | // monomorphization 83 | fn _extend(self, object: AnyObject) -> Result { 84 | unsafe { crate::protected_no_panic(|| self.extend_unchecked(object)) } 85 | } 86 | 87 | /// Extends `object` with the contents of `self`. 88 | #[inline] 89 | pub fn extend(self, object: impl Object) -> Result { 90 | self._extend(object.into()) 91 | } 92 | 93 | /// Extends `object` with the contents of `self`. 94 | /// 95 | /// # Safety 96 | /// 97 | /// The caller must ensure that `self` is not frozen or else a `FrozenError` 98 | /// exception will be raised. 99 | #[inline] 100 | pub unsafe fn extend_unchecked(self, object: impl Object) { 101 | ruby::rb_extend_object(object.raw(), self.raw()); 102 | } 103 | 104 | /// Defines a new top-level module with `name`. 105 | /// 106 | /// # Examples 107 | /// 108 | /// Defining a new module is straightforward: 109 | /// 110 | /// ``` 111 | /// # rosy::vm::init().unwrap(); 112 | /// let my_mod = rosy::Module::def("MyMod").unwrap(); 113 | /// ``` 114 | /// 115 | /// Attempting to define an existing module will result in an error: 116 | /// 117 | /// ``` 118 | /// use rosy::Module; 119 | /// # rosy::vm::init().unwrap(); 120 | /// 121 | /// let math = Module::def("Math").unwrap_err().existing_object(); 122 | /// assert_eq!(Module::math(), math.unwrap()); 123 | /// ``` 124 | #[inline] 125 | pub fn def(name: impl Into) -> Result { 126 | Class::object().def_module(name) 127 | } 128 | 129 | /// Retrieves an existing top-level `Module` defined by `name`. 130 | #[inline] 131 | pub fn get(name: impl Into) -> Option { 132 | Class::object().get_module(name) 133 | } 134 | 135 | /// Retrieves an existing top-level `Module` defined by `name`. 136 | /// 137 | /// # Safety 138 | /// 139 | /// This method does not: 140 | /// - Check whether an item for `name` exists (an exception will be thrown 141 | /// if this is the case) 142 | /// - Check whether the returned item for `name` is actually a `Module` 143 | #[inline] 144 | pub unsafe fn get_unchecked(name: impl Into) -> Self { 145 | Class::object().get_module_unchecked(name) 146 | } 147 | 148 | /// Retrieves an existing top-level `Module` defined by `name` or defines 149 | /// one if it doesn't exist. 150 | #[inline] 151 | pub fn get_or_def(name: impl Into) -> Result { 152 | match Module::def(name) { 153 | Ok(module) => Ok(module), 154 | Err(error) => if let Some(module) = error.existing_module() { 155 | Ok(module) 156 | } else { 157 | Err(error) 158 | } 159 | } 160 | } 161 | 162 | /// Returns the name of `self` or `nil` if anonymous. 163 | #[inline] 164 | pub fn name(self) -> Option { 165 | unsafe { 166 | match ruby::rb_mod_name(self.raw()) { 167 | crate::util::NIL_VALUE => None, 168 | raw => Some(String::from_raw(raw)), 169 | } 170 | } 171 | } 172 | 173 | /// Returns the ancestors of this module, including itself. 174 | #[inline] 175 | pub fn ancestors(self) -> Array { 176 | unsafe { Array::from_raw(ruby::rb_mod_ancestors(self.raw())) } 177 | } 178 | } 179 | 180 | macro_rules! built_in_modules { 181 | ($($vm_name:expr, $method:ident, $konst:ident;)+) => { 182 | /// Built-in modules. 183 | impl Module {$( 184 | /// The ` 185 | #[doc = $vm_name] 186 | ///` module. 187 | #[inline] 188 | pub fn $method() -> Self { 189 | unsafe { Self::from_raw(ruby::$konst) } 190 | } 191 | )+} 192 | } 193 | } 194 | 195 | built_in_modules! { 196 | "Kernel", kernel, rb_mKernel; 197 | "Comparable", comparable, rb_mComparable; 198 | "Enumerable", enumerable, rb_mEnumerable; 199 | "Errno", errno, rb_mErrno; 200 | "FileTest", file_test, rb_mFileTest; 201 | "GC", gc, rb_mGC; 202 | "Math", math, rb_mMath; 203 | "Process", process, rb_mProcess; 204 | "WaitReadable", wait_readable, rb_mWaitReadable; 205 | "WaitWritable", wait_writable, rb_mWaitWritable; 206 | } 207 | -------------------------------------------------------------------------------- /src/ruby_bindings/mixin.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | extern "C" { 4 | pub static rb_mKernel: VALUE; 5 | pub static rb_mComparable: VALUE; 6 | pub static rb_mEnumerable: VALUE; 7 | pub static rb_mErrno: VALUE; 8 | pub static rb_mFileTest: VALUE; 9 | pub static rb_mGC: VALUE; 10 | pub static rb_mMath: VALUE; 11 | pub static rb_mProcess: VALUE; 12 | pub static rb_mWaitReadable: VALUE; 13 | pub static rb_mWaitWritable: VALUE; 14 | 15 | pub static rb_cBasicObject: VALUE; 16 | pub static rb_cObject: VALUE; 17 | pub static rb_cArray: VALUE; 18 | pub static rb_cBinding: VALUE; 19 | pub static rb_cClass: VALUE; 20 | pub static rb_cCont: VALUE; 21 | pub static rb_cData: VALUE; 22 | pub static rb_cDir: VALUE; 23 | pub static rb_cEncoding: VALUE; 24 | pub static rb_cEnumerator: VALUE; 25 | pub static rb_cFalseClass: VALUE; 26 | pub static rb_cFile: VALUE; 27 | pub static rb_cComplex: VALUE; 28 | pub static rb_cFloat: VALUE; 29 | pub static rb_cHash: VALUE; 30 | pub static rb_cIO: VALUE; 31 | pub static rb_cInteger: VALUE; 32 | pub static rb_cMatch: VALUE; 33 | pub static rb_cMethod: VALUE; 34 | pub static rb_cModule: VALUE; 35 | pub static rb_cNameErrorMesg: VALUE; 36 | pub static rb_cNilClass: VALUE; 37 | pub static rb_cNumeric: VALUE; 38 | pub static rb_cProc: VALUE; 39 | pub static rb_cRandom: VALUE; 40 | pub static rb_cRange: VALUE; 41 | pub static rb_cRational: VALUE; 42 | pub static rb_cRegexp: VALUE; 43 | pub static rb_cStat: VALUE; 44 | pub static rb_cString: VALUE; 45 | pub static rb_cStruct: VALUE; 46 | pub static rb_cSymbol: VALUE; 47 | pub static rb_cThread: VALUE; 48 | pub static rb_cTime: VALUE; 49 | pub static rb_cTrueClass: VALUE; 50 | pub static rb_cUnboundMethod: VALUE; 51 | 52 | // Found in 'vm_core.h' 53 | pub static rb_cRubyVM: VALUE; 54 | pub static rb_cISeq: VALUE; 55 | 56 | pub static rb_eException: VALUE; 57 | pub static rb_eStandardError: VALUE; 58 | pub static rb_eSystemExit: VALUE; 59 | pub static rb_eInterrupt: VALUE; 60 | pub static rb_eSignal: VALUE; 61 | pub static rb_eFatal: VALUE; 62 | pub static rb_eArgError: VALUE; 63 | pub static rb_eEOFError: VALUE; 64 | pub static rb_eIndexError: VALUE; 65 | pub static rb_eStopIteration: VALUE; 66 | pub static rb_eKeyError: VALUE; 67 | pub static rb_eRangeError: VALUE; 68 | pub static rb_eIOError: VALUE; 69 | pub static rb_eRuntimeError: VALUE; 70 | pub static rb_eFrozenError: VALUE; 71 | pub static rb_eSecurityError: VALUE; 72 | pub static rb_eSystemCallError: VALUE; 73 | pub static rb_eThreadError: VALUE; 74 | pub static rb_eTypeError: VALUE; 75 | pub static rb_eZeroDivError: VALUE; 76 | pub static rb_eNotImpError: VALUE; 77 | pub static rb_eNoMemError: VALUE; 78 | pub static rb_eNoMethodError: VALUE; 79 | pub static rb_eFloatDomainError: VALUE; 80 | pub static rb_eLocalJumpError: VALUE; 81 | pub static rb_eSysStackError: VALUE; 82 | pub static rb_eRegexpError: VALUE; 83 | pub static rb_eEncodingError: VALUE; 84 | pub static rb_eEncCompatError: VALUE; 85 | pub static rb_eScriptError: VALUE; 86 | pub static rb_eNameError: VALUE; 87 | pub static rb_eSyntaxError: VALUE; 88 | pub static rb_eLoadError: VALUE; 89 | pub static rb_eMathDomainError: VALUE; 90 | 91 | // void rb_attr(VALUE klass, ID id, int read, int write, int ex) 92 | pub fn rb_attr(klass: VALUE, id: ID, read: c_int, write: c_int, ex: c_int); 93 | // VALUE rb_ivar_get(VALUE obj, ID id) 94 | pub fn rb_attr_get(obj: VALUE, id: ID) -> VALUE; 95 | 96 | // VALUE rb_call_super(int argc, const VALUE *argv) 97 | pub fn rb_call_super(argc: c_int, argv: *const VALUE) -> VALUE; 98 | 99 | // VALUE rb_class_inherited_p(VALUE mod, VALUE arg) 100 | pub fn rb_class_inherited_p(r#mod: VALUE, arg: VALUE) -> VALUE; 101 | // VALUE rb_class_name(VALUE klass) 102 | pub fn rb_class_name(klass: VALUE) -> VALUE; 103 | // VALUE rb_class_new_instance(int argc, const VALUE *argv, VALUE klass) 104 | pub fn rb_class_new_instance(argc: c_int, argv: *const VALUE, klass: VALUE) -> VALUE; 105 | // VALUE rb_class_superclass(VALUE klass) 106 | pub fn rb_class_superclass(klass: VALUE) -> VALUE; 107 | 108 | // int rb_const_defined(VALUE klass, ID id) 109 | pub fn rb_const_defined(klass: VALUE, id: ID) -> c_int; 110 | // VALUE rb_const_get(VALUE klass, ID id) 111 | pub fn rb_const_get(klass: VALUE, id: ID) -> VALUE; 112 | // VALUE rb_const_remove(VALUE klass, ID id) 113 | pub fn rb_const_remove(klass: VALUE, id: ID) -> VALUE; 114 | // void rb_const_set(VALUE klass, ID id, VALUE val) 115 | pub fn rb_const_set(klass: VALUE, id: ID, val: VALUE); 116 | 117 | // VALUE rb_cvar_defined(VALUE klass, ID id) 118 | pub fn rb_cvar_defined(klass: VALUE, id: ID) -> VALUE; 119 | // VALUE rb_cvar_get(VALUE klass, ID id) 120 | pub fn rb_cvar_get(klass: VALUE, id: ID) -> VALUE; 121 | // void rb_cvar_set(VALUE klass, ID id, VALUE val) 122 | pub fn rb_cvar_set(klass: VALUE, id: ID, val: VALUE); 123 | 124 | // void rb_define_method_id(VALUE klass, ID mid, VALUE (*func)(ANYARGS), int argc) 125 | pub fn rb_define_method_id( 126 | klass: VALUE, 127 | mid: ID, 128 | func: Option VALUE>, 129 | argc: c_int, 130 | ); 131 | 132 | // TODO: implement custom argument parsing rules 133 | // int rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...) 134 | pub fn rb_scan_args( 135 | argc: c_int, 136 | argv: *const VALUE, 137 | fmt: *const c_char, 138 | ... 139 | ) -> c_int; 140 | 141 | // VALUE rb_define_class_id_under(VALUE outer, ID id, VALUE super) 142 | pub fn rb_define_class_id_under(outer: VALUE, id: ID, sup: VALUE) -> VALUE; 143 | // VALUE rb_define_class_id_under(VALUE outer, ID id) 144 | pub fn rb_define_module_id_under(outer: VALUE, id: ID) -> VALUE; 145 | 146 | // void rb_prepend_module(VALUE klass, VALUE module) 147 | pub fn rb_prepend_module(klass: VALUE, module: VALUE); 148 | // void rb_include_module(VALUE klass, VALUE module) 149 | pub fn rb_include_module(klass: VALUE, module: VALUE); 150 | // void rb_extend_object(VALUE obj, VALUE module) 151 | pub fn rb_extend_object(obj: VALUE, module: VALUE); 152 | 153 | // VALUE rb_mod_ancestors(VALUE mod) 154 | pub fn rb_mod_ancestors(module: VALUE) -> VALUE; 155 | // VALUE rb_mod_include_p(VALUE mod, VALUE mod2) 156 | pub fn rb_mod_include_p(mod1: VALUE, mod2: VALUE) -> VALUE; 157 | // VALUE rb_mod_included_modules(VALUE mod) 158 | pub fn rb_mod_included_modules(module: VALUE) -> VALUE; 159 | // VALUE rb_mod_module_eval(int argc, const VALUE *argv, VALUE mod) 160 | pub fn rb_mod_module_eval(argc: c_int, argv: *const VALUE, module: VALUE) -> VALUE; 161 | // VALUE rb_mod_name(VALUE mod) 162 | pub fn rb_mod_name(module: VALUE) -> VALUE; 163 | } 164 | -------------------------------------------------------------------------------- /src/mixin/method.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | os::raw::c_int, 3 | mem, 4 | }; 5 | use crate::{ 6 | prelude::*, 7 | ruby::VALUE, 8 | }; 9 | 10 | /// An `extern "C" fn` that can be used as a method in 11 | /// [`Class::def_method`](struct.Class.html#method.def_method). 12 | pub unsafe trait MethodFn { 13 | /// The type that the method returns. 14 | type Output: Object; 15 | 16 | /// The number of arguments taken by `self`. 17 | const ARITY: c_int; 18 | 19 | /// Returns the raw function pointer for `self`. 20 | fn raw_fn(self) -> unsafe extern "C" fn() -> VALUE; 21 | } 22 | 23 | /// Defines a method on a [`Class`] instance in a simple manner. 24 | /// 25 | /// This is purely a convenience wrapper for [`def_method`] that makes the 26 | /// process much less painful and tedious. 27 | /// 28 | /// # Examples 29 | /// 30 | /// This macro skips all of the necessary type shenanigans when calling the 31 | /// method on [`Class`]. The focus is instead placed where it should be: on the 32 | /// method's definition. 33 | /// 34 | /// ```rust,edition2018 35 | /// # rosy::vm::init().unwrap(); 36 | /// # rosy::protected(|| { 37 | /// use rosy::prelude::*; 38 | /// 39 | /// let class = Class::of::(); 40 | /// 41 | /// rosy::def_method!(class, "blank?", |this: String| { 42 | /// this.is_whitespace() 43 | /// }).unwrap(); 44 | /// 45 | /// let string = String::from(" \n\r\t"); 46 | /// let output = unsafe { string.call("blank?") }; 47 | /// 48 | /// assert!(output.is_true()); 49 | /// # }).unwrap(); 50 | /// ``` 51 | /// 52 | /// All argument counts supported by [`def_method`] work here as well: 53 | /// 54 | /// ```rust,edition2018 55 | /// # rosy::vm::init().unwrap(); 56 | /// # rosy::protected(|| { 57 | /// use rosy::prelude::*; 58 | /// 59 | /// let class = Class::object(); 60 | /// 61 | /// rosy::def_method!(class, "eql_either?", |snap, crackle, pop| { 62 | /// snap == crackle || snap == pop 63 | /// }).unwrap(); 64 | /// 65 | /// let object = AnyObject::from("snap"); 66 | /// let output = unsafe { 67 | /// object.call_with("eql_either?", &[AnyObject::nil(), object]) 68 | /// }; 69 | /// 70 | /// assert!(output.is_true()); 71 | /// # }).unwrap(); 72 | /// ``` 73 | /// 74 | /// The same types supported in [`def_method`] are supported here via explicit 75 | /// type annotations: 76 | /// 77 | /// ```rust,edition2018 78 | /// # rosy::vm::init().unwrap(); 79 | /// # rosy::protected(|| { 80 | /// use rosy::prelude::*; 81 | /// 82 | /// let class = Class::of::(); 83 | /// 84 | /// rosy::def_method!(class, "plus_args", |this: Array, args: Array| { 85 | /// this.plus(args) 86 | /// }).unwrap(); 87 | /// 88 | /// let expected: &[i32] = &[0, 1, 2, 3, 4, 5, 6]; 89 | /// let array: Array = (0..4).collect(); 90 | /// 91 | /// let args = [Integer::from(4), Integer::from(5), Integer::from(6)]; 92 | /// let value = unsafe { array.call_with("plus_args", &args) }; 93 | /// 94 | /// assert_eq!(value.to_array().unwrap(), *expected); 95 | /// # }).unwrap(); 96 | /// ``` 97 | /// 98 | /// [`Class`]: struct.Class.html 99 | /// [`def_method`]: struct.Class.html#method.def_method 100 | #[macro_export] 101 | macro_rules! def_method { 102 | ( 103 | $class:expr, 104 | $name:expr, 105 | | 106 | $this:ident $(: $this_ty:ty)? 107 | $(, $args:ident $(: $args_ty:ty)?)* 108 | $(,)? 109 | | 110 | $body:expr 111 | ) => { { 112 | type __AnyObject = $crate::AnyObject; 113 | type __Class = $crate::Class; 114 | 115 | macro_rules! _replace { 116 | ($__t:tt $sub:tt) => { $sub } 117 | } 118 | macro_rules! _substitute_any_object { 119 | () => { __AnyObject }; 120 | ($__t:ty) => { $__t }; 121 | } 122 | macro_rules! _cast_class { 123 | ($c:expr,) => { __Class::into_any_class($c) }; 124 | ($c:expr, $_t:ty) => { $c }; 125 | } 126 | 127 | extern "C" fn _method( 128 | $this : _substitute_any_object!($($this_ty)?), 129 | $( $args : _substitute_any_object!($($args_ty)?) ),* 130 | ) -> AnyObject { $body.into() } 131 | 132 | let _method: extern "C" fn(_, $( _replace!($args _) ),*) -> _ = _method; 133 | 134 | let _class = _cast_class!($class, $($this_ty)?); 135 | $crate::Class::def_method(_class, $name, _method) 136 | } }; 137 | } 138 | 139 | /// Defines a method on a [`Class`](struct.Class.html) instance in a simple 140 | /// manner, without checking for exceptions. 141 | /// 142 | /// This is purely a convenience wrapper for 143 | /// [`def_method_unchecked`](struct.Class.html#method.def_method_unchecked) that 144 | /// makes the process much less painful and tedious. 145 | /// 146 | /// See [`def_method!`](macro.def_method.html) for usage info. 147 | /// 148 | /// # Safety 149 | /// 150 | /// The caller must ensure that `self` is not frozen or else a `FrozenError` 151 | /// exception will be raised. 152 | #[macro_export] 153 | macro_rules! def_method_unchecked { 154 | ( 155 | $class:expr, 156 | $name:expr, 157 | | 158 | $this:ident $(: $this_ty:ty)? 159 | $(, $args:ident $(: $args_ty:ty)?)* 160 | $(,)? 161 | | 162 | $body:expr 163 | ) => { { 164 | type __AnyObject = $crate::AnyObject; 165 | type __Class = $crate::Class; 166 | 167 | macro_rules! _replace { 168 | ($__t:tt $sub:tt) => { $sub } 169 | } 170 | macro_rules! _substitute_any_object { 171 | () => { __AnyObject }; 172 | ($__t:ty) => { $__t }; 173 | } 174 | macro_rules! _cast_class { 175 | ($c:expr,) => { __Class::into_any_class($c) }; 176 | ($c:expr, $_t:ty) => { $c }; 177 | } 178 | 179 | extern "C" fn _method( 180 | $this : _substitute_any_object!($($this_ty)?), 181 | $( $args : _substitute_any_object!($($args_ty)?) ),* 182 | ) -> AnyObject { $body.into() } 183 | 184 | let _method: extern "C" fn(_, $( _replace!($args _) ),*) -> _ = _method; 185 | 186 | let _class = _cast_class!($class, $($this_ty)?); 187 | $crate::Class::def_method_unchecked(_class, $name, _method) 188 | } }; 189 | } 190 | 191 | macro_rules! impl_trait { 192 | ($($a:expr $(,$args:ty)*;)+) => { $( 193 | impl_trait!(@fn $a, unsafe extern "C" fn(this: R $(,$args)*)); 194 | impl_trait!(@fn $a, extern "C" fn(this: R $(,$args)*)); 195 | )+ }; 196 | (@fn $a:expr, $($f:tt)+) => { 197 | unsafe impl MethodFn for $($f)+ -> O { 198 | type Output = O; 199 | 200 | const ARITY: c_int = $a; 201 | 202 | #[inline] 203 | fn raw_fn(self) -> unsafe extern "C" fn() -> VALUE { 204 | unsafe { mem::transmute(self) } 205 | } 206 | } 207 | }; 208 | } 209 | 210 | impl_trait! { 211 | -2, Array; 212 | -1, c_int, *const AnyObject; 213 | } 214 | 215 | macro_rules! replace { 216 | ($_t:tt $sub:tt) => { $sub }; 217 | } 218 | 219 | macro_rules! count { 220 | ($($t:tt)*) => { 0 $(+ replace!($t 1))* }; 221 | } 222 | 223 | // This macro lets us create an implementation of `MethodFn` on a pair of 224 | // `extern "C" fn` pairs (one being `unsafe`) for each comma token 225 | macro_rules! impl_trait_many { 226 | () => { 227 | impl_trait! { 0; } 228 | }; 229 | (, $($t:tt)*) => { 230 | impl_trait_many!($($t)*); 231 | impl_trait! { 1 + count!($($t)*), AnyObject $(, replace!($t AnyObject))* ; } 232 | }; 233 | } 234 | 235 | // 15 is the maximum arity allowed 236 | impl_trait_many!(,,,,, ,,,,, ,,,,,); 237 | -------------------------------------------------------------------------------- /src/vm/instr_seq.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, io}; 2 | use crate::{ 3 | object::NonNullObject, 4 | prelude::*, 5 | }; 6 | 7 | /// An instance of Ruby's `RubyVM::InstructionSequence` class. 8 | /// 9 | /// **Note:** The binary data that comes from an instruction sequence is not 10 | /// portable and should not be used in another version or architecture of Ruby. 11 | #[derive(Clone, Copy, Debug)] 12 | #[repr(transparent)] 13 | pub struct InstrSeq(NonNullObject); 14 | 15 | impl AsRef for InstrSeq { 16 | #[inline] 17 | fn as_ref(&self) -> &AnyObject { self.0.as_ref() } 18 | } 19 | 20 | impl From for AnyObject { 21 | #[inline] 22 | fn from(obj: InstrSeq) -> Self { obj.0.into() } 23 | } 24 | 25 | impl PartialEq for InstrSeq { 26 | #[inline] 27 | fn eq(&self, obj: &AnyObject) -> bool { 28 | self.as_any_object() == obj 29 | } 30 | } 31 | 32 | unsafe impl Object for InstrSeq { 33 | #[inline] 34 | fn unique_id() -> Option { 35 | Some((!0) - 1) 36 | } 37 | 38 | #[inline] 39 | fn cast(obj: A) -> Option { 40 | if obj.class().inherits(Class::of::()) { 41 | unsafe { Some(Self::cast_unchecked(obj)) } 42 | } else { 43 | None 44 | } 45 | } 46 | } 47 | 48 | impl fmt::Display for InstrSeq { 49 | #[inline] 50 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 51 | self.as_any_object().fmt(f) 52 | } 53 | } 54 | 55 | impl InstrSeq { 56 | #[inline] 57 | fn _compile(args: &[AnyObject]) -> Result { 58 | unsafe { 59 | Class::instr_seq() 60 | .call_with_protected(SymbolId::compile(), args) 61 | .map(|obj| Self::cast_unchecked(obj)) 62 | } 63 | } 64 | 65 | /// Compiles `script` into an instruction sequence. 66 | #[inline] 67 | pub fn compile(script: impl Into) -> Result { 68 | Self::_compile(&[script.into().into()]) 69 | } 70 | 71 | /// Compiles `script` with `options` into an instruction sequence. 72 | #[inline] 73 | pub fn compile_with( 74 | script: impl Into, 75 | options: impl Into, 76 | ) -> Result { 77 | Self::_compile(&[script.into().into(), options.into().into()]) 78 | } 79 | 80 | #[inline] 81 | fn _compile_file(args: &[AnyObject]) -> Result { 82 | unsafe { 83 | Class::instr_seq() 84 | .call_with_protected(SymbolId::compile_file(), args) 85 | .map(|obj| Self::cast_unchecked(obj)) 86 | } 87 | } 88 | 89 | /// Compiles the contents of a file at `path` into an instruction sequence. 90 | #[inline] 91 | pub fn compile_file(path: impl Into) -> Result { 92 | Self::_compile_file(&[path.into().into()]) 93 | } 94 | 95 | /// Compiles the contents of a file at `path` with `options into an 96 | /// instruction sequence. 97 | #[inline] 98 | pub fn compile_file_with( 99 | path: impl Into, 100 | options: impl Into, 101 | ) -> Result { 102 | Self::_compile_file(&[path.into().into(), options.into().into()]) 103 | } 104 | 105 | /// Loads an instruction sequence from a binary formatted string created by 106 | /// [`to_binary`](#method.to_binary). 107 | /// 108 | /// # Safety 109 | /// 110 | /// This loader does not have a verifier, so loading broken/modified binary 111 | /// causes critical problems. 112 | /// 113 | /// # Examples 114 | /// 115 | /// This is equivalent to calling 116 | /// `RubyVM::InstructionSequence.load_from_binary`: 117 | /// 118 | /// ``` 119 | /// # rosy::vm::init().unwrap(); 120 | /// # rosy::protected(|| { 121 | /// use rosy::{vm::InstrSeq, String}; 122 | /// 123 | /// let script = "'hi' * 3"; 124 | /// 125 | /// let seq1 = InstrSeq::compile(script).expect("Invalid script"); 126 | /// let seq2 = unsafe { InstrSeq::from_binary(seq1.to_binary()) }; 127 | /// 128 | /// assert_eq!(String::from("hihihi"), unsafe { seq2.eval() }); 129 | /// # }).unwrap(); 130 | /// ``` 131 | #[inline] 132 | pub unsafe fn from_binary(binary: impl Into) -> Self { 133 | Self::cast_unchecked(Class::instr_seq().call_with( 134 | SymbolId::load_from_binary(), 135 | &[binary.into()] 136 | )) 137 | } 138 | 139 | /// Evaluates `self` and returns the result. 140 | /// 141 | /// # Safety 142 | /// 143 | /// Code executed from `self` may void the type safety of objects accessible 144 | /// from Rust. For example, if one calls `push` on an `Array` with an 145 | /// object of type `B`, then the inserted object will be treated as being of 146 | /// type `A`. 147 | /// 148 | /// If this instruction sequence throws an exception, it must be caught. 149 | #[inline] 150 | pub unsafe fn eval(self) -> AnyObject { 151 | self.call(SymbolId::eval()) 152 | } 153 | 154 | /// Evaluates `self` and returns the result. 155 | /// 156 | /// # Safety 157 | /// 158 | /// Code executed from `self` may void the type safety of objects accessible 159 | /// from Rust. For example, if one calls `push` on an `Array` with an 160 | /// object of type `B`, then the inserted object will be treated as being of 161 | /// type `A`. 162 | /// 163 | /// # Examples 164 | /// 165 | /// This is equivalent to calling `eval` in a protected context: 166 | /// 167 | /// ``` 168 | /// use rosy::{vm::InstrSeq, String}; 169 | /// # rosy::vm::init().unwrap(); 170 | /// 171 | /// let script = "'hi' * 3"; 172 | /// let instr_seq = InstrSeq::compile(script).expect("Invalid script"); 173 | /// 174 | /// let output = unsafe { instr_seq.eval_protected().unwrap() }; 175 | /// assert_eq!(String::from("hihihi"), output); 176 | /// ``` 177 | #[inline] 178 | pub fn eval_protected(self) -> Result { 179 | unsafe { self.call_protected(SymbolId::eval()) } 180 | } 181 | 182 | /// Returns the serialized binary data. 183 | #[inline] 184 | pub fn to_binary(self) -> String { 185 | unsafe { String::cast_unchecked(self.call(SymbolId::to_binary())) } 186 | } 187 | 188 | /// Writes the serialized binary data of `self` to `w`. 189 | /// 190 | /// This makes it easy to write the contents of `self` to a 191 | /// [`File`](https://doc.rust-lang.org/std/fs/struct.File.html) or any other 192 | /// common I/O type. 193 | #[inline] 194 | pub fn write_binary(self, mut w: impl io::Write) -> io::Result<()> { 195 | let binary = self.to_binary(); 196 | let bytes = unsafe { binary.as_bytes() }; 197 | w.write_all(bytes) 198 | } 199 | 200 | /// Returns a human-readable form of `self`. 201 | #[inline] 202 | pub fn disassemble(self) -> String { 203 | unsafe { String::cast_unchecked(self.call(SymbolId::disasm())) } 204 | } 205 | 206 | /// Returns the file path of `self`, or `` if it was compiled from 207 | /// a string. 208 | #[inline] 209 | pub fn path(self) -> String { 210 | unsafe { String::cast_unchecked(self.call(SymbolId::path())) } 211 | } 212 | 213 | /// Returns the absolute path of `self` if it was compiled from a file. 214 | #[inline] 215 | pub fn absolute_path(self) -> Option { 216 | unsafe { 217 | let path = self.call(SymbolId::absolute_path()); 218 | if path.is_nil() { 219 | None 220 | } else { 221 | Some(String::cast_unchecked(path)) 222 | } 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog [![crates.io][crate-badge]][crate] [![docs.rs][docs-badge]][docs] 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog] and this project adheres to 5 | [Semantic Versioning]. 6 | 7 | ## [Unreleased] 8 | ### Added 9 | - `AnyObject::is_{bool|false_or_nil}` 10 | - `Range::{contains|size|len}` 11 | - Pre-interned `SymbolId` getters 12 | - `SymbolId::from_raw` 13 | - `Symbol::is_{static|dynamic}` 14 | - Concrete `Exception` types 15 | - Allows for safely creating instances of specific exceptions via 16 | `Exception::new` 17 | 18 | ### Changed 19 | - Functions to be `const`: 20 | - `Object::is_fixnum` 21 | - `SymbolId::raw` 22 | - `AnyException::class` to be faster 23 | 24 | ## [0.0.9] - 2019-05-29 25 | ### Added 26 | - `Range` object type 27 | - Generic `Classify` implementation for `Array` and `Hash` 28 | - `Symbol::{all|global_vars}` 29 | - `Object::get_attr` 30 | - `vm::backtrace` 31 | - `Array::subseq` 32 | - Ability to index with `Array::get` using a `Range` 33 | - `Integer` methods: 34 | - `to_f64` 35 | - `is_zero` 36 | - `to_s_radix` 37 | - `EvalArgs::eval_in_object` and `Object::eval` 38 | 39 | ### Changed 40 | - `Object::call`, `eval`s, `vm::load`, `vm::require` to be `unsafe` due to 41 | ability to break assumptions made by types such as `Array`, where `push` is 42 | called on a value of type `B` 43 | - Functions to be `const`: 44 | - `AnyObject::is_{nil|undefined|true|false}` 45 | - `Integer::is_{big|fix}num` 46 | - `Integer::{{max|min}_fixnum|from_fixnum_wrapping|zero}` 47 | - `Exception::raise` return type to `!` 48 | - `Mixin::attr` and friends to `Mixin::def_attr` 49 | - `Array::{contains|remove_all}` to take an arg that implements `Into` 50 | - `Array::remove_all` to return `Option` 51 | - `fixnum_value` in `Integer` to `to_fixnum` 52 | - Fixnum type for `Integer` from `i64` to `isize` 53 | - `EvalArgs::eval_in` to `eval_in_mixin` 54 | 55 | ### Fixed 56 | - `Integer::to_fixnum` value for negative numbers 57 | 58 | ## [0.0.8] - 2019-05-23 59 | ### Added 60 | - `Array`-based `PartialEq<[A]>` and `PartialEq>` implementations on 61 | `AnyObject` 62 | - `vm::DestroyError` type with the same [`NonZeroI32`] memory optimization as 63 | `vm::InitError` 64 | - Ability to specify a Ruby version via `ROSY_RUBY=client:version`; e.g. 65 | `ROSY_RUBY=rvm:2.6.0` 66 | 67 | ### Changed 68 | - Internal representation of `vm::InitError` to use [`NonZeroI32`] 69 | 70 | ### Removed 71 | - Falling back to `RUBY` environment variable if `ROSY_RUBY` is not set 72 | - `String`-based `PartialEq<[u8]>` and `PartialEq>` implementations on 73 | `AnyObject` 74 | 75 | ## [0.0.7] - 2019-05-22 76 | ### Added 77 | - `duplicate` methods to `Array` and `Hash` 78 | - More `PartialEq` and `PartialOrd` implementations to `Float` and `Integer` 79 | - `meta` module with Ruby's metadata 80 | - `From<()>` implementation for `AnyObject` 81 | - Allows for not returning anything in `def_method[_unchecked]!` 82 | - `InstrSeq::eval_unchecked` 83 | - `vm::[set_]safe_level` for managing Ruby's level of paranoia 84 | - `vm::require` and friends that allow for importing files 85 | - `vm::load[_unchecked]` that allows for simply executing files 86 | 87 | ### Changed 88 | - Where `Float` and `Integer` are located; both now reside in a `num` module 89 | - Also moved types related to `Integer::[un_]pack` into `num::pack`. 90 | - `Ty` into a `struct` with associated constants instead of an `enum` 91 | - This prevents the possibility of having an instance of `Ty` from Ruby that 92 | isn't a valid `enum` variant 93 | 94 | ### Fixed 95 | - `vm::eval` error checking 96 | 97 | ## [0.0.6] - 2019-05-21 98 | ### Added 99 | - Explicit typing to `Class` 100 | - `Object::class` and `Object::singleton_class` now return `Class` 101 | - `Classify` trait for getting a typed `Class` instance for `Self` 102 | - Defining methods on a typed class takes the class's wrapped type as a receiver 103 | - `MethodFn` now looks similar type-wise to [`FnOnce`] in that it now has an 104 | associated `Output` type and has `Receiver` instead of `Args` 105 | - Ability to specify the receiver type in `def_method[_unchecked]!` 106 | - `Float` object type 107 | - Supports arithmetic operations 108 | - Functions for evaluating Ruby scripts to the `vm` module 109 | 110 | ## [0.0.5] - 2019-05-20 111 | ### Added 112 | - Variants of `Class::new_instance` that are `unsafe` or take arguments 113 | - `PartialEq<[A]>` implementation for `Array` where `O: PartialEq` 114 | - `Partial{Eq|Cmp}` implementation over integers for `AnyObject` 115 | - `def_method[_unchecked]!` macros for convenience over 116 | `Class::def_method[_unchecked]` 117 | 118 | ### Fixed 119 | - Safety of `Class::new_instance` 120 | - Indexing into a heap-allocated `Array` 121 | 122 | ### Removed 123 | - `PartialEq + PartialOrd` from `Word` 124 | 125 | ## [0.0.4] - 2019-05-19 126 | ### Added 127 | - `Integer` object type 128 | - Features `From` conversions for every native Rust integer type, including 129 | `u128` and `i128` 130 | - Supports logical bitwise operations 131 | - Methods: 132 | - `pack` and `unpack` for converting to and from words respectively 133 | - `to_truncated` for converting similarly to `as` with primitives 134 | - `to_value` for converting similarly to `TryFrom` on primitives 135 | - Has `can_represent` helper method 136 | - [`Debug`] requirement for `Object` trait 137 | 138 | ## [0.0.3] - 2019-05-18 139 | ### Added 140 | - Typed keys and values for `Hash` 141 | - Fast encoding-checking methods to `String` that give `String::to_str` a ~7.5x 142 | performance improvement when the internal Ruby encoding is UTF-8 143 | - `encoding_is_ascii_8bit` 144 | - `encoding_is_utf8` 145 | - `encoding_is_us_ascii` 146 | - Made some methods on `Encoding` a bit faster: 147 | - `is_ascii_8bit` 148 | - `is_utf8` 149 | - `is_us_ascii` 150 | - Unsafe `protected_no_panic` variant for when the argument is guaranteed by the 151 | caller to not panic 152 | 153 | ### Fixed 154 | - `Array::cast` would pass for any objects for `Array` 155 | - `protected` is now panic-safe via [`std::panic::catch_unwind`] 156 | 157 | ### Removed 158 | - Fallback call to `is_ascii_whitespace` in `is_whitespace` on `String` 159 | 160 | ## [0.0.2] - 2019-05-17 161 | ### Added 162 | - `_skip_linking` feature flag to hopefully get https://docs.rs/rosy up 163 | 164 | ## 0.0.1 - 2019-05-17 165 | Initial release 166 | 167 | [crate]: https://crates.io/crates/rosy 168 | [crate-badge]: https://img.shields.io/crates/v/rosy.svg 169 | [docs]: https://docs.rs/rosy 170 | [docs-badge]: https://docs.rs/rosy/badge.svg 171 | 172 | [Keep a Changelog]: http://keepachangelog.com/en/1.0.0/ 173 | [Semantic Versioning]: http://semver.org/spec/v2.0.0.html 174 | 175 | [Unreleased]: https://github.com/oceanpkg/rosy/compare/v0.0.9...HEAD 176 | [0.0.9]: https://github.com/oceanpkg/rosy/compare/v0.0.8...v0.0.9 177 | [0.0.8]: https://github.com/oceanpkg/rosy/compare/v0.0.7...v0.0.8 178 | [0.0.7]: https://github.com/oceanpkg/rosy/compare/v0.0.6...v0.0.7 179 | [0.0.6]: https://github.com/oceanpkg/rosy/compare/v0.0.5...v0.0.6 180 | [0.0.5]: https://github.com/oceanpkg/rosy/compare/v0.0.4...v0.0.5 181 | [0.0.4]: https://github.com/oceanpkg/rosy/compare/v0.0.3...v0.0.4 182 | [0.0.3]: https://github.com/oceanpkg/rosy/compare/v0.0.2...v0.0.3 183 | [0.0.2]: https://github.com/oceanpkg/rosy/compare/v0.0.1...v0.0.2 184 | 185 | [`Debug`]: https://doc.rust-lang.org/std/fmt/trait.Debug.html 186 | [`FnOnce`]: https://doc.rust-lang.org/std/ops/trait.FnOnce.html 187 | [`std::panic::catch_unwind`]: https://doc.rust-lang.org/std/panic/fn.catch_unwind.html 188 | [`NonZeroI32`]: https://doc.rust-lang.org/std/num/struct.NonZeroI32.html 189 | -------------------------------------------------------------------------------- /src/gc.rs: -------------------------------------------------------------------------------- 1 | //! Ruby's garbage collector. 2 | 3 | use crate::{ 4 | prelude::*, 5 | ruby::{self, VALUE}, 6 | }; 7 | 8 | /// Starts the garbage collector. 9 | #[inline] 10 | pub fn start() { 11 | unsafe { ruby::rb_gc_start() }; 12 | } 13 | 14 | /// Tells the garbage collector how much memory is being used by an external 15 | /// library. This may trigger the GC to resize or free emory blocks. 16 | #[inline] 17 | pub fn adjust_mem_usage(diff: isize) { 18 | unsafe { ruby::rb_gc_adjust_memory_usage(diff) }; 19 | } 20 | 21 | /// Returns the number of times the GC has ran. 22 | #[inline] 23 | pub fn count() -> usize { 24 | unsafe { ruby::rb_gc_count() } 25 | } 26 | 27 | /// Disables the garbage collector, returning whether it was already previously 28 | /// disabled. 29 | #[inline] 30 | pub fn disable() -> bool { 31 | unsafe { ruby::rb_gc_disable() != 0 } 32 | } 33 | 34 | /// Enables the garbage collector, returning whether it was already previously 35 | /// enabled. 36 | #[inline] 37 | pub fn enable() -> bool { 38 | unsafe { ruby::rb_gc_enable() != 0 } 39 | } 40 | 41 | /// Calls `f` while the garbage collector is disabled. 42 | #[inline] 43 | pub fn disabled(f: F) -> O 44 | where F: FnOnce() -> O 45 | { 46 | disable(); 47 | let output = f(); 48 | enable(); 49 | output 50 | } 51 | 52 | /// Forces `obj` to be garbage-collected. 53 | /// 54 | /// # Safety 55 | /// 56 | /// The caller must ensure that `obj` does not have ownership over any 57 | /// currently-referenced memory. 58 | #[inline] 59 | pub unsafe fn force_recycle(obj: impl Object) { 60 | ruby::rb_gc_force_recycle(obj.raw()); 61 | } 62 | 63 | // Only safely usable with `Symbol` and `Hash` 64 | #[inline] 65 | unsafe fn _stat_unchecked(key: AnyObject) -> usize { 66 | ruby::rb_gc_stat(key.raw()) 67 | } 68 | 69 | // monomorphization 70 | unsafe fn _stat(key: AnyObject) -> Result { 71 | crate::protected_no_panic(|| _stat_unchecked(key)) 72 | } 73 | 74 | /// Returns the status information for `key`, or an exception if one is raised. 75 | /// 76 | /// # Examples 77 | /// 78 | /// The number of available heap slots can be retrieved as such: 79 | /// 80 | /// ``` 81 | /// # rosy::vm::init().unwrap(); 82 | /// let slots = rosy::gc::stat("heap_available_slots").unwrap(); 83 | /// assert_ne!(slots, 0); 84 | /// ``` 85 | #[inline] 86 | pub fn stat(key: impl GcInfoKey) -> Result { 87 | key.stat_gc() 88 | } 89 | 90 | /// Returns the status information for `key`. 91 | /// 92 | /// # Safety 93 | /// 94 | /// An exception may be raised if `key` is unknown. 95 | #[inline] 96 | pub unsafe fn stat_unchecked(key: impl GcInfoKey) -> usize { 97 | key.stat_gc_unchecked() 98 | } 99 | 100 | // Only safely usable with `Symbol` and `Hash` 101 | #[inline] 102 | unsafe fn _latest_info_unchecked(key: AnyObject) -> AnyObject { 103 | AnyObject::from_raw(ruby::rb_gc_latest_gc_info(key.raw())) 104 | } 105 | 106 | // monomorphization 107 | unsafe fn _latest_info(key: AnyObject) -> Result { 108 | crate::protected_no_panic(|| _latest_info_unchecked(key)) 109 | } 110 | 111 | /// Returns the latest information regarding `key` with respect to the garbage 112 | /// collector. 113 | #[inline] 114 | pub fn latest_info(key: impl GcInfoKey) -> Result { 115 | key.latest_gc_info() 116 | } 117 | 118 | /// Returns the latest information regarding `key` with respect to the garbage 119 | /// collector. 120 | /// 121 | /// # Safety 122 | /// 123 | /// An exception may be raised if `key` is unknown. 124 | #[inline] 125 | pub unsafe fn latest_info_unchecked(key: impl GcInfoKey) -> AnyObject { 126 | key.latest_gc_info_unchecked() 127 | } 128 | 129 | /// Marks the object for Ruby to avoid garbage collecting it. 130 | #[inline] 131 | pub fn mark(obj: impl Object) { 132 | unsafe { ruby::rb_gc_mark(obj.raw()) }; 133 | } 134 | 135 | /// Iterates over `objs`, `mark`ing each one. 136 | #[inline] 137 | pub fn mark_iter(objs: I) 138 | where 139 | I: IntoIterator, 140 | O: Object, 141 | { 142 | objs.into_iter().for_each(mark); 143 | } 144 | 145 | /// Marks the object for Ruby to avoid garbage collecting it. 146 | // TODO: Figure out what the difference is between this and `mark` 147 | #[inline] 148 | pub fn mark_maybe(obj: impl Object) { 149 | unsafe { ruby::rb_gc_mark_maybe(obj.raw()) }; 150 | } 151 | 152 | /// Registers the object address with the garbage collector and tells it to 153 | /// avoid collecting it. 154 | #[inline] 155 | pub fn register_mark(obj: impl Object) { 156 | unsafe { ruby::rb_gc_register_mark_object(obj.raw()) }; 157 | } 158 | 159 | /// Registers `address` with the garbage collector. 160 | #[inline] 161 | pub fn register(address: &impl Object) { 162 | let address = address as *const _ as *const VALUE as *mut VALUE; 163 | unsafe { ruby::rb_gc_register_address(address) }; 164 | } 165 | 166 | /// Unregisters `address` with the garbage collector. 167 | #[inline] 168 | pub fn unregister(address: &impl Object) { 169 | let address = address as *const _ as *const VALUE as *mut VALUE; 170 | unsafe { ruby::rb_gc_unregister_address(address) }; 171 | } 172 | 173 | /// A key that can be used to look up what the latest information is about the 174 | /// garbage collector. 175 | pub trait GcInfoKey: Sized { 176 | /// Returns the status information for `self` with respect to the garbage 177 | /// collector, or an exception if one is raised. 178 | #[inline] 179 | fn stat_gc(self) -> Result; 180 | 181 | /// Returns the status information for `self` with respect to the garbage 182 | /// collector. 183 | /// 184 | /// # Safety 185 | /// 186 | /// If the key is not available, an exception is thrown that should be 187 | /// caught and handled correctly. 188 | unsafe fn stat_gc_unchecked(self) -> usize; 189 | 190 | /// Returns the latest information regarding `self` with respect to the 191 | /// garbage collector. 192 | /// 193 | /// If an exception is raised, it is returned as a `Result::Err`. 194 | #[inline] 195 | fn latest_gc_info(self) -> Result; 196 | 197 | /// Returns the latest information regarding `self` with respect to the 198 | /// garbage collector. 199 | /// 200 | /// # Safety 201 | /// 202 | /// If the key is not available, an exception is thrown that should be 203 | /// caught and handled correctly. 204 | unsafe fn latest_gc_info_unchecked(self) -> AnyObject; 205 | } 206 | 207 | impl GcInfoKey for Hash { 208 | #[inline] 209 | fn stat_gc(self) -> Result { 210 | unsafe { _stat(self.into()) } 211 | } 212 | 213 | #[inline] 214 | unsafe fn stat_gc_unchecked(self) -> usize { 215 | _stat_unchecked(self.into()) 216 | } 217 | 218 | #[inline] 219 | fn latest_gc_info(self) -> Result { 220 | unsafe { _latest_info(self.into()) } 221 | } 222 | 223 | #[inline] 224 | unsafe fn latest_gc_info_unchecked(self) -> AnyObject { 225 | _latest_info_unchecked(self.into()) 226 | } 227 | } 228 | 229 | impl GcInfoKey for Symbol { 230 | #[inline] 231 | fn stat_gc(self) -> Result { 232 | unsafe { _stat(self.into()) } 233 | } 234 | 235 | #[inline] 236 | unsafe fn stat_gc_unchecked(self) -> usize { 237 | _stat_unchecked(self.into()) 238 | } 239 | 240 | #[inline] 241 | fn latest_gc_info(self) -> Result { 242 | unsafe { _latest_info(self.into()) } 243 | } 244 | 245 | #[inline] 246 | unsafe fn latest_gc_info_unchecked(self) -> AnyObject { 247 | _latest_info_unchecked(self.into()) 248 | } 249 | } 250 | 251 | impl GcInfoKey for &str { 252 | #[inline] 253 | fn stat_gc(self) -> Result { 254 | Symbol::from(self).stat_gc() 255 | } 256 | 257 | #[inline] 258 | unsafe fn stat_gc_unchecked(self) -> usize { 259 | Symbol::from(self).stat_gc_unchecked() 260 | } 261 | 262 | #[inline] 263 | fn latest_gc_info(self) -> Result { 264 | Symbol::from(self).latest_gc_info() 265 | } 266 | 267 | #[inline] 268 | unsafe fn latest_gc_info_unchecked(self) -> AnyObject { 269 | Symbol::from(self).latest_gc_info_unchecked() 270 | } 271 | } 272 | 273 | impl GcInfoKey for String { 274 | #[inline] 275 | fn stat_gc(self) -> Result { 276 | Symbol::from(self).stat_gc() 277 | } 278 | 279 | #[inline] 280 | unsafe fn stat_gc_unchecked(self) -> usize { 281 | Symbol::from(self).stat_gc_unchecked() 282 | } 283 | 284 | #[inline] 285 | fn latest_gc_info(self) -> Result { 286 | Symbol::from(self).latest_gc_info() 287 | } 288 | 289 | #[inline] 290 | unsafe fn latest_gc_info_unchecked(self) -> AnyObject { 291 | Symbol::from(self).latest_gc_info_unchecked() 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/ruby_bindings/string.rs: -------------------------------------------------------------------------------- 1 | use std::ptr; 2 | use super::{ 3 | fl_type, 4 | OpaqueFn, 5 | prelude::*, 6 | RBasic, 7 | }; 8 | 9 | #[repr(C)] 10 | #[derive(Clone, Copy)] 11 | pub struct RString { 12 | pub basic: RBasic, 13 | pub as_: RStringAs, 14 | } 15 | 16 | impl RString { 17 | #[inline] 18 | fn embed_len(&self) -> usize { 19 | use rstring_flags::*; 20 | 21 | const MASK: usize = EMBED_LEN_MASK >> EMBED_LEN_SHIFT; 22 | MASK & (self.basic.volatile_flags() >> EMBED_LEN_SHIFT) 23 | } 24 | 25 | #[inline] 26 | fn is_embedded(&self) -> bool { 27 | self.basic.volatile_flags() & rstring_flags::NO_EMBED == 0 28 | } 29 | 30 | #[inline] 31 | pub fn is_locked(&self) -> bool { 32 | self.basic.volatile_flags() & STR_TMPLOCK != 0 33 | } 34 | 35 | #[inline] 36 | pub fn len(&self) -> usize { 37 | if self.is_embedded() { 38 | self.embed_len() 39 | } else { 40 | unsafe { ptr::read_volatile(&self.as_.heap.len) as usize } 41 | } 42 | } 43 | 44 | #[inline] 45 | pub fn start(&self) -> *const c_char { 46 | if self.is_embedded() { 47 | unsafe { self.as_.ary.as_ptr() } 48 | } else { 49 | unsafe { ptr::read_volatile(&self.as_.heap.ptr) } 50 | } 51 | } 52 | 53 | #[inline] 54 | pub fn start_mut(&mut self) -> *mut c_char { 55 | if self.is_embedded() { 56 | unsafe { self.as_.ary.as_mut_ptr() } 57 | } else { 58 | unsafe { ptr::read_volatile(&self.as_.heap.ptr) as *mut c_char } 59 | } 60 | } 61 | } 62 | 63 | #[repr(C)] 64 | #[derive(Clone, Copy)] 65 | pub union RStringAs { 66 | pub heap: RStringHeap, 67 | pub ary: [c_char; rstring_flags::EMBED_LEN_MAX + 1], 68 | } 69 | 70 | #[repr(C)] 71 | #[derive(Clone, Copy)] 72 | pub struct RStringHeap { 73 | pub len: c_long, 74 | pub ptr: *const c_char, 75 | pub aux: RStringHeapAux, 76 | } 77 | 78 | #[repr(C)] 79 | #[derive(Clone, Copy)] 80 | pub union RStringHeapAux { 81 | pub capa: c_long, 82 | pub shared: VALUE, 83 | } 84 | 85 | #[repr(C)] 86 | pub struct rb_encoding { 87 | _precise_mbc_enc_len: OpaqueFn, 88 | pub name: *const c_char, 89 | pub max_enc_len: c_int, 90 | pub min_enc_len: c_int, 91 | _is_mbc_newline: OpaqueFn, 92 | _mbc_to_code: OpaqueFn, 93 | _code_to_mbclen: OpaqueFn, 94 | _code_to_mbc: OpaqueFn, 95 | _mbc_case_fold: OpaqueFn, 96 | _apply_all_case_fold: OpaqueFn, 97 | _get_case_fold_codes_by_str: OpaqueFn, 98 | _property_name_to_ctype: OpaqueFn, 99 | _is_code_ctype: OpaqueFn, 100 | _get_ctype_code_range: OpaqueFn, 101 | _left_adjust_char_head: OpaqueFn, 102 | _is_allowed_reverse_match: OpaqueFn, 103 | _case_map: OpaqueFn, 104 | pub ruby_encoding_index: c_int, 105 | pub flags: c_uint, 106 | } 107 | 108 | impl rb_encoding { 109 | #[inline] 110 | pub fn ascii_8bit_index() -> c_int { 111 | let index = preserved_encindex::ASCII as c_int; 112 | debug_assert_eq!(index, unsafe { rb_ascii8bit_encindex() }); 113 | index 114 | } 115 | 116 | #[inline] 117 | pub fn utf8_index() -> c_int { 118 | let index = preserved_encindex::UTF_8 as c_int; 119 | debug_assert_eq!(index, unsafe { rb_utf8_encindex() }); 120 | index 121 | } 122 | 123 | #[inline] 124 | pub fn us_ascii_index() -> c_int { 125 | let index = preserved_encindex::US_ASCII as c_int; 126 | debug_assert_eq!(index, unsafe { rb_usascii_encindex() }); 127 | index 128 | } 129 | 130 | #[inline] 131 | pub fn index(&mut self) -> c_int { 132 | let index = self.ruby_encoding_index & ENC_INDEX_MASK; 133 | debug_assert_eq!(index, unsafe { rb_enc_to_index(self) }); 134 | index 135 | } 136 | } 137 | 138 | #[repr(C)] 139 | #[allow(non_camel_case_types)] 140 | pub enum preserved_encindex { 141 | ASCII, 142 | UTF_8, 143 | US_ASCII, 144 | 145 | /* preserved indexes */ 146 | UTF_16BE, 147 | UTF_16LE, 148 | UTF_32BE, 149 | UTF_32LE, 150 | UTF_16, 151 | UTF_32, 152 | UTF_8_MAC, 153 | 154 | /* for old options of regexp */ 155 | EUC_JP, 156 | Windows_31J, 157 | 158 | BUILTIN_MAX, 159 | } 160 | 161 | pub const ENC_INDEX_MASK: c_int = !(!(0 as c_uint) << 24) as c_int; 162 | 163 | pub const STR_TMPLOCK: VALUE = fl_type::FL_USER_7; 164 | 165 | pub mod rstring_flags { 166 | use std::mem::size_of; 167 | use super::{*, fl_type::*}; 168 | 169 | pub const NO_EMBED: usize = FL_USER_1; 170 | 171 | pub const EMBED_LEN_MASK: usize = FL_USER_2 | FL_USER_3 | FL_USER_4 | FL_USER_5 | FL_USER_6; 172 | pub const EMBED_LEN_SHIFT: usize = FL_USHIFT + 2; 173 | pub const EMBED_LEN_MAX: usize = (size_of::() * 3) / size_of::() - 1; 174 | 175 | pub const FSTR: usize = FL_USER_17; 176 | } 177 | 178 | // Taken from `ruby_encoding_consts` in 'encoding.h' 179 | pub mod encoding_consts { 180 | pub const INLINE_MAX: usize = 127; 181 | pub const SHIFT: usize = super::fl_type::FL_USHIFT + 10; 182 | pub const MASK: usize = INLINE_MAX << SHIFT; 183 | pub const MAX_NAME_LEN: usize = 42; 184 | } 185 | 186 | impl RBasic { 187 | // Taken from `RB_ENCODING_GET_INLINED(obj)` in 'encoding.h' 188 | #[inline] 189 | pub fn encoding_index(&self) -> c_int { 190 | use encoding_consts::*; 191 | ((self.flags & MASK) >> SHIFT) as c_int 192 | } 193 | } 194 | 195 | extern "C" { 196 | // VALUE rb_str_buf_new(long capa) 197 | pub fn rb_str_buf_new(capa: c_long) -> VALUE; 198 | // VALUE rb_external_str_new_with_enc(const char *ptr, long len, rb_encoding *eenc) 199 | pub fn rb_external_str_new_with_enc(ptr: *const c_char, len: c_long, enc: *mut rb_encoding) -> VALUE; 200 | 201 | // VALUE rb_str_cat(VALUE str, const char *ptr, long len) 202 | pub fn rb_str_cat(str: VALUE, ptr: *const c_char, len: c_long) -> VALUE; 203 | // int rb_str_cmp(VALUE str1, VALUE str2) 204 | pub fn rb_str_cmp(str1: VALUE, str2: VALUE) -> c_int; 205 | // VALUE rb_str_dup(VALUE str) 206 | pub fn rb_str_dup(str: VALUE) -> VALUE; 207 | // VALUE rb_str_ellipsize(VALUE str, long len) 208 | pub fn rb_str_ellipsize(str: VALUE, len: c_long) -> VALUE; 209 | // VALUE rb_str_equal(VALUE str1, VALUE str2) 210 | pub fn rb_str_equal(str1: VALUE, str2: VALUE) -> VALUE; 211 | // VALUE rb_str_new(const char *ptr, long len) 212 | pub fn rb_str_new(ptr: *const c_char, len: c_long) -> VALUE; 213 | // VALUE rb_utf8_str_new(const char *ptr, long len) 214 | pub fn rb_utf8_str_new(ptr: *const c_char, len: c_long) -> VALUE; 215 | // long rb_str_strlen(VALUE str) 216 | pub fn rb_str_strlen(str: VALUE) -> c_long; 217 | 218 | // VALUE rb_str_locktmp(VALUE str) 219 | pub fn rb_str_locktmp(str: VALUE) -> VALUE; 220 | // VALUE rb_str_unlocktmp(VALUE str) 221 | pub fn rb_str_unlocktmp(str: VALUE) -> VALUE; 222 | } 223 | 224 | // Encoding 225 | extern "C" { 226 | // rb_encoding * rb_default_external_encoding(void) 227 | pub fn rb_default_external_encoding() -> *mut rb_encoding; 228 | // rb_encoding * rb_default_internal_encoding(void) 229 | pub fn rb_default_internal_encoding() -> *mut rb_encoding; 230 | 231 | // VALUE rb_enc_associate_index(VALUE obj, int idx) 232 | pub fn rb_enc_associate_index(obj: VALUE, idx: c_int) -> VALUE; 233 | 234 | // int rb_enc_find_index(const char *name) 235 | pub fn rb_enc_find_index(name: *const c_char) -> c_int; 236 | // VALUE rb_enc_from_encoding(rb_encoding *encoding) 237 | pub fn rb_enc_from_encoding(encoding: *mut rb_encoding) -> VALUE; 238 | // rb_encoding * rb_enc_from_index(int index) 239 | pub fn rb_enc_from_index(index: c_int) -> *mut rb_encoding; 240 | // int rb_enc_get_index(VALUE obj) 241 | pub fn rb_enc_get_index(obj: VALUE) -> c_int; 242 | // int rb_enc_to_index(rb_encoding *enc) 243 | pub fn rb_enc_to_index(enc: *mut rb_encoding) -> c_int; 244 | 245 | // int rb_filesystem_encindex(void) 246 | pub fn rb_filesystem_encindex() -> c_int; 247 | // int rb_locale_encindex(void) 248 | pub fn rb_locale_encindex() -> c_int; 249 | // int rb_ascii8bit_encindex(void) 250 | pub fn rb_ascii8bit_encindex() -> c_int; 251 | // rb_encoding * rb_ascii8bit_encoding(void) 252 | pub fn rb_ascii8bit_encoding() -> *mut rb_encoding; 253 | // int rb_usascii_encindex(void) 254 | pub fn rb_usascii_encindex() -> c_int; 255 | // rb_encoding * rb_usascii_encoding(void) 256 | pub fn rb_usascii_encoding() -> *mut rb_encoding; 257 | // int rb_utf8_encindex(void) 258 | pub fn rb_utf8_encindex() -> c_int; 259 | // rb_encoding * rb_utf8_encoding(void) 260 | pub fn rb_utf8_encoding() -> *mut rb_encoding; 261 | 262 | // rb_encoding * rb_to_encoding(VALUE enc) 263 | pub fn rb_to_encoding(enc: VALUE) -> *mut rb_encoding; 264 | } 265 | -------------------------------------------------------------------------------- /src/range/mod.rs: -------------------------------------------------------------------------------- 1 | //! Ruby ranges. 2 | 3 | use std::{ 4 | fmt, 5 | marker::PhantomData, 6 | ops::Bound, 7 | os::raw::c_int, 8 | }; 9 | use crate::{ 10 | prelude::*, 11 | object::NonNullObject, 12 | ruby, 13 | }; 14 | 15 | mod into_bounds; 16 | pub use into_bounds::*; 17 | 18 | /// An instance of Ruby's `Range` type. 19 | /// 20 | /// This type supports being instantiated from [`Range`], [`RangeInclusive`], 21 | /// and [`RangeTo`]. 22 | /// 23 | /// # `a..b` and `a...b` versus `a..b` and `a..=b` 24 | /// 25 | /// In Rust (and many other languages), `a..b` denotes an _inclusive_ range. 26 | /// However, in Ruby this syntax denotes an _exclusive_ range. 27 | /// 28 | /// In Rust, `a..=b` denotes an _exclusive_ range whereas in Ruby, `...` denotes 29 | /// an _inclusive_ range. 30 | /// 31 | /// # Examples 32 | /// 33 | /// An exclusive range can be instantiated quite simply: 34 | /// 35 | /// ``` 36 | /// # rosy::vm::init().unwrap(); 37 | /// use rosy::{Range, Integer, Object}; 38 | /// 39 | /// let range = Range::::new(0..10).unwrap(); 40 | /// assert_eq!(range.to_s(), "0...10"); 41 | /// ``` 42 | /// 43 | /// The start and end bounds can be retrieved via `into_bounds`: 44 | /// 45 | /// ``` 46 | /// # rosy::vm::init().unwrap(); 47 | /// # use rosy::{Range, Integer, Object}; 48 | /// use std::ops::Bound; 49 | /// 50 | /// let range = Range::::new(1..=10).unwrap(); 51 | /// 52 | /// let (start, end) = range.into_bounds(); 53 | /// 54 | /// assert_eq!(start, 1); 55 | /// assert_eq!(end, Bound::Included(Integer::from(10))); 56 | /// ``` 57 | /// 58 | /// [`Range`]: https://doc.rust-lang.org/std/ops/struct.Range.html 59 | /// [`RangeInclusive`]: https://doc.rust-lang.org/std/ops/struct.RangeInclusive.html 60 | /// [`RangeTo`]: https://doc.rust-lang.org/std/ops/struct.RangeTo.html 61 | #[repr(transparent)] 62 | pub struct Range { 63 | inner: NonNullObject, 64 | _marker: PhantomData<(S, E)>, 65 | } 66 | 67 | impl Clone for Range { 68 | fn clone(&self) -> Self { *self } 69 | } 70 | 71 | impl Copy for Range {} 72 | 73 | impl AsRef for Range { 74 | #[inline] 75 | fn as_ref(&self) -> &AnyObject { self.inner.as_ref() } 76 | } 77 | 78 | impl From> for AnyObject { 79 | #[inline] 80 | fn from(object: Range) -> AnyObject { object.inner.into() } 81 | } 82 | 83 | impl PartialEq for Range { 84 | #[inline] 85 | fn eq(&self, obj: &AnyObject) -> bool { 86 | self.as_any_object() == obj 87 | } 88 | } 89 | 90 | impl fmt::Debug for Range { 91 | #[inline] 92 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 93 | f.debug_tuple("Range") 94 | .field(&self.inner) 95 | .finish() 96 | } 97 | } 98 | 99 | unsafe impl Object for Range { 100 | } 101 | 102 | impl IntoBounds for Range { 103 | #[inline] 104 | fn into_bounds(self) -> (S, Bound) { 105 | unsafe { 106 | let mut start: ruby::VALUE = 0; 107 | let mut end: ruby::VALUE = 0; 108 | let mut excl: c_int = 0; 109 | ruby::rb_range_values(self.raw(), &mut start, &mut end, &mut excl); 110 | 111 | let start = S::from_raw(start); 112 | 113 | let end = if end == crate::util::NIL_VALUE { 114 | Bound::Unbounded 115 | } else { 116 | let end = E::from_raw(end); 117 | if excl != 0 { 118 | Bound::Excluded(end) 119 | } else { 120 | Bound::Included(end) 121 | } 122 | }; 123 | 124 | (start, end) 125 | } 126 | } 127 | } 128 | 129 | impl Range { 130 | /// Creates a new instance from the given bounds, returning an exception if 131 | /// one is raised. 132 | /// 133 | /// If `end` is `nil`, an infinite (unbounded) range is produced. 134 | pub fn from_bounds( 135 | start: AnyObject, 136 | end: AnyObject, 137 | exclusive: bool, 138 | ) -> Result { 139 | unsafe { 140 | crate::protected_no_panic(|| { 141 | Self::from_bounds_unchecked(start, end, exclusive) 142 | }) 143 | } 144 | } 145 | 146 | /// Creates a new instance from the given bounds, without checking for 147 | /// exceptions. 148 | /// 149 | /// If `end` is `nil`, an infinite (unbounded) range is produced. 150 | /// 151 | /// # Safety 152 | /// 153 | /// An exception may be raised if `start` and `end` cannot be compared. 154 | #[inline] 155 | pub unsafe fn from_bounds_unchecked( 156 | start: AnyObject, 157 | end: AnyObject, 158 | exclusive: bool, 159 | ) -> Self { 160 | let raw = ruby::rb_range_new(start.raw(), end.raw(), exclusive as _); 161 | Self::from_raw(raw) 162 | } 163 | } 164 | 165 | impl Range { 166 | /// Creates a new instance from `range`, returning an exception if one is 167 | /// raised. 168 | #[inline] 169 | pub fn new(range: R) -> Result 170 | where 171 | R: IntoBounds, 172 | A: Into, 173 | B: Into, 174 | { 175 | let (start, end) = range.into_bounds(); 176 | let start = start.into().into_any_object(); 177 | let (end, exclusive) = match end { 178 | Bound::Included(end) => (end.into().into_any_object(), false), 179 | Bound::Excluded(end) => (end.into().into_any_object(), true), 180 | Bound::Unbounded => (AnyObject::nil(), true), 181 | }; 182 | unsafe { 183 | let range = Range::from_bounds(start, end, exclusive)?; 184 | Ok(Self::cast_unchecked(range)) 185 | } 186 | } 187 | 188 | /// Creates a new instance from `range`, without checking for exceptions. 189 | /// 190 | /// # Safety 191 | /// 192 | /// An exception may be raised if `S` and `E` cannot be compared. 193 | #[inline] 194 | pub unsafe fn new_unchecked(range: R) -> Self 195 | where 196 | R: IntoBounds, 197 | A: Into, 198 | B: Into, 199 | { 200 | let (start, end) = range.into_bounds(); 201 | let start = start.into().into_any_object(); 202 | let (end, exclusive) = match end { 203 | Bound::Included(end) => (end.into().into_any_object(), false), 204 | Bound::Excluded(end) => (end.into().into_any_object(), true), 205 | Bound::Unbounded => (AnyObject::nil(), true), 206 | }; 207 | let range = Range::from_bounds_unchecked(start, end, exclusive); 208 | Self::cast_unchecked(range) 209 | } 210 | 211 | /// Returns a range over `AnyObject`s. 212 | #[inline] 213 | pub fn into_any_range(self) -> Range { 214 | unsafe { Range::cast_unchecked(self) } 215 | } 216 | 217 | /// Returns the start (inclusive) and end bounds of `self`. 218 | #[inline] 219 | pub fn into_bounds(self) -> (S, Bound) { 220 | IntoBounds::into_bounds(self) 221 | } 222 | 223 | /// Returns whether `obj` is contained within `self`. 224 | /// 225 | /// # Examples 226 | /// 227 | /// This corresponds to the `include?` method in Ruby. 228 | /// 229 | /// ``` 230 | /// # rosy::vm::init().unwrap(); 231 | /// use rosy::prelude::*; 232 | /// 233 | /// let range = Range::::new(10..15).unwrap(); 234 | /// 235 | /// assert!(range.contains(12)); 236 | /// ``` 237 | #[inline] 238 | pub fn contains(self, obj: impl Into) -> bool { 239 | let include = SymbolId::include_q(); 240 | unsafe { self.call_with(include, &[obj.into()]).is_true() } 241 | } 242 | 243 | /// Returns the size of `self` as an `Integer`, if the bounds are `Numeric` 244 | /// values. 245 | #[inline] 246 | pub fn size(self) -> Option { 247 | unsafe { 248 | let size = self.call(SymbolId::size()); 249 | if size.is_nil() { 250 | None 251 | } else { 252 | Some(Integer::cast_unchecked(size)) 253 | } 254 | } 255 | } 256 | 257 | /// Returns the size of `self` as a `usize`, if the bounds are `Numeric` 258 | /// values and the value can be represented as a `usize`. 259 | /// 260 | /// # Examples 261 | /// 262 | /// ``` 263 | /// # rosy::vm::init().unwrap(); 264 | /// use rosy::prelude::*; 265 | /// 266 | /// let range = Range::::new(0..10).unwrap(); 267 | /// 268 | /// assert_eq!(range.len(), Some(10)); 269 | /// ``` 270 | #[inline] 271 | pub fn len(self) -> Option { 272 | self.size()?.to_value() 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/hash.rs: -------------------------------------------------------------------------------- 1 | //! Ruby hash tables. 2 | 3 | use std::{ 4 | fmt, 5 | iter::FromIterator, 6 | marker::PhantomData, 7 | }; 8 | use crate::{ 9 | object::{NonNullObject, Ty}, 10 | prelude::*, 11 | ruby, 12 | }; 13 | 14 | /// An instance of Ruby's `Hash` class. 15 | #[repr(transparent)] 16 | pub struct Hash { 17 | inner: NonNullObject, 18 | _marker: PhantomData<(*mut K, *mut V)> 19 | } 20 | 21 | impl Clone for Hash { 22 | #[inline] 23 | fn clone(&self) -> Self { *self } 24 | } 25 | 26 | impl Copy for Hash {} 27 | 28 | impl AsRef for Hash { 29 | #[inline] 30 | fn as_ref(&self) -> &AnyObject { self.inner.as_ref() } 31 | } 32 | 33 | impl From> for AnyObject { 34 | #[inline] 35 | fn from(object: Hash) -> AnyObject { object.inner.into() } 36 | } 37 | 38 | impl PartialEq for Hash { 39 | #[inline] 40 | fn eq(&self, obj: &AnyObject) -> bool { 41 | self.as_any_object() == obj 42 | } 43 | } 44 | 45 | unsafe impl Object for Hash { 46 | #[inline] 47 | fn unique_id() -> Option { 48 | let key = K::unique_id()?; 49 | let val = V::unique_id()?; 50 | let hash = !(Ty::HASH.id() as u128); 51 | Some(key.rotate_left(11) ^ val.rotate_right(7) ^ hash) 52 | } 53 | 54 | #[inline] 55 | fn cast(obj: A) -> Option { 56 | if obj.is_ty(Ty::HASH) { 57 | unsafe { Some(Self::from_raw(obj.raw())) } 58 | } else { 59 | None 60 | } 61 | } 62 | 63 | #[inline] 64 | fn ty(self) -> Ty { Ty::HASH } 65 | 66 | #[inline] 67 | fn is_ty(self, ty: Ty) -> bool { ty == Ty::HASH } 68 | } 69 | 70 | impl fmt::Display for Hash { 71 | #[inline] 72 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 73 | self.as_any_object().fmt(f) 74 | } 75 | } 76 | 77 | impl fmt::Debug for Hash { 78 | #[inline] 79 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 80 | f.debug_tuple("Hash") 81 | .field(&self.inner) 82 | .finish() 83 | } 84 | } 85 | 86 | #[cfg(feature = "ruby_2_6")] 87 | impl From<&[(K, V)]> for Hash { 88 | #[inline] 89 | fn from(pairs: &[(K, V)]) -> Self { 90 | Self::from_pairs(pairs) 91 | } 92 | } 93 | 94 | impl FromIterator<(K2, V2)> for Hash 95 | where K1: Object, K2: Into, V1: Object, V2: Into 96 | { 97 | #[inline] 98 | fn from_iter>(iter: I) -> Self { 99 | let hash = Self::new(); 100 | for (key, val) in iter { 101 | unsafe { hash.insert(key.into(), val.into()) }; 102 | } 103 | hash 104 | } 105 | } 106 | 107 | impl Hash { 108 | /// Creates a new hash table. 109 | #[inline] 110 | pub fn new() -> Self { 111 | unsafe { Self::from_raw(ruby::rb_hash_new()) } 112 | } 113 | 114 | /// Creates an instance from the key-value pairs in `map`. 115 | /// 116 | /// # Examples 117 | /// 118 | /// This initializer is general enough to work with most map types. The most 119 | /// common use case would probably be interacting with Rust's [`HashMap`]. 120 | /// 121 | /// ``` 122 | /// # rosy::vm::init().unwrap(); 123 | /// # rosy::protected(|| { 124 | /// use std::collections::HashMap; 125 | /// use rosy::prelude::*; 126 | /// 127 | /// let mut map = HashMap::new(); 128 | /// map.insert("is_working", true); 129 | /// 130 | /// let hash = Hash::::from_map(&map); 131 | /// assert_eq!(hash.get("is_working").unwrap(), true); 132 | /// # }).unwrap(); 133 | /// ``` 134 | /// 135 | /// [`HashMap`]: https://doc.rust-lang.org/std/collections/struct.HashMap.html 136 | #[inline] 137 | pub fn from_map<'a, M, MK, MV>(map: M) -> Self 138 | where 139 | M: IntoIterator, 140 | MK: Copy + Into + 'a, 141 | MV: Copy + Into + 'a, 142 | { 143 | map.into_iter().map(|(&k, &v)| (k, v)).collect() 144 | } 145 | 146 | /// Creates a new hash table from `pairs`. 147 | /// 148 | #[cfg_attr(not(nightly), doc = "**Requires feature:** `ruby_2_6`")] 149 | /// 150 | /// # Examples 151 | /// 152 | /// Although this may insert the objects efficiently, it requires a bit more 153 | /// verbose code with explicit 154 | /// [`Into`](https://doc.rust-lang.org/std/convert/trait.Into.html) 155 | /// conversions from non-Ruby types. 156 | /// 157 | /// ``` 158 | /// # rosy::vm::init().unwrap(); 159 | /// # rosy::protected(|| { 160 | /// use rosy::{Hash, String}; 161 | /// 162 | /// let hash = Hash::::from_pairs(&[ 163 | /// ("user".into(), "nvzqz".into()), 164 | /// ("name".into(), "Nikolai Vazquez".into()), 165 | /// ]); 166 | /// 167 | /// assert_eq!(hash.get("user").unwrap(), "nvzqz"); 168 | /// assert_eq!(hash.get("name").unwrap(), "Nikolai Vazquez"); 169 | /// # }).unwrap(); 170 | /// ``` 171 | #[cfg(feature = "ruby_2_6")] 172 | #[cfg_attr(nightly, doc(cfg(feature = "ruby_2_6")))] 173 | #[inline] 174 | pub fn from_pairs(pairs: &[(K, V)]) -> Self { 175 | let hash = Self::new(); 176 | unsafe { hash.insert_pairs(pairs) }; 177 | hash 178 | } 179 | 180 | /// Duplicates the contents of `self` into a new instance. 181 | #[inline] 182 | pub fn duplicate(self) -> Self { 183 | unsafe { Self::from_raw(ruby::rb_hash_dup(self.raw())) } 184 | } 185 | 186 | /// Associates `val` with `key`. 187 | /// 188 | /// # Safety 189 | /// 190 | /// The caller must ensure that `self` is not frozen or else a `FrozenError` 191 | /// exception will be raised. 192 | /// 193 | /// # Examples 194 | /// 195 | /// Rust types can automagically be converted to keys and values: 196 | /// 197 | /// ``` 198 | /// # rosy::vm::init().unwrap(); 199 | /// # rosy::protected(|| { 200 | /// use rosy::prelude::*; 201 | /// 202 | /// let hash = Hash::::new(); 203 | /// unsafe { hash.insert("should_eat", true) }; 204 | /// 205 | /// assert_eq!(hash.to_s(), r#"{"should_eat"=>true}"#); 206 | /// # }).unwrap(); 207 | /// ``` 208 | #[inline] 209 | pub unsafe fn insert(self, key: impl Into, val: impl Into) { 210 | let key = key.into().raw(); 211 | let val = val.into().raw(); 212 | ruby::rb_hash_aset(self.raw(), key, val); 213 | } 214 | 215 | /// Inserts `pairs` into `self` in bulk. 216 | /// 217 | #[cfg_attr(not(nightly), doc = "**Requires feature:** `ruby_2_6`")] 218 | /// 219 | /// # Safety 220 | /// 221 | /// The caller must ensure that `self` is not frozen or else a `FrozenError` 222 | /// exception will be raised. 223 | #[cfg(feature = "ruby_2_6")] 224 | #[cfg_attr(nightly, doc(cfg(feature = "ruby_2_6")))] 225 | #[inline] 226 | pub unsafe fn insert_pairs(self, pairs: &[(K, V)]) { 227 | ruby::rb_hash_bulk_insert_into_st_table( 228 | (pairs.len() * 2) as _, 229 | pairs.as_ptr() as *const _, 230 | self.raw(), 231 | ); 232 | } 233 | 234 | /// Returns the value for `key`. 235 | #[inline] 236 | pub fn get(self, key: impl Into) -> Option { 237 | let key = key.into().raw(); 238 | unsafe { 239 | let val = AnyObject::from_raw(ruby::rb_hash_aref(self.raw(), key)); 240 | if val.is_nil() { 241 | None 242 | } else { 243 | Some(V::cast_unchecked(val)) 244 | } 245 | } 246 | } 247 | 248 | /// Returns the number of key-value pairs in `self`. 249 | #[inline] 250 | pub fn len(self) -> usize { 251 | unsafe { ruby::rb_hash_size_num(self.raw()) } 252 | } 253 | 254 | /// Returns whether `self` is empty. 255 | #[inline] 256 | pub fn is_empty(self) -> bool { 257 | self.len() == 0 258 | } 259 | 260 | /// Removes the value associated with `key` from `self` and returns it. 261 | /// 262 | /// # Safety 263 | /// 264 | /// The caller must ensure that `self` is not frozen or else a `FrozenError` 265 | /// exception will be raised. 266 | /// 267 | /// # Examples 268 | /// 269 | /// ``` 270 | /// # rosy::vm::init().unwrap(); 271 | /// # rosy::protected(|| { 272 | /// use rosy::prelude::*; 273 | /// 274 | /// let hash = Hash::::new(); 275 | /// 276 | /// unsafe { 277 | /// assert!(hash.remove("not_here").is_none()); 278 | /// hash.insert("is_here", true); 279 | /// assert_eq!(hash.remove("is_here").unwrap(), true); 280 | /// } 281 | /// # }).unwrap(); 282 | /// ``` 283 | #[inline] 284 | pub unsafe fn remove(self, key: impl Into) -> Option { 285 | let key = key.into().raw(); 286 | let val = AnyObject::from_raw(ruby::rb_hash_delete(self.raw(), key)); 287 | if val.is_nil() { 288 | None 289 | } else { 290 | Some(V::cast_unchecked(val)) 291 | } 292 | } 293 | 294 | /// Removes all elements from `self` in-place. 295 | /// 296 | /// # Safety 297 | /// 298 | /// The caller must ensure that `self` is not frozen or else a `FrozenError` 299 | /// exception will be raised. 300 | #[inline] 301 | pub unsafe fn clear(self) { 302 | ruby::rb_hash_clear(self.raw()); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/symbol.rs: -------------------------------------------------------------------------------- 1 | //! Ruby symbols. 2 | 3 | use std::{ 4 | convert::TryFrom, 5 | ffi::CStr, 6 | fmt, 7 | }; 8 | use crate::{ 9 | object::{NonNullObject, Ty}, 10 | prelude::*, 11 | string::Encoding, 12 | ruby, 13 | }; 14 | 15 | /// An instance of Ruby's `Symbol` class. 16 | #[derive(Clone, Copy, Debug)] 17 | #[repr(transparent)] 18 | pub struct Symbol(NonNullObject); 19 | 20 | impl AsRef for Symbol { 21 | #[inline] 22 | fn as_ref(&self) -> &AnyObject { self.0.as_ref() } 23 | } 24 | 25 | impl From for AnyObject { 26 | #[inline] 27 | fn from(object: Symbol) -> AnyObject { object.0.into() } 28 | } 29 | 30 | impl PartialEq for Symbol { 31 | #[inline] 32 | fn eq(&self, obj: &AnyObject) -> bool { 33 | self.as_any_object() == obj 34 | } 35 | } 36 | 37 | unsafe impl Object for Symbol { 38 | #[inline] 39 | fn cast(obj: A) -> Option { 40 | if obj.is_ty(Ty::SYMBOL) { 41 | unsafe { Some(Self::cast_unchecked(obj)) } 42 | } else { 43 | None 44 | } 45 | } 46 | 47 | #[inline] 48 | fn ty(self) -> Ty { Ty::SYMBOL } 49 | 50 | #[inline] 51 | fn is_ty(self, ty: Ty) -> bool { ty == Ty::SYMBOL } 52 | } 53 | 54 | impl fmt::Display for Symbol { 55 | #[inline] 56 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 57 | self.as_any_object().fmt(f) 58 | } 59 | } 60 | 61 | impl From for Symbol { 62 | #[inline] 63 | fn from(s: String) -> Symbol { 64 | SymbolId::from(s).into() 65 | } 66 | } 67 | 68 | impl From<&str> for Symbol { 69 | #[inline] 70 | fn from(s: &str) -> Symbol { 71 | SymbolId::from(s).into() 72 | } 73 | } 74 | 75 | impl TryFrom for &str { 76 | type Error = std::str::Utf8Error; 77 | 78 | #[inline] 79 | fn try_from(s: Symbol) -> Result { 80 | s.name().to_str() 81 | } 82 | } 83 | 84 | impl TryFrom for std::string::String { 85 | type Error = std::str::Utf8Error; 86 | 87 | #[inline] 88 | fn try_from(s: Symbol) -> Result { 89 | s.name().to_str().map(Into::into) 90 | } 91 | } 92 | 93 | impl Symbol { 94 | #[inline] 95 | pub(crate) fn _id(self) -> ruby::ID { 96 | unsafe { ruby::rb_sym2id(self.raw()) } 97 | } 98 | 99 | /// Returns an array of all the symbols currently in Ruby's symbol table. 100 | /// 101 | /// # Examples 102 | /// 103 | /// ``` 104 | /// # rosy::vm::init().unwrap(); 105 | /// assert!(rosy::Symbol::all().contains("eql?")); 106 | /// ``` 107 | #[inline] 108 | pub fn all() -> Array { 109 | unsafe { Array::from_raw(ruby::rb_sym_all_symbols()) } 110 | } 111 | 112 | /// Returns an array of the names of global variables. 113 | /// 114 | /// # Examples 115 | /// 116 | /// ``` 117 | /// # rosy::vm::init().unwrap(); 118 | /// assert!(rosy::Symbol::global_vars().contains("$SAFE")); 119 | /// ``` 120 | #[inline] 121 | pub fn global_vars() -> Array { 122 | unsafe { Array::from_raw(ruby::rb_f_global_variables()) } 123 | } 124 | 125 | /// Returns whether `name` is valid as a symbol value. 126 | /// 127 | /// # Examples 128 | /// 129 | /// ``` 130 | /// # rosy::vm::init().unwrap(); 131 | /// use rosy::Symbol; 132 | /// 133 | /// assert!(Symbol::is_valid("@hello")); 134 | /// 135 | /// assert!(!Symbol::is_valid("\0a")); 136 | /// assert!(!Symbol::is_valid("$")); 137 | /// assert!(!Symbol::is_valid("@")); 138 | /// assert!(!Symbol::is_valid("")); 139 | /// ``` 140 | #[inline] 141 | pub fn is_valid(name: impl AsRef<[u8]>) -> bool { 142 | let name = name.as_ref(); 143 | let ptr = name.as_ptr(); 144 | let len = name.len(); 145 | let enc = Encoding::utf8()._enc(); 146 | unsafe { ruby::rb_enc_symname2_p(ptr as _, len as _, enc) != 0 } 147 | } 148 | 149 | /// Returns the identifier associated with this symbol. 150 | #[inline] 151 | pub fn id(self) -> SymbolId { 152 | SymbolId(self._id()) 153 | } 154 | 155 | /// Returns the symbol's name as a nul-terminated C string. 156 | #[inline] 157 | pub fn name(self) -> &'static CStr { 158 | self.id().name() 159 | } 160 | 161 | /// Returns whether the symbol has its identifier encoded directly. 162 | #[inline] 163 | pub const fn is_static(self) -> bool { 164 | crate::util::value_is_static_sym(self.0.raw()) 165 | } 166 | 167 | /// Returns whether `self` is backed by an `RSymbol` runtime value. 168 | #[inline] 169 | pub const fn is_dynamic(self) -> bool { 170 | !self.is_static() 171 | } 172 | } 173 | 174 | /// An identifier for a [`Symbol`](struct.Symbol.html). 175 | /// 176 | /// # What is `Into`? 177 | /// 178 | /// This is a convenience trait implementation that allows for instantiating a 179 | /// `SymbolId` from a Rust 180 | /// [`&str`](https://doc.rust-lang.org/std/primitive.str.html) or 181 | /// Ruby [`String`](../struct.String.html). With it, one can simply pass a 182 | /// normal string literal into a function that takes some `impl Into`. 183 | /// For an example, look at [`Object::call`](../trait.Object.html#method.call). 184 | #[derive(Clone, Copy)] 185 | pub struct SymbolId(ruby::ID); 186 | 187 | impl fmt::Debug for SymbolId { 188 | #[inline] 189 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 190 | f.debug_tuple("SymbolId") 191 | .field(&self.raw()) 192 | .finish() 193 | } 194 | } 195 | 196 | impl From for SymbolId { 197 | #[inline] 198 | fn from(s: String) -> SymbolId { 199 | unsafe { SymbolId(ruby::rb_intern_str(s.raw())) } 200 | } 201 | } 202 | 203 | impl From<&str> for SymbolId { 204 | #[inline] 205 | fn from(s: &str) -> SymbolId { 206 | let raw = unsafe { ruby::rb_intern3( 207 | s.as_ptr() as _, 208 | s.len() as _, 209 | Encoding::utf8()._enc(), 210 | ) }; 211 | SymbolId(raw) 212 | } 213 | } 214 | 215 | impl From for SymbolId { 216 | #[inline] 217 | fn from(s: Symbol) -> Self { 218 | SymbolId(s._id()) 219 | } 220 | } 221 | 222 | impl From for Symbol { 223 | #[inline] 224 | fn from(id: SymbolId) -> Symbol { 225 | unsafe { Symbol::from_raw(ruby::rb_id2sym(id.raw())) } 226 | } 227 | } 228 | 229 | impl SymbolId { 230 | /// Creates a symbol ID from the raw value. 231 | /// 232 | /// # Safety 233 | /// 234 | /// The value must have come from the Ruby VM. 235 | #[inline] 236 | pub const unsafe fn from_raw(raw: ruby::ID) -> Self { 237 | SymbolId(raw) 238 | } 239 | 240 | /// Returns the raw underlying ID. 241 | #[inline] 242 | pub const fn raw(self) -> ruby::ID { 243 | self.0 244 | } 245 | 246 | /// Returns the symbol's name as a nul-terminated C string. 247 | #[inline] 248 | pub fn name(self) -> &'static CStr { 249 | unsafe { CStr::from_ptr(ruby::rb_id2name(self.raw())) } 250 | } 251 | } 252 | 253 | macro_rules! common_ids { 254 | ($($name:ident => $sym:expr,)+) => { 255 | struct Common { 256 | $($name: SymbolId,)+ 257 | } 258 | 259 | impl Common { 260 | // Initializes the `COMMON` table when first called and makes 261 | // subsequent calls simply access `COMMON` directly 262 | #[inline] 263 | fn get() -> &'static Common { 264 | static mut COMMON: Common = Common { 265 | $($name: SymbolId(0),)+ 266 | }; 267 | 268 | static mut GET_COMMON: fn() -> &'static Common = || unsafe { 269 | $(COMMON.$name = SymbolId::from($sym);)+ 270 | 271 | GET_COMMON = || &COMMON; 272 | 273 | &COMMON 274 | }; 275 | unsafe { GET_COMMON() } 276 | } 277 | } 278 | 279 | /// Commonly used symbol IDs. 280 | /// 281 | /// These functions are generally faster than using `SymbolId::from`. 282 | impl SymbolId { 283 | $( 284 | /// ID for the 285 | /// ` 286 | #[doc = $sym] 287 | /// ` symbol. 288 | #[inline] 289 | pub fn $name() -> SymbolId { 290 | Common::get().$name 291 | } 292 | )+ 293 | } 294 | }; 295 | } 296 | 297 | common_ids! { 298 | equal_op => "==", 299 | backtrace => "backtrace", 300 | cause => "cause", 301 | size => "size", 302 | eval => "eval", 303 | to_binary => "to_binary", 304 | load_from_binary => "load_from_binary", 305 | disasm => "disasm", 306 | path => "path", 307 | absolute_path => "absolute_path", 308 | include_q => "include?", 309 | compile => "compile", 310 | compile_file => "compile_file", 311 | } 312 | 313 | #[cfg(all(test, nightly))] 314 | mod benches { 315 | use test::{Bencher, black_box}; 316 | use super::*; 317 | 318 | #[bench] 319 | fn get_equal_op_sym(b: &mut Bencher) { 320 | crate::vm::init().unwrap(); 321 | 322 | b.iter(|| { 323 | black_box(black_box(SymbolId::equal_op)()); 324 | }); 325 | } 326 | 327 | #[bench] 328 | fn intern_equal_op_sym(b: &mut Bencher) { 329 | crate::vm::init().unwrap(); 330 | 331 | b.iter(|| { 332 | black_box(SymbolId::from(black_box("=="))); 333 | }); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /src/string/encoding.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | convert::{TryFrom, TryInto}, 3 | error::Error, 4 | ffi::{CStr, CString, FromBytesWithNulError}, 5 | fmt, 6 | os::raw::c_int, 7 | }; 8 | use crate::{ 9 | object::NonNullObject, 10 | prelude::*, 11 | ruby, 12 | }; 13 | 14 | /// An encoding for `String`. 15 | #[derive(Clone, Copy, Debug)] 16 | #[repr(transparent)] 17 | pub struct Encoding(NonNullObject); 18 | 19 | impl AsRef for Encoding { 20 | #[inline] 21 | fn as_ref(&self) -> &AnyObject { self.0.as_ref() } 22 | } 23 | 24 | impl From for AnyObject { 25 | #[inline] 26 | fn from(object: Encoding) -> AnyObject { object.0.into() } 27 | } 28 | 29 | impl PartialEq for Encoding { 30 | #[inline] 31 | fn eq(&self, obj: &AnyObject) -> bool { 32 | self.as_any_object() == obj 33 | } 34 | } 35 | 36 | unsafe impl Object for Encoding { 37 | #[inline] 38 | fn cast(obj: A) -> Option { 39 | if obj.class().inherits(Class::of::()) { 40 | unsafe { Some(Self::cast_unchecked(obj)) } 41 | } else { 42 | None 43 | } 44 | } 45 | } 46 | 47 | impl fmt::Display for Encoding { 48 | #[inline] 49 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 50 | self.as_any_object().fmt(f) 51 | } 52 | } 53 | 54 | impl PartialEq for Encoding { 55 | #[inline] 56 | fn eq(&self, other: &Self) -> bool { 57 | self._enc() == other._enc() 58 | } 59 | } 60 | 61 | impl Eq for Encoding {} 62 | 63 | impl TryFrom<&CStr> for Encoding { 64 | type Error = EncodingLookupError; 65 | 66 | #[inline] 67 | fn try_from(s: &CStr) -> Result { 68 | let index = unsafe { ruby::rb_enc_find_index(s.as_ptr()) }; 69 | if index < 0 { 70 | Err(EncodingLookupError::UnknownName) 71 | } else { 72 | Ok(Encoding::_from_index(index)) 73 | } 74 | } 75 | } 76 | 77 | impl TryFrom<&CString> for Encoding { 78 | type Error = EncodingLookupError; 79 | 80 | #[inline] 81 | fn try_from(s: &CString) -> Result { 82 | s.as_c_str().try_into() 83 | } 84 | } 85 | 86 | impl TryFrom<&[u8]> for Encoding { 87 | type Error = EncodingLookupError; 88 | 89 | #[inline] 90 | fn try_from(bytes: &[u8]) -> Result { 91 | CStr::from_bytes_with_nul(bytes)?.try_into() 92 | } 93 | } 94 | 95 | impl TryFrom<&Vec> for Encoding { 96 | type Error = EncodingLookupError; 97 | 98 | #[inline] 99 | fn try_from(bytes: &Vec) -> Result { 100 | bytes.as_slice().try_into() 101 | } 102 | } 103 | 104 | impl TryFrom<&str> for Encoding { 105 | type Error = EncodingLookupError; 106 | 107 | #[inline] 108 | fn try_from(s: &str) -> Result { 109 | s.as_bytes().try_into() 110 | } 111 | } 112 | 113 | impl TryFrom<&std::string::String> for Encoding { 114 | type Error = EncodingLookupError; 115 | 116 | #[inline] 117 | fn try_from(s: &std::string::String) -> Result { 118 | s.as_str().try_into() 119 | } 120 | } 121 | 122 | impl Encoding { 123 | #[inline] 124 | pub(crate) fn _from_enc(enc: *mut ruby::rb_encoding) -> Self { 125 | unsafe { Self::from_raw(ruby::rb_enc_from_encoding(enc)) } 126 | } 127 | 128 | #[inline] 129 | pub(crate) fn _from_index(i: c_int) -> Self { 130 | unsafe { Self::_from_enc(ruby::rb_enc_from_index(i)) } 131 | } 132 | 133 | #[inline] 134 | pub(crate) fn _rdata(self) -> *mut ruby::RData { 135 | self.as_any_object()._ptr() as _ 136 | } 137 | 138 | #[inline] 139 | pub(crate) fn _enc(self) -> *mut ruby::rb_encoding { 140 | unsafe { 141 | let enc = (*self._rdata()).data as *mut ruby::rb_encoding; 142 | debug_assert_eq!(enc, ruby::rb_to_encoding(self.raw())); 143 | enc 144 | } 145 | } 146 | 147 | #[inline] 148 | pub(crate) fn _index(self) -> c_int { 149 | unsafe { (*self._enc()).index() } 150 | } 151 | 152 | /// Returns the `ASCII-8BIT` encoding. 153 | /// 154 | /// # Examples 155 | /// 156 | /// This is essentially an "anything goes" encoding: 157 | /// 158 | /// ``` 159 | /// use rosy::string::{String, Encoding}; 160 | /// 161 | /// # rosy::vm::init().unwrap(); 162 | /// let bytes: &[u8] = &[b'a', b'z', 0, 255]; 163 | /// let string = String::from(bytes); 164 | /// 165 | /// assert_eq!(string.encoding(), Encoding::ascii_8bit()); 166 | /// ``` 167 | #[inline] 168 | pub fn ascii_8bit() -> Encoding { 169 | unsafe { Encoding::_from_enc(ruby::rb_ascii8bit_encoding()) } 170 | } 171 | 172 | /// Returns the `UTF-8` encoding. 173 | /// 174 | /// # Examples 175 | /// 176 | /// ``` 177 | /// # rosy::vm::init().unwrap(); 178 | /// use rosy::string::Encoding; 179 | /// 180 | /// let utf8 = Encoding::find("UTF-8\0").unwrap(); 181 | /// assert_eq!(utf8, Encoding::utf8()); 182 | /// ``` 183 | #[inline] 184 | pub fn utf8() -> Encoding { 185 | unsafe { Encoding::_from_enc(ruby::rb_utf8_encoding()) } 186 | } 187 | 188 | /// Returns the `US-ASCII` encoding. 189 | /// 190 | /// # Examples 191 | /// 192 | /// ``` 193 | /// # rosy::vm::init().unwrap(); 194 | /// use rosy::string::Encoding; 195 | /// 196 | /// let ascii = Encoding::find("US-ASCII\0").unwrap(); 197 | /// assert_eq!(ascii, Encoding::us_ascii()); 198 | /// ``` 199 | #[inline] 200 | pub fn us_ascii() -> Encoding { 201 | unsafe { Encoding::_from_enc(ruby::rb_usascii_encoding()) } 202 | } 203 | 204 | /// Attempts to find `encoding`, returning an error if either: 205 | /// - `encoding` cannot be passed in as a nul-terminated C string. 206 | /// - The requested encoding was not found. 207 | /// 208 | /// # Examples 209 | /// 210 | /// Looking up an encoding is straightforward since Rust allows for 211 | /// embedding nul bytes in its UTF-8 strings: 212 | /// 213 | /// ``` 214 | /// # rosy::vm::init().unwrap(); 215 | /// use rosy::string::Encoding; 216 | /// 217 | /// let utf8 = Encoding::find("UTF-8\0").unwrap(); 218 | /// let ascii = Encoding::find("US-ASCII\0").unwrap(); 219 | /// 220 | /// assert_ne!(utf8, ascii); 221 | /// ``` 222 | #[inline] 223 | pub fn find(encoding: E) -> Result 224 | where E: TryInto 225 | { 226 | encoding.try_into() 227 | } 228 | 229 | /// Returns the encoding's name. 230 | /// 231 | /// # Examples 232 | /// 233 | /// ``` 234 | /// # rosy::vm::init().unwrap(); 235 | /// use rosy::string::Encoding; 236 | /// 237 | /// assert_eq!(Encoding::utf8().name().to_bytes(), b"UTF-8"); 238 | /// ``` 239 | #[inline] 240 | pub fn name(&self) -> &CStr { 241 | unsafe { CStr::from_ptr((*self._enc()).name) } 242 | } 243 | 244 | /// Returns whether `self` is `ASCII-8BIT`. 245 | #[inline] 246 | pub fn is_ascii_8bit(self) -> bool { 247 | self._index() == ruby::rb_encoding::ascii_8bit_index() 248 | } 249 | 250 | /// Returns whether `self` is `UTF-8`. 251 | #[inline] 252 | pub fn is_utf8(self) -> bool { 253 | self._index() == ruby::rb_encoding::utf8_index() 254 | } 255 | 256 | /// Returns whether `self` is `US-ASCII`. 257 | #[inline] 258 | pub fn is_us_ascii(self) -> bool { 259 | self._index() == ruby::rb_encoding::us_ascii_index() 260 | } 261 | 262 | /// Returns whether `self` is the locale encoding. 263 | #[inline] 264 | pub fn is_locale(self) -> bool { 265 | unsafe { self._index() == ruby::rb_locale_encindex() } 266 | } 267 | 268 | /// Returns whether `self` is the filesystem encoding. 269 | #[inline] 270 | pub fn is_filesystem(self) -> bool { 271 | unsafe { self._index() == ruby::rb_filesystem_encindex() } 272 | } 273 | 274 | /// Returns whether `self` is the default external encoding. 275 | #[inline] 276 | pub fn is_default_external(self) -> bool { 277 | unsafe { self._enc() == ruby::rb_default_external_encoding() } 278 | } 279 | 280 | /// Returns whether `self` is the default internal encoding. 281 | #[inline] 282 | pub fn is_default_internal(self) -> bool { 283 | unsafe { self._enc() == ruby::rb_default_internal_encoding() } 284 | } 285 | } 286 | 287 | /// The error returned when [`Encoding::find`](struct.Encoding.html#method.find) 288 | /// fails. 289 | #[derive(Debug)] 290 | pub enum EncodingLookupError { 291 | /// The encoding name could not be found. 292 | UnknownName, 293 | /// The encoding name string was not C-compatible. 294 | InvalidCStr(FromBytesWithNulError), 295 | } 296 | 297 | impl Error for EncodingLookupError { 298 | #[inline] 299 | fn description(&self) -> &str { 300 | use EncodingLookupError::*; 301 | match self { 302 | UnknownName => "Unknown encoding name", 303 | InvalidCStr(error) => error.description(), 304 | } 305 | } 306 | } 307 | 308 | impl fmt::Display for EncodingLookupError { 309 | #[inline] 310 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 311 | use EncodingLookupError::*; 312 | match self { 313 | UnknownName => self.description().fmt(f), 314 | InvalidCStr(error) => error.fmt(f), 315 | } 316 | } 317 | } 318 | 319 | impl From for EncodingLookupError { 320 | #[inline] 321 | fn from(error: FromBytesWithNulError) -> Self { 322 | EncodingLookupError::InvalidCStr(error) 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/vm/mod.rs: -------------------------------------------------------------------------------- 1 | //! Interacting with the Ruby VM directly. 2 | 3 | use std::{ 4 | error::Error, 5 | fmt, 6 | num::NonZeroI32, 7 | os::raw::c_int, 8 | }; 9 | use crate::{ 10 | prelude::*, 11 | ruby, 12 | }; 13 | 14 | mod eval; 15 | mod instr_seq; 16 | 17 | pub use self::{ 18 | eval::*, 19 | instr_seq::*, 20 | }; 21 | 22 | /// Initializes the Ruby VM, returning an error code if it failed. 23 | #[inline] 24 | pub fn init() -> Result<(), InitError> { 25 | if let Some(code) = NonZeroI32::new(unsafe { ruby::ruby_setup() as i32 }) { 26 | Err(InitError(code)) 27 | } else { 28 | Ok(()) 29 | } 30 | } 31 | 32 | /// Destructs the Ruby VM, runs its finalization processes, and frees all 33 | /// resources used by it. 34 | /// 35 | /// Returns an exit code on error appropriate for passing into 36 | /// [`std::process::exit`](https://doc.rust-lang.org/std/process/fn.exit.html). 37 | /// 38 | /// # Safety 39 | /// 40 | /// The caller must ensure that no VM resources are being used by other threads 41 | /// or will continue to be used after this function finishes. 42 | /// 43 | /// After this function is called, it will no longer be possible to call 44 | /// [`init`](fn.init.html). 45 | #[inline] 46 | pub unsafe fn destroy() -> Result<(), DestroyError> { 47 | if let Some(code) = NonZeroI32::new(ruby::ruby_cleanup(0) as i32) { 48 | Err(DestroyError(code)) 49 | } else { 50 | Ok(()) 51 | } 52 | } 53 | 54 | /// Returns Ruby's level of paranoia. This is equivalent to reading `$SAFE`. 55 | #[inline] 56 | pub fn safe_level() -> c_int { 57 | unsafe { ruby::rb_safe_level() } 58 | } 59 | 60 | /// Sets Ruby's level of paranoia. The default value is 0. 61 | /// 62 | /// # Safety 63 | /// 64 | /// An exception will be raised if `level` is either negative or not supported. 65 | #[inline] 66 | pub unsafe fn set_safe_level(level: c_int) { 67 | ruby::rb_set_safe_level(level); 68 | } 69 | 70 | /// Initializes the load path for `require`-ing gems. 71 | /// 72 | /// # Examples 73 | /// 74 | /// ``` 75 | /// rosy::vm::init().unwrap(); 76 | /// rosy::vm::init_load_path(); 77 | /// ``` 78 | #[inline] 79 | pub fn init_load_path() { 80 | unsafe { ruby::ruby_init_loadpath() }; 81 | } 82 | 83 | /// Loads `file` with the current `safe_level`, without checking for exceptions. 84 | /// 85 | /// This returns `true` if successful or `false` if already loaded. 86 | /// 87 | /// See [`require_with`](fn.require_with.html) for more info. 88 | /// 89 | /// # Safety 90 | /// 91 | /// Code executed from `file` may void the type safety of objects accessible 92 | /// from Rust. For example, if one calls `push` on `Array` with an object of 93 | /// type `B`, then the inserted object will be treated as being of type `A`. 94 | /// 95 | /// An exception may be raised by the code in `file` or by `file` being invalid. 96 | #[inline] 97 | pub unsafe fn require(file: impl Into) -> bool { 98 | require_with(file, safe_level()) 99 | } 100 | 101 | /// Loads `file` with `safe_level`, without checking for exceptions. 102 | /// 103 | /// This returns `true` if successful or `false` if already loaded. 104 | /// 105 | // Taken from docs on `rb_f_require` in Ruby's source code 106 | /// If the filename does not resolve to an absolute path, it will be searched 107 | /// for in the directories listed in`$LOAD_PATH` (`$:`). 108 | /// 109 | /// If the filename has the extension `.rb`, it is loaded as a source file; if 110 | /// the extension is `.so`, `.o`, or `.dll`, or the default shared library 111 | /// extension on the current platform, Ruby loads the shared library as a Ruby 112 | /// extension. Otherwise, Ruby tries adding `.rb`, `.so`, and so on to the name 113 | /// until found. If the file named cannot be found, a `LoadError` will be 114 | /// returned. 115 | /// 116 | /// For Ruby extensions the filename given may use any shared library extension. 117 | /// For example, on Linux the socket extension is `socket.so` and `require 118 | /// 'socket.dll'` will load the socket extension. 119 | /// 120 | /// The absolute path of the loaded file is added to `$LOADED_FEATURES` (`$"`). 121 | /// A file will not be loaded again if its path already appears in `$"`. For 122 | /// example, `require 'a'; require './a'` will not load `a.rb` again. 123 | /// 124 | /// ```ruby 125 | /// require "my-library.rb" 126 | /// require "db-driver" 127 | /// ``` 128 | /// 129 | /// Any constants or globals within the loaded source file will be available in 130 | /// the calling program's global namespace. However, local variables will not be 131 | /// propagated to the loading environment. 132 | /// 133 | /// # Safety 134 | /// 135 | /// Code executed from `file` may void the type safety of objects accessible 136 | /// from Rust. For example, if one calls `push` on `Array` with an object of 137 | /// type `B`, then the inserted object will be treated as being of type `A`. 138 | /// 139 | /// An exception may be raised by the code in `file` or by `file` being invalid. 140 | #[inline] 141 | pub unsafe fn require_with( 142 | file: impl Into, 143 | safe_level: c_int, 144 | ) -> bool { 145 | ruby::rb_require_safe(file.into().raw(), safe_level) != 0 146 | } 147 | 148 | /// Loads `file` with the current `safe_level`. 149 | /// 150 | /// This returns `true` if successful or `false` if already loaded. 151 | /// 152 | /// See [`require_with`](fn.require_with.html) for more info. 153 | /// 154 | /// # Safety 155 | /// 156 | /// Code executed from `file` may void the type safety of objects accessible 157 | /// from Rust. For example, if one calls `push` on `Array` with an object of 158 | /// type `B`, then the inserted object will be treated as being of type `A`. 159 | #[inline] 160 | pub unsafe fn require_protected(file: impl Into) -> Result { 161 | require_with_protected(file, safe_level()) 162 | } 163 | 164 | /// Loads `file` with `safe_level`. 165 | /// 166 | /// This returns `true` if successful or `false` if already loaded. 167 | /// 168 | /// See [`require_with`](fn.require_with.html) for more info. 169 | /// 170 | /// # Safety 171 | /// 172 | /// Code executed from `file` may void the type safety of objects accessible 173 | /// from Rust. For example, if one calls `push` on `Array` with an object of 174 | /// type `B`, then the inserted object will be treated as being of type `A`. 175 | #[inline] 176 | pub unsafe fn require_with_protected( 177 | file: impl Into, 178 | safe_level: c_int, 179 | ) -> Result { 180 | // monomorphization 181 | unsafe fn require(file: String, safe: c_int) -> Result { 182 | crate::protected_no_panic(|| ruby::rb_require_safe(file.raw(), safe)) 183 | } 184 | // Convert to `bool` here for inlining 185 | Ok(require(file.into(), safe_level)? != 0) 186 | } 187 | 188 | /// Loads and executes the Ruby program `file`, without checking for exceptions. 189 | /// 190 | /// If the filename does not resolve to an absolute path, the file is searched 191 | /// for in the library directories listed in `$:`. 192 | /// 193 | /// If `wrap` is `true`, the loaded script will be executed under an anonymous 194 | /// module, protecting the calling program's global namespace. In no 195 | /// circumstance will any local variables in the loaded file be propagated to 196 | /// the loading environment. 197 | /// 198 | /// # Safety 199 | /// 200 | /// Code executed from `file` may void the type safety of objects accessible 201 | /// from Rust. For example, if one calls `push` on `Array` with an object of 202 | /// type `B`, then the inserted object will be treated as being of type `A`. 203 | /// 204 | /// An exception may be raised by the code in `file` or by `file` being invalid. 205 | #[inline] 206 | pub unsafe fn load(file: impl Into, wrap: bool) { 207 | ruby::rb_load(file.into().raw(), wrap as c_int) 208 | } 209 | 210 | /// Loads and executes the Ruby program `file`. 211 | /// 212 | /// See [`load`](fn.load.html) for more info. 213 | /// 214 | /// # Safety 215 | /// 216 | /// Code executed from `file` may void the type safety of objects accessible 217 | /// from Rust. For example, if one calls `push` on `Array` with an object of 218 | /// type `B`, then the inserted object will be treated as being of type `A`. 219 | #[inline] 220 | pub unsafe fn load_protected(file: impl Into, wrap: bool) -> Result { 221 | let mut err = 0; 222 | ruby::rb_load_protect(file.into().raw(), wrap as c_int, &mut err); 223 | match err { 224 | 0 => Ok(()), 225 | _ => Err(AnyException::_take_current()), 226 | } 227 | } 228 | 229 | /// Returns the current backtrace. 230 | #[inline] 231 | pub fn backtrace() -> Array { 232 | unsafe { Array::from_raw(ruby::rb_make_backtrace()) } 233 | } 234 | 235 | /// An error indicating that [`init`](fn.init.html) failed. 236 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 237 | pub struct InitError(NonZeroI32); 238 | 239 | impl InitError { 240 | /// Returns the error code given by the VM. 241 | #[inline] 242 | pub fn code(self) -> i32 { 243 | self.0.get() 244 | } 245 | } 246 | 247 | impl fmt::Display for InitError { 248 | #[inline] 249 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 250 | write!(f, "{} (error code {})", self.description(), self.code()) 251 | } 252 | } 253 | 254 | impl Error for InitError { 255 | #[inline] 256 | fn description(&self) -> &str { 257 | "Failed to initialize Ruby" 258 | } 259 | } 260 | 261 | /// An error indicating that [`destroy`](fn.destroy.html) failed. 262 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 263 | pub struct DestroyError(NonZeroI32); 264 | 265 | impl DestroyError { 266 | /// Returns the error code given by the VM. 267 | #[inline] 268 | pub fn code(self) -> i32 { 269 | self.0.get() 270 | } 271 | 272 | /// Exits the process with the returned error code. 273 | #[inline] 274 | pub fn exit_process(self) -> ! { 275 | std::process::exit(self.code()) 276 | } 277 | } 278 | 279 | impl fmt::Display for DestroyError { 280 | #[inline] 281 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 282 | write!(f, "{} (error code {})", self.description(), self.code()) 283 | } 284 | } 285 | 286 | impl Error for DestroyError { 287 | #[inline] 288 | fn description(&self) -> &str { 289 | "Failed to destroy Ruby" 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/vm/eval.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use crate::{ 3 | prelude::*, 4 | ruby::{self, VALUE}, 5 | }; 6 | 7 | /// Evaluates `script` in an isolated binding without handling exceptions. 8 | /// 9 | /// Variables: 10 | /// - `__FILE__`: "(eval)" 11 | /// - `__LINE__`: starts at 1 12 | /// 13 | /// # Safety 14 | /// 15 | /// Code executed from `script` may void the type safety of objects accessible 16 | /// from Rust. For example, if one calls `push` on `Array` with an object of 17 | /// type `B`, then the inserted object will be treated as being of type `A`. 18 | /// 19 | /// ``` 20 | /// # rosy::vm::init().unwrap(); 21 | /// use std::ffi::CStr; 22 | /// use rosy::prelude::*; 23 | /// 24 | /// let array: Array = (1..=3).collect(); // [1, 2, 3] 25 | /// 26 | /// let module = Module::def("Evil").unwrap(); 27 | /// unsafe { module.set_const("ARR", array) }; 28 | /// 29 | /// let script = b"Evil::ARR.push('hi')\0"; 30 | /// let script = CStr::from_bytes_with_nul(script).unwrap(); 31 | /// 32 | /// unsafe { rosy::vm::eval(script) }; 33 | /// let hi = array.get(3).unwrap(); 34 | /// ``` 35 | /// 36 | /// If we try using `hi` as an `Integer` here, we will get a segmentation fault: 37 | /// 38 | // Supports all failures, apparently 39 | /// ```should_panic 40 | // This is just for demonstration purposes 41 | /// # rosy::vm::init().unwrap(); 42 | /// # use rosy::prelude::*; 43 | /// # let hi = unsafe { Integer::cast_unchecked(String::from("hi")) }; 44 | /// let val = hi.to_truncated::(); 45 | /// ``` 46 | /// 47 | /// However, we can see that `hi` is actually a `String` despite being typed as 48 | /// an `Integer`: 49 | /// 50 | /// ``` 51 | // This is just for demonstration purposes 52 | /// # rosy::vm::init().unwrap(); 53 | /// # use rosy::prelude::*; 54 | /// # let hi = AnyObject::from("hi"); 55 | /// let hi = unsafe { String::cast_unchecked(hi) }; 56 | /// assert_eq!(hi, "hi"); 57 | /// ``` 58 | /// 59 | /// ...also, any exceptions raised in `script` must be handled in Rust-land. 60 | #[inline] 61 | pub unsafe fn eval(script: &CStr) -> AnyObject { 62 | AnyObject::from_raw(ruby::rb_eval_string(script.as_ptr())) 63 | } 64 | 65 | /// Evaluates `script` in an isolated binding, returning an exception if one is 66 | /// raised. 67 | /// 68 | /// Variables: 69 | /// - `__FILE__`: "(eval)" 70 | /// - `__LINE__`: starts at 1 71 | /// 72 | /// # Safety 73 | /// 74 | /// Code executed from `script` may void the type safety of objects accessible 75 | /// from Rust. For example, if one calls `push` on `Array` with an object of 76 | /// type `B`, then the inserted object will be treated as being of type `A`. 77 | #[inline] 78 | pub unsafe fn eval_protected(script: &CStr) -> Result { 79 | let mut err = 0; 80 | let raw = ruby::rb_eval_string_protect(script.as_ptr(), &mut err); 81 | match err { 82 | 0 => Ok(AnyObject::from_raw(raw)), 83 | _ => Err(AnyException::_take_current()), 84 | } 85 | } 86 | 87 | /// Evaluates `script` under a module binding in an isolated binding, returning 88 | /// an exception if one is raised. 89 | /// 90 | /// Variables: 91 | /// - `__FILE__`: "(eval)" 92 | /// - `__LINE__`: starts at 1 93 | /// 94 | /// # Safety 95 | /// 96 | /// Code executed from `script` may void the type safety of objects accessible 97 | /// from Rust. For example, if one calls `push` on `Array` with an object of 98 | /// type `B`, then the inserted object will be treated as being of type `A`. 99 | #[inline] 100 | pub unsafe fn eval_wrapped(script: &CStr) -> Result { 101 | let mut err = 0; 102 | let raw = ruby::rb_eval_string_wrap(script.as_ptr(), &mut err); 103 | match err { 104 | 0 => Ok(AnyObject::from_raw(raw)), 105 | _ => Err(AnyException::_take_current()), 106 | } 107 | } 108 | 109 | /// A type that can be used as one or more arguments for evaluating code within 110 | /// the context of a [`Mixin`](trait.Mixin.html). 111 | /// 112 | /// The difference between `eval_in_object` and `eval_in_mixin` 113 | /// 114 | /// See the documentation of [its implementors](#foreign-impls) for much more 115 | /// detailed information. 116 | /// 117 | /// # Safety 118 | /// 119 | /// Code executed from `self` may void the type safety of objects accessible 120 | /// from Rust. For example, if one calls `push` on an `Array` with an 121 | /// object of type `B`, then the inserted object will be treated as being of 122 | /// type `A`. 123 | /// 124 | /// For non-`protected` variants, if an exception is raised due to an argument 125 | /// error or from evaluating the script itself, it should be caught. 126 | pub trait EvalArgs: Sized { 127 | /// Evaluates `self` in the context of `object`. 128 | /// 129 | /// This corresponds directly to `rb_obj_instance_eval`. 130 | /// 131 | /// In order to set the context, the variable `self` is set to `object` 132 | /// while the code is executing, giving the code access to `object`'s 133 | /// instance variables and private methods. 134 | unsafe fn eval_in_object(self, object: impl Into) -> AnyObject; 135 | 136 | /// Evaluates `self` in the context of `object`, returning any raised 137 | /// exceptions. 138 | unsafe fn eval_in_object_protected(self, object: impl Into) -> Result; 139 | 140 | /// Evaluates `self` in the context of `mixin`. 141 | /// 142 | /// This corresponds directly to `rb_mod_module_eval`. 143 | unsafe fn eval_in_mixin(self, mixin: impl Mixin) -> AnyObject; 144 | 145 | /// Evaluates `self` in the context of `mixin`, returning any raised 146 | /// exceptions. 147 | unsafe fn eval_in_mixin_protected(self, mixin: impl Mixin) -> Result; 148 | } 149 | 150 | /// Unchecked arguments directly to the evaluation function. 151 | impl EvalArgs for &[O] { 152 | #[inline] 153 | unsafe fn eval_in_object_protected(self, object: impl Into) -> Result { 154 | // monomorphization 155 | unsafe fn eval(args: &[AnyObject], object: AnyObject) -> Result { 156 | crate::protected_no_panic(|| args.eval_in_object(object)) 157 | } 158 | eval(AnyObject::convert_slice(self), object.into()) 159 | } 160 | 161 | #[inline] 162 | unsafe fn eval_in_object(self, object: impl Into) -> AnyObject { 163 | let raw = ruby::rb_obj_instance_eval( 164 | self.len() as _, 165 | self.as_ptr() as *const VALUE, 166 | object.into().raw(), 167 | ); 168 | AnyObject::from_raw(raw) 169 | } 170 | 171 | #[inline] 172 | unsafe fn eval_in_mixin_protected(self, mixin: impl Mixin) -> Result { 173 | // monomorphization 174 | unsafe fn eval(args: &[AnyObject], mixin: VALUE) -> Result { 175 | let raw = crate::protected_no_panic(|| ruby::rb_mod_module_eval( 176 | args.len() as _, 177 | args.as_ptr() as *const VALUE, 178 | mixin, 179 | ))?; 180 | Ok(AnyObject::from_raw(raw)) 181 | } 182 | eval(AnyObject::convert_slice(self), mixin.raw()) 183 | } 184 | 185 | #[inline] 186 | unsafe fn eval_in_mixin(self, mixin: impl Mixin) -> AnyObject { 187 | let raw = ruby::rb_mod_module_eval( 188 | self.len() as _, 189 | self.as_ptr() as *const VALUE, 190 | mixin.raw(), 191 | ); 192 | AnyObject::from_raw(raw) 193 | } 194 | } 195 | 196 | /// The script argument without any extra information. 197 | impl EvalArgs for String { 198 | #[inline] 199 | unsafe fn eval_in_object_protected(self, object: impl Into) -> Result { 200 | self.as_any_slice().eval_in_object_protected(object) 201 | } 202 | 203 | #[inline] 204 | unsafe fn eval_in_object(self, object: impl Into) -> AnyObject { 205 | self.as_any_slice().eval_in_object(object) 206 | } 207 | 208 | #[inline] 209 | unsafe fn eval_in_mixin_protected(self, mixin: impl Mixin) -> Result { 210 | self.as_any_slice().eval_in_mixin_protected(mixin) 211 | } 212 | 213 | #[inline] 214 | unsafe fn eval_in_mixin(self, mixin: impl Mixin) -> AnyObject { 215 | self.as_any_slice().eval_in_mixin(mixin) 216 | } 217 | } 218 | 219 | /// The script argument as a UTF-8 string, without any extra information. 220 | // TODO: Impl for `Into` when specialization stabilizes 221 | impl EvalArgs for &str { 222 | #[inline] 223 | unsafe fn eval_in_object_protected(self, object: impl Into) -> Result { 224 | String::from(self).eval_in_object_protected(object) 225 | } 226 | 227 | #[inline] 228 | unsafe fn eval_in_object(self, object: impl Into) -> AnyObject { 229 | String::from(self).eval_in_object(object) 230 | } 231 | 232 | #[inline] 233 | unsafe fn eval_in_mixin_protected(self, mixin: impl Mixin) -> Result { 234 | String::from(self).eval_in_mixin_protected(mixin) 235 | } 236 | 237 | #[inline] 238 | unsafe fn eval_in_mixin(self, mixin: impl Mixin) -> AnyObject { 239 | String::from(self).eval_in_mixin(mixin) 240 | } 241 | } 242 | 243 | /// The script and filename arguments. 244 | impl, F: Into> EvalArgs for (S, F) { 245 | #[inline] 246 | unsafe fn eval_in_object_protected(self, object: impl Into) -> Result { 247 | let (s, f) = self; 248 | [s.into(), f.into()].eval_in_object_protected(object) 249 | } 250 | 251 | #[inline] 252 | unsafe fn eval_in_object(self, object: impl Into) -> AnyObject { 253 | let (s, f) = self; 254 | [s.into(), f.into()].eval_in_object(object) 255 | } 256 | 257 | #[inline] 258 | unsafe fn eval_in_mixin_protected(self, mixin: impl Mixin) -> Result { 259 | let (s, f) = self; 260 | [s.into(), f.into()].eval_in_mixin_protected(mixin) 261 | } 262 | 263 | #[inline] 264 | unsafe fn eval_in_mixin(self, mixin: impl Mixin) -> AnyObject { 265 | let (s, f) = self; 266 | [s.into(), f.into()].eval_in_mixin(mixin) 267 | } 268 | } 269 | 270 | /// The script, filename, and line number arguments. 271 | impl EvalArgs for (S, F, L) 272 | where 273 | S: Into, 274 | F: Into, 275 | L: Into, 276 | { 277 | #[inline] 278 | unsafe fn eval_in_object_protected(self, object: impl Into) -> Result { 279 | let (s, f, l) = self; 280 | let s = AnyObject::from(s.into()); 281 | let f = AnyObject::from(f.into()); 282 | let l = AnyObject::from(l.into()); 283 | [s, f, l].eval_in_object_protected(object) 284 | } 285 | 286 | #[inline] 287 | unsafe fn eval_in_object(self, object: impl Into) -> AnyObject { 288 | let (s, f, l) = self; 289 | let s = AnyObject::from(s.into()); 290 | let f = AnyObject::from(f.into()); 291 | let l = AnyObject::from(l.into()); 292 | [s, f, l].eval_in_object(object) 293 | } 294 | 295 | #[inline] 296 | unsafe fn eval_in_mixin_protected(self, mixin: impl Mixin) -> Result { 297 | let (s, f, l) = self; 298 | let s = AnyObject::from(s.into()); 299 | let f = AnyObject::from(f.into()); 300 | let l = AnyObject::from(l.into()); 301 | [s, f, l].eval_in_mixin_protected(mixin) 302 | } 303 | 304 | #[inline] 305 | unsafe fn eval_in_mixin(self, mixin: impl Mixin) -> AnyObject { 306 | let (s, f, l) = self; 307 | let s = AnyObject::from(s.into()); 308 | let f = AnyObject::from(f.into()); 309 | let l = AnyObject::from(l.into()); 310 | [s, f, l].eval_in_mixin(mixin) 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | **Copyright (c) 2019 Nikolai Vazquez** 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | ---- 24 | 25 | # Apache License 26 | 27 | Version 2.0, January 2004 28 | 29 | http://www.apache.org/licenses/ 30 | 31 | ## Terms and Conditions for use, reproduction, and distribution 32 | 33 | ### 1. Definitions 34 | 35 | "License" shall mean the terms and conditions for use, reproduction, and 36 | distribution as defined by Sections 1 through 9 of this document. 37 | 38 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 39 | owner that is granting the License. 40 | 41 | "Legal Entity" shall mean the union of the acting entity and all other entities 42 | that control, are controlled by, or are under common control with that entity. 43 | For the purposes of this definition, "control" means (i) the power, direct or 44 | indirect, to cause the direction or management of such entity, whether by 45 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 46 | outstanding shares, or (iii) beneficial ownership of such entity. 47 | 48 | "You" (or "Your") shall mean an individual or Legal Entity exercising 49 | permissions granted by this License. 50 | 51 | "Source" form shall mean the preferred form for making modifications, including 52 | but not limited to software source code, documentation source, and configuration 53 | files. 54 | 55 | "Object" form shall mean any form resulting from mechanical transformation or 56 | translation of a Source form, including but not limited to compiled object code, 57 | generated documentation, and conversions to other media types. 58 | 59 | "Work" shall mean the work of authorship, whether in Source or Object form, made 60 | available under the License, as indicated by a copyright notice that is included 61 | in or attached to the work (an example is provided in the Appendix below). 62 | 63 | "Derivative Works" shall mean any work, whether in Source or Object form, that 64 | is based on (or derived from) the Work and for which the editorial revisions, 65 | annotations, elaborations, or other modifications represent, as a whole, an 66 | original work of authorship. For the purposes of this License, Derivative Works 67 | shall not include works that remain separable from, or merely link (or bind by 68 | name) to the interfaces of, the Work and Derivative Works thereof. 69 | 70 | "Contribution" shall mean any work of authorship, including the original version 71 | of the Work and any modifications or additions to that Work or Derivative Works 72 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 73 | by the copyright owner or by an individual or Legal Entity authorized to submit 74 | on behalf of the copyright owner. For the purposes of this definition, 75 | "submitted" means any form of electronic, verbal, or written communication sent 76 | to the Licensor or its representatives, including but not limited to 77 | communication on electronic mailing lists, source code control systems, and 78 | issue tracking systems that are managed by, or on behalf of, the Licensor for 79 | the purpose of discussing and improving the Work, but excluding communication 80 | that is conspicuously marked or otherwise designated in writing by the copyright 81 | owner as "Not a Contribution." 82 | 83 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 84 | of whom a Contribution has been received by Licensor and subsequently 85 | incorporated within the Work. 86 | 87 | ### 2. Grant of Copyright License 88 | 89 | Subject to the terms and conditions of this License, each Contributor hereby 90 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 91 | irrevocable copyright license to reproduce, prepare Derivative Works of, 92 | publicly display, publicly perform, sublicense, and distribute the Work and such 93 | Derivative Works in Source or Object form. 94 | 95 | ### 3. Grant of Patent License 96 | 97 | Subject to the terms and conditions of this License, each Contributor hereby 98 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 99 | irrevocable (except as stated in this section) patent license to make, have 100 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 101 | such license applies only to those patent claims licensable by such Contributor 102 | that are necessarily infringed by their Contribution(s) alone or by combination 103 | of their Contribution(s) with the Work to which such Contribution(s) was 104 | submitted. If You institute patent litigation against any entity (including a 105 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 106 | Contribution incorporated within the Work constitutes direct or contributory 107 | patent infringement, then any patent licenses granted to You under this License 108 | for that Work shall terminate as of the date such litigation is filed. 109 | 110 | ### 4. Redistribution 111 | 112 | You may reproduce and distribute copies of the Work or Derivative Works thereof 113 | in any medium, with or without modifications, and in Source or Object form, 114 | provided that You meet the following conditions: 115 | 116 | * **(a)** You must give any other recipients of the Work or Derivative Works a 117 | copy of this License; and 118 | 119 | * **(b)** You must cause any modified files to carry prominent notices 120 | stating that You changed the files; and 121 | 122 | * **(c)** You must retain, in the Source form of any Derivative Works that You 123 | distribute, all copyright, patent, trademark, and attribution notices from the 124 | Source form of the Work, excluding those notices that do not pertain to any 125 | part of the Derivative Works; and 126 | 127 | * **(d)** If the Work includes a "NOTICE" text file as part of its distribution, 128 | then any Derivative Works that You distribute must include a readable copy of 129 | the attribution notices contained within such NOTICE file, excluding those 130 | notices that do not pertain to any part of the Derivative Works, in at least 131 | one of the following places: within a NOTICE text file distributed as part of 132 | the Derivative Works; within the Source form or documentation, if provided 133 | along with the Derivative Works; or, within a display generated by the 134 | Derivative Works, if and wherever such third-party notices normally appear. 135 | The contents of the NOTICE file are for informational purposes only and do not 136 | modify the License. You may add Your own attribution notices within Derivative 137 | Works that You distribute, alongside or as an addendum to the NOTICE text from 138 | the Work, provided that such additional attribution notices cannot be 139 | construed as modifying the License. 140 | 141 | You may add Your own copyright statement to Your modifications and may provide 142 | additional or different license terms and conditions for use, reproduction, or 143 | distribution of Your modifications, or for any such Derivative Works as a 144 | whole, provided Your use, reproduction, and distribution of the Work otherwise 145 | complies with the conditions stated in this License. 146 | 147 | ### 5. Submission of Contributions 148 | 149 | Unless You explicitly state otherwise, any Contribution intentionally submitted 150 | for inclusion in the Work by You to the Licensor shall be under the terms and 151 | conditions of this License, without any additional terms or conditions. 152 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 153 | any separate license agreement you may have executed with Licensor regarding 154 | such Contributions. 155 | 156 | ### 6. Trademarks 157 | 158 | This License does not grant permission to use the trade names, trademarks, 159 | service marks, or product names of the Licensor, except as required for 160 | reasonable and customary use in describing the origin of the Work and 161 | reproducing the content of the NOTICE file. 162 | 163 | ### 7. Disclaimer of Warranty 164 | 165 | Unless required by applicable law or agreed to in writing, Licensor provides the 166 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 167 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 168 | including, without limitation, any warranties or conditions of TITLE, 169 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 170 | solely responsible for determining the appropriateness of using or 171 | redistributing the Work and assume any risks associated with Your exercise of 172 | permissions under this License. 173 | 174 | ### 8. Limitation of Liability 175 | 176 | In no event and under no legal theory, whether in tort (including negligence), 177 | contract, or otherwise, unless required by applicable law (such as deliberate 178 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 179 | liable to You for damages, including any direct, indirect, special, incidental, 180 | or consequential damages of any character arising as a result of this License or 181 | out of the use or inability to use the Work (including but not limited to 182 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 183 | any and all other commercial damages or losses), even if such Contributor has 184 | been advised of the possibility of such damages. 185 | 186 | ### 9. Accepting Warranty or Additional Liability 187 | 188 | While redistributing the Work or Derivative Works thereof, You may choose to 189 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 190 | other liability obligations and/or rights consistent with this License. However, 191 | in accepting such obligations, You may act only on Your own behalf and on Your 192 | sole responsibility, not on behalf of any other Contributor, and only if You 193 | agree to indemnify, defend, and hold each Contributor harmless for any liability 194 | incurred by, or claims asserted against, such Contributor by reason of your 195 | accepting any such warranty or additional liability. 196 | 197 | END OF TERMS AND CONDITIONS 198 | 199 | ## Appendix: How to apply the Apache License to your work. 200 | 201 | To apply the Apache License to your work, attach the following boilerplate 202 | notice, with the fields enclosed by brackets "[]" replaced with your own 203 | identifying information. (Don't include the brackets!) The text should be 204 | enclosed in the appropriate comment syntax for the file format. We also 205 | recommend that a file or class name and description of purpose be included on 206 | the same "printed page" as the copyright notice for easier identification within 207 | third-party archives. 208 | 209 | Copyright [yyyy] [name of copyright owner] 210 | 211 | Licensed under the Apache License, Version 2.0 (the "License"); 212 | you may not use this file except in compliance with the License. 213 | You may obtain a copy of the License at 214 | 215 | http://www.apache.org/licenses/LICENSE-2.0 216 | 217 | Unless required by applicable law or agreed to in writing, software 218 | distributed under the License is distributed on an "AS IS" BASIS, 219 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 220 | See the License for the specific language governing permissions and 221 | limitations under the License. 222 | -------------------------------------------------------------------------------- /src/object/any.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | ffi::{c_void, CStr, CString}, 4 | marker::PhantomData, 5 | }; 6 | use crate::{ 7 | object::Ty, 8 | prelude::*, 9 | ruby, 10 | }; 11 | 12 | /// An instance of any Ruby object. 13 | #[derive(Clone, Copy)] 14 | #[repr(transparent)] 15 | pub struct AnyObject { 16 | raw: ruby::VALUE, 17 | // !Send + !Sync 18 | _marker: PhantomData<*const c_void>, 19 | } 20 | 21 | impl AsRef for AnyObject { 22 | #[inline] 23 | fn as_ref(&self) -> &Self { self } 24 | } 25 | 26 | unsafe impl Object for AnyObject { 27 | #[inline] 28 | fn unique_id() -> Option { 29 | Some(!0) 30 | } 31 | 32 | #[inline] 33 | unsafe fn from_raw(raw: ruby::VALUE) -> Self { 34 | AnyObject { raw, _marker: PhantomData } 35 | } 36 | 37 | #[inline] 38 | fn cast(obj: A) -> Option { 39 | Some(obj.into_any_object()) 40 | } 41 | 42 | fn ty(self) -> Ty { 43 | crate::util::value_ty(self.raw()) 44 | } 45 | 46 | #[inline] 47 | fn raw(self) -> ruby::VALUE { 48 | self.raw 49 | } 50 | 51 | #[inline] 52 | fn as_any_object(&self) -> &Self { &self } 53 | 54 | #[inline] 55 | fn into_any_object(self) -> Self { self } 56 | } 57 | 58 | impl PartialEq for AnyObject { 59 | #[inline] 60 | fn eq(&self, other: &O) -> bool { 61 | let result = unsafe { self.call_with(SymbolId::equal_op(), &[*other]) }; 62 | result.raw() == crate::util::TRUE_VALUE 63 | } 64 | } 65 | 66 | // Implements `PartialEq` against all relevant convertible types 67 | macro_rules! impl_eq { 68 | ($($t:ty, $convert:ident;)+) => { $( 69 | impl PartialEq<$t> for AnyObject { 70 | #[inline] 71 | fn eq(&self, other: &$t) -> bool { 72 | if let Some(value) = AnyObject::$convert(*self) { 73 | value == *other 74 | } else { 75 | false 76 | } 77 | } 78 | } 79 | 80 | // Needed to prevent conflict with `PartialEq` 81 | impl PartialEq<&$t> for AnyObject { 82 | #[inline] 83 | fn eq(&self, other: &&$t) -> bool { 84 | *self == **other 85 | } 86 | } 87 | 88 | impl PartialEq for $t { 89 | #[inline] 90 | fn eq(&self, obj: &AnyObject) -> bool { 91 | obj == self 92 | } 93 | } 94 | 95 | impl PartialEq for &$t { 96 | #[inline] 97 | fn eq(&self, obj: &AnyObject) -> bool { 98 | obj == self 99 | } 100 | } 101 | )+ } 102 | } 103 | 104 | impl_eq! { 105 | str, to_string; 106 | std::string::String, to_string; 107 | CStr, to_string; 108 | CString, to_string; 109 | bool, to_bool; 110 | } 111 | 112 | impl Eq for AnyObject {} 113 | 114 | impl fmt::Debug for AnyObject { 115 | #[inline] 116 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 117 | fmt::Display::fmt(&self.inspect(), f) 118 | } 119 | } 120 | 121 | impl fmt::Display for AnyObject { 122 | #[inline] 123 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 124 | fmt::Display::fmt(&self.to_s(), f) 125 | } 126 | } 127 | 128 | impl> From> for AnyObject { 129 | #[inline] 130 | fn from(option: Option) -> Self { 131 | option.map(Into::into).unwrap_or(AnyObject::nil()) 132 | } 133 | } 134 | 135 | impl, E: Into> From> for AnyObject { 136 | #[inline] 137 | fn from(result: Result) -> Self { 138 | match result { 139 | Ok(obj) => obj.into(), 140 | Err(err) => err.into(), 141 | } 142 | } 143 | } 144 | 145 | impl From for AnyObject { 146 | #[inline] 147 | fn from(b: bool) -> Self { 148 | Self::from_bool(b) 149 | } 150 | } 151 | 152 | impl From<()> for AnyObject { 153 | #[inline] 154 | fn from(_nil: ()) -> Self { 155 | Self::nil() 156 | } 157 | } 158 | 159 | impl AnyObject { 160 | #[inline] 161 | pub(crate) fn _ptr(self) -> *mut std::ffi::c_void { 162 | self.raw() as usize as _ 163 | } 164 | 165 | /// Casts the concrete slice `objects` into a slice of `AnyObject`. 166 | #[inline] 167 | pub fn convert_slice(objects: &[impl Object]) -> &[AnyObject] { 168 | unsafe { &*(objects as *const [_] as *const _) } 169 | } 170 | 171 | /// Calls `super` on the current receiver without any arguments in the 172 | /// context of a method. 173 | #[inline] 174 | pub fn call_super() -> Result { 175 | let args: &[AnyObject] = &[]; 176 | Self::call_super_with(args) 177 | } 178 | 179 | /// Calls `super` on the current receiver without any arguments in the 180 | /// context of a method, without checking for an exception. 181 | #[inline] 182 | pub unsafe fn call_super_unchecked() -> AnyObject { 183 | let args: &[AnyObject] = &[]; 184 | Self::call_super_with_unchecked(args) 185 | } 186 | 187 | /// Calls `super` on the current receiver with `args` in the context of a 188 | /// method. 189 | #[inline] 190 | pub fn call_super_with(args: &[impl Object]) -> Result { 191 | Self::_call_super_with(Self::convert_slice(args)) 192 | } 193 | 194 | // monomorphization 195 | fn _call_super_with(args: &[AnyObject]) -> Result { 196 | unsafe { 197 | crate::protected_no_panic(|| Self::call_super_with_unchecked(args)) 198 | } 199 | } 200 | 201 | /// Calls `super` on the current receiver with `args` in the context of a 202 | /// method, without checking for an exception. 203 | #[inline] 204 | pub unsafe fn call_super_with_unchecked(args: &[impl Object]) -> AnyObject { 205 | let len = args.len(); 206 | let ptr = args.as_ptr() as *const ruby::VALUE; 207 | AnyObject::from_raw(ruby::rb_call_super(len as _, ptr)) 208 | } 209 | 210 | /// An alternative to 211 | /// [`Object::from_raw`](trait.Object.html#method.from_raw) that works in a 212 | /// `const` context. 213 | #[inline] 214 | pub const unsafe fn from_raw(raw: ruby::VALUE) -> AnyObject { 215 | AnyObject { raw, _marker: PhantomData } 216 | } 217 | 218 | /// Returns a `nil` instance. 219 | #[inline] 220 | pub const fn nil() -> AnyObject { 221 | unsafe { AnyObject::from_raw(crate::util::NIL_VALUE) } 222 | } 223 | 224 | /// Returns an instance from a boolean. 225 | #[inline] 226 | pub const fn from_bool(b: bool) -> AnyObject { 227 | // `false` uses 0 in Ruby 228 | let raw = crate::util::TRUE_VALUE * b as ruby::VALUE; 229 | unsafe { AnyObject::from_raw(raw) } 230 | } 231 | 232 | /// Returns whether `self` is `nil`. 233 | #[inline] 234 | pub const fn is_nil(self) -> bool { 235 | self.raw == crate::util::NIL_VALUE 236 | } 237 | 238 | /// Returns whether `self` is undefined. 239 | #[inline] 240 | pub const fn is_undefined(self) -> bool { 241 | self.raw == crate::util::UNDEF_VALUE 242 | } 243 | 244 | /// Returns whether `self` is `true`. 245 | #[inline] 246 | pub const fn is_true(self) -> bool { 247 | self.raw == crate::util::TRUE_VALUE 248 | } 249 | 250 | /// Returns whether `self` is `false`. 251 | #[inline] 252 | pub const fn is_false(self) -> bool { 253 | self.raw == crate::util::FALSE_VALUE 254 | } 255 | 256 | /// Returns whether `self` is either `false` or `nil`. 257 | /// 258 | /// Ruby treats both values as falsey in control flow contexts. 259 | #[inline] 260 | pub const fn is_false_or_nil(self) -> bool { 261 | crate::util::test_value(self.raw) 262 | } 263 | 264 | /// Returns whether `self` is either `false` or `true`. 265 | #[inline] 266 | pub const fn is_bool(self) -> bool { 267 | // HACK: Needed for working with `const fn` while `||` isn't stable; 268 | // only returns `false` if neither is `true` since both can't be `true` 269 | self.is_true() != self.is_false() 270 | } 271 | 272 | /// Returns the boolean value for `self`, if any. 273 | #[inline] 274 | pub fn to_bool(self) -> Option { 275 | match self.raw() { 276 | crate::util::TRUE_VALUE => Some(true), 277 | crate::util::FALSE_VALUE => Some(false), 278 | _ => None, 279 | } 280 | } 281 | 282 | /// Returns whether `self` is a fixed-sized number. 283 | #[inline] 284 | pub const fn is_fixnum(self) -> bool { 285 | crate::util::value_is_fixnum(self.raw) 286 | } 287 | 288 | /// Returns whether `self` is a variable-sized number. 289 | #[inline] 290 | pub fn is_bignum(self) -> bool { 291 | self.ty() == Ty::BIGNUM 292 | } 293 | 294 | /// Returns whether `self` is a fixed-sized number. 295 | #[inline] 296 | pub fn is_integer(self) -> bool { 297 | self.is_fixnum() || self.is_bignum() 298 | } 299 | 300 | /// Returns `self` as an `Integer` if it is one. 301 | #[inline] 302 | pub fn to_integer(self) -> Option { 303 | Integer::cast(self) 304 | } 305 | 306 | /// Returns whether `self` is a floating point number type. 307 | #[inline] 308 | pub fn is_float(self) -> bool { 309 | crate::util::value_is_float(self.raw()) 310 | } 311 | 312 | /// Returns `self` as a `Float` if it is one. 313 | #[inline] 314 | pub fn to_float(self) -> Option { 315 | Float::cast(self) 316 | } 317 | 318 | /// Returns whether `self` is a `String`. 319 | #[inline] 320 | pub fn is_string(self) -> bool { 321 | crate::util::value_is_built_in_ty(self.raw(), Ty::STRING) 322 | } 323 | 324 | /// Returns `self` as a `String` if it is one. 325 | #[inline] 326 | pub fn to_string(self) -> Option { 327 | if self.is_string() { 328 | unsafe { Some(String::cast_unchecked(self)) } 329 | } else { 330 | None 331 | } 332 | } 333 | 334 | /// Returns whether `self` is a `Symbol`. 335 | #[inline] 336 | pub fn is_symbol(self) -> bool { 337 | crate::util::value_is_sym(self.raw()) 338 | } 339 | 340 | /// Returns `self` as a `Symbol` if it is one. 341 | #[inline] 342 | pub fn to_symbol(self) -> Option { 343 | if self.is_symbol() { 344 | unsafe { Some(Symbol::cast_unchecked(self)) } 345 | } else { 346 | None 347 | } 348 | } 349 | 350 | /// Returns whether `self` is an `Array`. 351 | #[inline] 352 | pub fn is_array(self) -> bool { 353 | crate::util::value_is_built_in_ty(self.raw(), Ty::ARRAY) 354 | } 355 | 356 | /// Returns `self` as an `Array` if it is one. 357 | /// 358 | /// # Safety 359 | /// 360 | /// If `self` refers to an `Array` and after this method objects of type 361 | /// `Y` are inserted, expect 362 | /// [nasal demons](https://en.wikipedia.org/wiki/Nasal_demons). You've been 363 | /// warned. 364 | #[inline] 365 | pub fn to_array(self) -> Option { 366 | if self.is_array() { 367 | unsafe { Some(Array::cast_unchecked(self)) } 368 | } else { 369 | None 370 | } 371 | } 372 | 373 | /// Returns whether `self` is a `Class`. 374 | #[inline] 375 | pub fn is_class(self) -> bool { 376 | crate::util::value_is_class(self.raw()) 377 | } 378 | 379 | /// Returns `self` as a `Class` if it is one. 380 | #[inline] 381 | pub fn to_class(self) -> Option { 382 | if self.is_class() { 383 | unsafe { Some(Class::cast_unchecked(self)) } 384 | } else { 385 | None 386 | } 387 | } 388 | 389 | /// Returns whether `self` is a `Module`. 390 | #[inline] 391 | pub fn is_module(self) -> bool { 392 | crate::util::value_is_module(self.raw()) 393 | } 394 | 395 | /// Returns `self` as a `Module` if it is one. 396 | #[inline] 397 | pub fn to_module(self) -> Option { 398 | if self.is_module() { 399 | unsafe { Some(Module::cast_unchecked(self)) } 400 | } else { 401 | None 402 | } 403 | } 404 | 405 | /// Returns whether `self` is an `Exception`. 406 | #[inline] 407 | pub fn is_exception(self) -> bool { 408 | self.class().inherits(Class::exception()) 409 | } 410 | 411 | /// Returns `self` as an `AnyException` if it is one. 412 | #[inline] 413 | pub fn to_exception(self) -> Option { 414 | if self.is_exception() { 415 | unsafe { Some(AnyException::cast_unchecked(self)) } 416 | } else { 417 | None 418 | } 419 | } 420 | } 421 | --------------------------------------------------------------------------------