├── .ruby-version ├── src ├── helpers │ ├── mod.rs │ └── codepoint_iterator.rs ├── binding │ ├── global.rs │ ├── mod.rs │ ├── rproc.rs │ ├── float.rs │ ├── symbol.rs │ ├── fixnum.rs │ ├── gc.rs │ ├── hash.rs │ ├── module.rs │ ├── array.rs │ ├── encoding.rs │ ├── string.rs │ ├── thread.rs │ ├── class.rs │ └── vm.rs ├── typed_data │ ├── data_type_wrapper.rs │ └── mod.rs ├── class │ ├── traits │ │ ├── mod.rs │ │ ├── encoding_support.rs │ │ ├── try_convert.rs │ │ ├── verified_object.rs │ │ └── exception.rs │ ├── mod.rs │ ├── binding.rs │ ├── nil_class.rs │ ├── boolean.rs │ ├── any_exception.rs │ ├── float.rs │ ├── rproc.rs │ ├── any_object.rs │ ├── fixnum.rs │ ├── symbol.rs │ ├── thread.rs │ ├── encoding.rs │ ├── gc.rs │ ├── enumerator.rs │ ├── hash.rs │ └── integer.rs ├── rubysys │ ├── mod.rs │ ├── float.rs │ ├── symbol.rs │ ├── hash.rs │ ├── types.rs │ ├── fixnum.rs │ ├── gc.rs │ ├── rproc.rs │ ├── constant.rs │ ├── array.rs │ ├── typed_data.rs │ ├── vm.rs │ ├── string.rs │ ├── class.rs │ ├── encoding.rs │ ├── value.rs │ └── thread.rs ├── types.rs ├── lib.rs └── util.rs ├── examples ├── rutie_rust_example │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── rutie_ruby_example │ ├── lib │ │ ├── rutie_ruby_example │ │ │ └── version.rb │ │ └── rutie_ruby_example.rb │ ├── bin │ │ ├── setup │ │ └── console │ ├── .gitignore │ ├── test │ │ ├── rutie_ruby_example_test.rb │ │ └── test_helper.rb │ ├── Cargo.toml │ ├── Gemfile │ ├── src │ │ └── lib.rs │ ├── Rakefile │ ├── LICENSE │ └── rutie_ruby_example.gemspec ├── rutie_ruby_gvl_example │ ├── lib │ │ ├── rutie_ruby_gvl_example │ │ │ └── version.rb │ │ └── rutie_ruby_gvl_example.rb │ ├── bin │ │ ├── setup │ │ └── console │ ├── .gitignore │ ├── test │ │ ├── test_helper.rb │ │ └── rutie_ruby_gvl_example_test.rb │ ├── Cargo.toml │ ├── Gemfile │ ├── Rakefile │ ├── LICENSE │ ├── rutie_ruby_gvl_example.gemspec │ └── src │ │ └── lib.rs └── eval.rs ├── .gitmodules ├── .gitignore ├── LICENSE ├── Cargo.toml ├── .github └── workflows │ └── ci.yml └── CHANGELOG.md /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.8 2 | -------------------------------------------------------------------------------- /src/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod codepoint_iterator; 2 | -------------------------------------------------------------------------------- /examples/rutie_rust_example/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gem"] 2 | path = gem 3 | url = https://github.com/danielpclark/rutie-gem.git 4 | -------------------------------------------------------------------------------- /src/binding/global.rs: -------------------------------------------------------------------------------- 1 | pub use crate::rubysys::{ 2 | rb_cObject, 3 | value::{RubySpecialConsts, ValueType}, 4 | }; 5 | -------------------------------------------------------------------------------- /src/typed_data/data_type_wrapper.rs: -------------------------------------------------------------------------------- 1 | use crate::types::DataType; 2 | 3 | pub trait DataTypeWrapper { 4 | fn data_type(&self) -> &DataType; 5 | } 6 | -------------------------------------------------------------------------------- /examples/rutie_ruby_example/lib/rutie_ruby_example/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RutieRubyExample 4 | VERSION = '0.1.0' 5 | end 6 | -------------------------------------------------------------------------------- /examples/rutie_ruby_gvl_example/lib/rutie_ruby_gvl_example/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RutieRubyGvlExample 4 | VERSION = '0.1.0' 5 | end 6 | -------------------------------------------------------------------------------- /examples/rutie_ruby_example/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /examples/rutie_ruby_gvl_example/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /examples/rutie_ruby_example/.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | **/*.swp 10 | 11 | 12 | /target 13 | **/*.rs.bk 14 | Cargo.lock 15 | -------------------------------------------------------------------------------- /examples/rutie_ruby_gvl_example/.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | **/*.swp 10 | 11 | 12 | /target 13 | **/*.rs.bk 14 | Cargo.lock 15 | -------------------------------------------------------------------------------- /examples/rutie_ruby_example/test/rutie_ruby_example_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class RutieRubyExampleTest < Minitest::Test 4 | def test_it_reverses 5 | assert_equal "selppa", RutieExample.reverse("apples") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /examples/rutie_rust_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rutie_rust_example" 3 | version = "0.1.0" 4 | authors = ["Daniel P. Clark <6ftdan@gmail.com>"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | rutie = { path = "../../", features = ["link-ruby"] } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/Cargo.lock 2 | **/Gemfile.lock 3 | **/target 4 | **/*.swp 5 | **/.bundle/ 6 | **/.yardoc 7 | **/_yardoc/ 8 | **/coverage/ 9 | **/doc/ 10 | **/pkg/ 11 | **/spec/reports/ 12 | **/tmp/ 13 | **/*.swp 14 | **/*.rs.bk 15 | **/*.gem 16 | **/.DS_Store 17 | -------------------------------------------------------------------------------- /examples/rutie_ruby_example/lib/rutie_ruby_example.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rutie_ruby_example/version' 4 | require 'rutie' 5 | 6 | module RutieRubyExample 7 | Rutie.new(:rutie_ruby_example).init 'Init_rutie_ruby_example', __dir__ 8 | end 9 | -------------------------------------------------------------------------------- /examples/rutie_ruby_example/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__) 2 | require 'rutie_ruby_example' 3 | 4 | require 'minitest/autorun' 5 | require 'color_pound_spec_reporter' 6 | Minitest::Reporters.use! [ColorPoundSpecReporter.new] 7 | -------------------------------------------------------------------------------- /src/class/traits/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod encoding_support; 2 | pub mod exception; 3 | // Is good to have these long examples to be able to be copied and pasted as is. 4 | #[allow(clippy::needless_doctest_main)] 5 | pub mod object; 6 | pub mod try_convert; 7 | pub mod verified_object; 8 | -------------------------------------------------------------------------------- /examples/rutie_ruby_gvl_example/lib/rutie_ruby_gvl_example.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rutie_ruby_gvl_example/version' 4 | require 'rutie' 5 | 6 | module RutieRubyGvlExample 7 | Rutie.new(:rutie_ruby_gvl_example).init 'Init_rutie_ruby_gvl_example', __dir__ 8 | end 9 | -------------------------------------------------------------------------------- /src/binding/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod array; 2 | pub mod class; 3 | pub mod encoding; 4 | pub mod fixnum; 5 | pub mod float; 6 | pub mod gc; 7 | pub mod global; 8 | pub mod hash; 9 | pub mod module; 10 | pub mod rproc; 11 | pub mod string; 12 | pub mod symbol; 13 | pub mod thread; 14 | pub mod vm; 15 | -------------------------------------------------------------------------------- /examples/rutie_ruby_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rutie_ruby_example" 3 | version = "0.1.0" 4 | authors = ["Daniel P. Clark <6ftdan@gmail.com>"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | rutie = { path = "../../" } 9 | 10 | [lib] 11 | name = "rutie_ruby_example" 12 | crate-type = ["dylib"] 13 | -------------------------------------------------------------------------------- /examples/rutie_ruby_gvl_example/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__) 4 | require 'rutie_ruby_gvl_example' 5 | 6 | require 'minitest/autorun' 7 | require 'color_pound_spec_reporter' 8 | Minitest::Reporters.use! [ColorPoundSpecReporter.new] 9 | -------------------------------------------------------------------------------- /src/typed_data/mod.rs: -------------------------------------------------------------------------------- 1 | mod data_type_wrapper; 2 | 3 | use crate::types::c_void; 4 | 5 | pub use self::data_type_wrapper::DataTypeWrapper; 6 | 7 | pub extern "C" fn free(data: *mut c_void) { 8 | // Memory is freed when the box goes out of the scope 9 | unsafe { 10 | let _ = Box::from_raw(data as *mut T); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/rubysys/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod array; 2 | pub mod class; 3 | pub mod constant; 4 | pub mod encoding; 5 | pub mod fixnum; 6 | pub mod float; 7 | pub mod gc; 8 | pub mod hash; 9 | pub mod rproc; 10 | pub mod string; 11 | pub mod symbol; 12 | pub mod thread; 13 | pub mod typed_data; 14 | pub mod types; 15 | pub mod value; 16 | pub mod vm; 17 | 18 | pub use rb_sys::rb_cObject; 19 | -------------------------------------------------------------------------------- /src/rubysys/float.rs: -------------------------------------------------------------------------------- 1 | use super::types::{c_double, Value}; 2 | 3 | extern "C" { 4 | // VALUE 5 | // rb_float_new(double d) 6 | pub fn rb_float_new(num: f64) -> Value; 7 | // double 8 | // rb_num2dbl(VALUE val) 9 | pub fn rb_num2dbl(num: Value) -> c_double; 10 | // VALUE 11 | // rb_to_float(VALUE val) 12 | pub fn rb_to_float(num: Value) -> Value; 13 | } 14 | -------------------------------------------------------------------------------- /examples/rutie_ruby_gvl_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rutie_ruby_gvl_example" 3 | version = "0.1.0" 4 | authors = ["Daniel P. Clark <6ftdan@gmail.com>"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | rutie = { path = "../../" } 9 | 10 | [lib] 11 | name = "rutie_ruby_gvl_example" 12 | crate-type = ["dylib"] 13 | 14 | [profile.release] 15 | debug = true 16 | opt-level = 0 17 | rpath = true 18 | debug-assertions = true 19 | -------------------------------------------------------------------------------- /examples/rutie_ruby_example/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'rutie_ruby_example' 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require 'irb' 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /examples/rutie_ruby_gvl_example/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "rutie_ruby_gvl_example" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use crate::AnyObject; 2 | 3 | pub use crate::rubysys::types::{ 4 | c_char, c_int, c_long, c_void, size_t, st_retval, Argc, CallbackMutPtr, CallbackPtr, 5 | EncodingIndex, EncodingType, Id, InternalValue, RbDataType as DataType, 6 | RbDataTypeFunction as DataTypeFunction, SignedValue, Value, ValueType, VmPointer, 7 | }; 8 | 9 | #[cfg(unix)] 10 | pub use crate::rubysys::types::RawFd; 11 | 12 | pub type Callback = extern "C" fn(Argc, *const AnyObject, I) -> O; 13 | -------------------------------------------------------------------------------- /src/class/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod any_exception; 2 | pub mod any_object; 3 | pub mod array; 4 | pub mod binding; 5 | pub mod boolean; 6 | #[allow(clippy::module_inception)] // we want class::class. 7 | pub mod class; 8 | pub mod encoding; 9 | pub mod enumerator; 10 | pub mod fixnum; 11 | pub mod float; 12 | pub mod gc; 13 | pub mod hash; 14 | pub mod integer; 15 | pub mod module; 16 | pub mod nil_class; 17 | pub mod rproc; 18 | pub mod string; 19 | pub mod symbol; 20 | pub mod thread; 21 | pub mod traits; 22 | pub mod vm; 23 | -------------------------------------------------------------------------------- /examples/eval.rs: -------------------------------------------------------------------------------- 1 | use rutie::VM; 2 | use std::{env, process}; 3 | 4 | fn main() { 5 | VM::init(); 6 | let args: Vec = env::args().collect(); 7 | if args.len() > 1 { 8 | match VM::eval(&args[1]) { 9 | Ok(_) => (), 10 | Err(e) => { 11 | println!("{}", e); 12 | process::exit(1); 13 | } 14 | } 15 | } else { 16 | eprintln!(r#"Usage: eval "puts 'Put ruby code to be evaluated in a string after eval.' ""#); 17 | process::exit(1); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/rutie_ruby_example/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } 6 | 7 | # Specify your gem's dependencies in rutie_ruby_example.gemspec 8 | gemspec 9 | 10 | # Not needed for production code. Gemspec file is enough for your gems. 11 | # Make sure you do `git submodule update --init` 12 | gem 'rutie', path: '../../gem/' 13 | 14 | group :test do 15 | gem 'color_pound_spec_reporter', '~> 0.0.6' 16 | gem 'minitest', '~> 5.10' 17 | gem 'minitest-reporters', '~> 1.1' 18 | end 19 | -------------------------------------------------------------------------------- /src/class/traits/encoding_support.rs: -------------------------------------------------------------------------------- 1 | use crate::{AnyException, AnyObject, Encoding, Hash, Object}; 2 | 3 | pub trait EncodingSupport { 4 | fn encode(&self, enc: Encoding, opts: Option) -> Self 5 | where 6 | Self: Sized; 7 | fn encoding(&self) -> Encoding; 8 | fn force_encoding(&mut self, enc: Encoding) -> Result 9 | where 10 | Self: Sized; 11 | fn is_valid_encoding(&self) -> bool; 12 | fn compatible_with(&self, other: &impl Object) -> bool; 13 | fn compatible_encoding(obj1: &impl Object, obj2: &impl Object) -> AnyObject; 14 | } 15 | -------------------------------------------------------------------------------- /examples/rutie_ruby_gvl_example/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } 6 | 7 | # Specify your gem's dependencies in rutie_ruby_gvl_example.gemspec 8 | gemspec 9 | 10 | # Not needed for production code. Gemspec file is enough for your gems. 11 | # Make sure you do `git submodule update --init` 12 | gem 'rutie', path: '../../gem/' 13 | 14 | group :test do 15 | gem 'color_pound_spec_reporter', '~> 0.0.6' 16 | gem 'minitest', '~> 5.10' 17 | gem 'minitest-reporters', '~> 1.1' 18 | end 19 | -------------------------------------------------------------------------------- /src/rubysys/symbol.rs: -------------------------------------------------------------------------------- 1 | use super::types::{c_char, c_long, Id, Value}; 2 | 3 | extern "C" { 4 | // VALUE 5 | // rb_id2sym(ID x) 6 | pub fn rb_id2sym(id: Id) -> Value; 7 | // const char * 8 | // rb_id2name(ID id) 9 | pub fn rb_id2name(id: Id) -> *const c_char; 10 | // ID 11 | // rb_sym2id(VALUE sym) 12 | pub fn rb_sym2id(id: Value) -> Id; 13 | // ID 14 | // rb_intern(const char *name) 15 | pub fn rb_intern(name: *const c_char) -> Id; 16 | // ID 17 | // rb_intern2(const char *name, long len) 18 | pub fn rb_intern2(name: *const c_char, len: c_long) -> Id; 19 | } 20 | -------------------------------------------------------------------------------- /src/binding/rproc.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | binding::global::RubySpecialConsts, 3 | rubysys::rproc, 4 | types::{InternalValue, Value}, 5 | util, 6 | }; 7 | 8 | pub fn call(rproc: Value, arguments: &[Value]) -> Value { 9 | let (argc, argv) = util::process_arguments(arguments); 10 | 11 | unsafe { 12 | rproc::rb_proc_call_with_block( 13 | rproc, 14 | argc, 15 | argv as *const _, 16 | Value::from(RubySpecialConsts::Nil as InternalValue), 17 | ) 18 | } 19 | } 20 | 21 | pub fn binding_new() -> Value { 22 | unsafe { rproc::rb_binding_new() } 23 | } 24 | -------------------------------------------------------------------------------- /src/class/traits/try_convert.rs: -------------------------------------------------------------------------------- 1 | /// Implicit conversion or `nil`. 2 | /// 3 | /// This is meant for “implicit conversions” much like Ruby's: 4 | /// 5 | /// * `Array.try_convert` 6 | /// * `Hash.try_convert` 7 | /// * `String.try_convert` 8 | /// * `Regexp.try_convert` 9 | /// * `IO.try_convert` 10 | /// 11 | /// This is NOT Rust object to Rust object casting for Ruby objects like `try_convert_to` is. 12 | pub trait TryConvert: Sized { 13 | /// The type returned in the event of a conversion error. 14 | type Nil; 15 | 16 | /// Performs the conversion. 17 | fn try_convert(value: T) -> Result; 18 | } 19 | -------------------------------------------------------------------------------- /examples/rutie_ruby_example/src/lib.rs: -------------------------------------------------------------------------------- 1 | use rutie::{class, methods, Class, Object, RString, VM}; 2 | 3 | class!(RutieExample); 4 | 5 | methods!( 6 | RutieExample, 7 | _rtself, 8 | fn pub_reverse(input: RString) -> RString { 9 | let ruby_string = input.map_err(VM::raise_ex).unwrap(); 10 | 11 | RString::new_utf8(&ruby_string.to_string().chars().rev().collect::()) 12 | } 13 | ); 14 | 15 | #[allow(non_snake_case)] 16 | #[no_mangle] 17 | pub extern "C" fn Init_rutie_ruby_example() { 18 | Class::new("RutieExample", None).define(|klass| { 19 | klass.def_self("reverse", pub_reverse); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/binding/float.rs: -------------------------------------------------------------------------------- 1 | use crate::{rubysys::float, types::Value, AnyException, AnyObject, Float, Object, VM}; 2 | 3 | pub fn float_to_num(num: f64) -> Value { 4 | unsafe { float::rb_float_new(num) } 5 | } 6 | 7 | pub fn num_to_float(num: Value) -> f64 { 8 | unsafe { float::rb_num2dbl(num) } 9 | } 10 | 11 | pub fn implicit_to_f(num: Value) -> Result { 12 | let closure = || unsafe { 13 | let value: Value = float::rb_to_float(num); 14 | AnyObject::from(value) 15 | }; 16 | 17 | let result = VM::protect(closure); 18 | 19 | result.map(|f| Float::from(f.value())).map_err(|_| { 20 | let output = VM::error_info().unwrap(); 21 | 22 | // error cleanup 23 | VM::clear_error_info(); 24 | 25 | output 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /examples/rutie_rust_example/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | 6 | use rutie::{Object, RString, VM}; 7 | 8 | fn try_it(s: &str) -> String { 9 | let a = RString::new_utf8(s); 10 | 11 | // Send returns an AnyObject type 12 | let b = unsafe { a.send("reverse", &[]) }; 13 | 14 | // We must try to convert the AnyObject 15 | // type back to our usable type. 16 | match b.try_convert_to::() { 17 | Ok(ruby_string) => ruby_string.to_string(), 18 | Err(_) => "Fail!".to_string(), 19 | } 20 | } 21 | 22 | #[test] 23 | fn it_works() { 24 | // Rust projects must start the Ruby VM 25 | VM::init(); 26 | 27 | assert_eq!("selppa", try_it("apples")); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/binding/symbol.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | rubysys::symbol, 3 | types::{c_char, c_long, Id, Value}, 4 | util, 5 | }; 6 | 7 | pub fn value_to_str<'a>(value: Value) -> &'a str { 8 | let ptr = sym_to_ptr(value); 9 | 10 | unsafe { util::cstr_to_str(ptr) } 11 | } 12 | 13 | pub fn value_to_string(value: Value) -> String { 14 | let ptr = sym_to_ptr(value); 15 | 16 | unsafe { util::cstr_to_string(ptr) } 17 | } 18 | 19 | pub fn id_to_sym(id: Id) -> Value { 20 | unsafe { symbol::rb_id2sym(id) } 21 | } 22 | 23 | fn sym_to_ptr(value: Value) -> *const c_char { 24 | let id = sym_to_id(value); 25 | 26 | id_to_name(id) 27 | } 28 | 29 | fn sym_to_id(sym: Value) -> Id { 30 | unsafe { symbol::rb_sym2id(sym) } 31 | } 32 | 33 | fn id_to_name(id: Id) -> *const c_char { 34 | unsafe { symbol::rb_id2name(id) } 35 | } 36 | 37 | pub fn internal_id(string: &str) -> Id { 38 | let str = string.as_ptr() as *const c_char; 39 | let len = string.len() as c_long; 40 | 41 | unsafe { symbol::rb_intern2(str, len) } 42 | } 43 | -------------------------------------------------------------------------------- /examples/rutie_ruby_example/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rbconfig' 4 | require 'bundler/gem_tasks' 5 | require 'rake/testtask' 6 | 7 | # Mac OS with rbenv users keep leaving behind build artifacts from 8 | # when they tried to build against a statically linked Ruby and then 9 | # try against a dynamically linked one causing errors in the build result. 10 | desc 'Preventative work' 11 | task :tidy_up do 12 | sh 'cargo clean' 13 | end 14 | 15 | desc 'Build Rust extension' 16 | task :build_lib do 17 | case RbConfig::CONFIG['host_os'] 18 | when /darwin|mac os/ 19 | sh 'cargo rustc --release -- -C link-args=-Wl,-undefined,dynamic_lookup' 20 | else 21 | sh 'cargo build --release' 22 | end 23 | end 24 | 25 | desc 'bundle install' 26 | task :bundle_install do 27 | sh 'bundle install' 28 | end 29 | 30 | Rake::TestTask.new(test: %i[tidy_up bundle_install build_lib]) do |t| 31 | t.libs << 'test' 32 | t.libs << 'lib' 33 | t.test_files = FileList['test/**/*_test.rb'] 34 | end 35 | 36 | task default: :test 37 | -------------------------------------------------------------------------------- /examples/rutie_ruby_gvl_example/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rbconfig' 4 | require 'bundler/gem_tasks' 5 | require 'rake/testtask' 6 | 7 | # Mac OS with rbenv users keep leaving behind build artifacts from 8 | # when they tried to build against a statically linked Ruby and then 9 | # try against a dynamically linked one causing errors in the build result. 10 | desc 'Preventative work' 11 | task :tidy_up do 12 | sh 'cargo clean' 13 | end 14 | 15 | desc 'Build Rust extension' 16 | task :build_lib do 17 | case RbConfig::CONFIG['host_os'] 18 | when /darwin|mac os/ 19 | sh 'cargo rustc --release -- -C link-args=-Wl,-undefined,dynamic_lookup' 20 | else 21 | sh 'cargo build --release' 22 | end 23 | end 24 | 25 | desc 'bundle install' 26 | task :bundle_install do 27 | sh 'bundle install' 28 | end 29 | 30 | Rake::TestTask.new(test: %i[tidy_up bundle_install build_lib]) do |t| 31 | t.libs << 'test' 32 | t.libs << 'lib' 33 | t.test_files = FileList['test/**/*_test.rb'] 34 | end 35 | 36 | task default: :test 37 | -------------------------------------------------------------------------------- /src/rubysys/hash.rs: -------------------------------------------------------------------------------- 1 | use super::types::{CallbackMutPtr, CallbackPtr, Value}; 2 | 3 | extern "C" { 4 | // VALUE 5 | // rb_hash_aref(VALUE hash, VALUE key) 6 | pub fn rb_hash_aref(hash: Value, key: Value) -> Value; 7 | // VALUE 8 | // rb_hash_aset(VALUE hash, VALUE key, VALUE val) 9 | pub fn rb_hash_aset(hash: Value, key: Value, value: Value) -> Value; 10 | // VALUE 11 | // rb_hash_clear(VALUE hash) 12 | pub fn rb_hash_clear(hash: Value) -> Value; 13 | // VALUE 14 | // rb_hash_delete(VALUE hash, VALUE key) 15 | pub fn rb_hash_delete(hash: Value, key: Value) -> Value; 16 | // VALUE 17 | // rb_hash_dup(VALUE hash) 18 | pub fn rb_hash_dup(hash: Value) -> Value; 19 | // void 20 | // rb_hash_foreach(VALUE hash, int (*func)(ANYARGS), VALUE farg) 21 | pub fn rb_hash_foreach(hash: Value, callback: CallbackPtr, pass: CallbackMutPtr); 22 | // VALUE 23 | // rb_hash_new(void) 24 | pub fn rb_hash_new() -> Value; 25 | // VALUE 26 | // rb_hash_size(VALUE hash) 27 | pub fn rb_hash_size(hash: Value) -> Value; 28 | } 29 | -------------------------------------------------------------------------------- /examples/rutie_ruby_example/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Daniel P. Clark 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/rutie_ruby_gvl_example/test/rutie_ruby_gvl_example_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | 5 | class RutieRubyExampleTest < Minitest::Test 6 | def test_it_returns_stack_allocated_captured_variables 7 | ret = RutieExample.stack_allocated_returning_input 8 | assert_equal 42, ret 9 | end 10 | 11 | def test_it_returns_heap_allocated_captured_variables 12 | ret = RutieExample.heap_allocated_returning_input 13 | assert_equal 'Object', ret 14 | end 15 | 16 | def test_it_captures_stack_allocated_variables_and_returns_computation 17 | ret = RutieExample.stack_allocated_returning_from_closure(5) 18 | assert_equal '8', ret 19 | end 20 | 21 | def test_it_captures_heap_allocated_variables_and_returns_computation 22 | ret = RutieExample.heap_allocated_returning_from_closure(5) 23 | assert_equal 8, ret 24 | end 25 | 26 | def test_calls_ruby_method_via__call_with_gvl 27 | ret = RutieExample.call_ruby_in_call_with_gvl 28 | assert_equal 'Object', ret 29 | end 30 | 31 | def test_it_spawns_a_ruby_thread 32 | ret = RutieExample.create_thread 33 | assert_equal 'Object', ret 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /examples/rutie_ruby_gvl_example/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Daniel P. Clark 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/rubysys/types.rs: -------------------------------------------------------------------------------- 1 | use libc::intptr_t; 2 | pub use libc::{c_char, c_double, c_int, c_long, c_void, size_t, ssize_t}; 3 | 4 | pub use super::{ 5 | typed_data::{RbDataType, RbDataTypeFunction}, 6 | value::{Value, ValueType}, 7 | }; 8 | 9 | #[cfg(unix)] 10 | pub use std::os::unix::io::RawFd; 11 | 12 | pub type Id = rb_sys::ID; 13 | pub type InternalValue = rb_sys::VALUE; 14 | pub type SignedValue = intptr_t; 15 | 16 | pub type EncodingIndex = c_int; 17 | pub type EncodingType = CallbackPtr; 18 | 19 | pub type VmPointer = CallbackPtr; 20 | 21 | pub type Argc = c_int; 22 | pub type CallbackPtr = *const c_void; 23 | pub type CallbackMutPtr = *mut c_void; 24 | pub type BlockCallFunction = extern "C" fn( 25 | yielded_arg: Value, 26 | callback_arg: Value, 27 | argc: c_int, 28 | argv: *const Value, 29 | block_arg: Value, 30 | ) -> Value; 31 | 32 | pub use rb_sys::RBasic; 33 | 34 | #[repr(C)] 35 | pub enum st_retval { 36 | Continue = rb_sys::st_retval::ST_CONTINUE as isize, 37 | Stop = rb_sys::st_retval::ST_STOP as isize, 38 | Delete = rb_sys::st_retval::ST_DELETE as isize, 39 | Check = rb_sys::st_retval::ST_CHECK as isize, 40 | Replace = rb_sys::st_retval::ST_REPLACE as isize, 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014 Alex Crichton 4 | Copyright (c) 2015-2016 Dmitry Gritsay 5 | Copyright (c) 2017-2025 Daniel P. Clark 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rutie" 3 | version = "0.10.0" 4 | authors = [ 5 | "Steve Klabnik ", 6 | "Dmitry Gritsay ", 7 | "Daniel P. Clark <6ftdan@gmail.com>", 8 | "Jose Narvaez ", 9 | ] 10 | edition = "2021" 11 | description = "The tie between Ruby and Rust." 12 | repository = "https://github.com/danielpclark/rutie" 13 | readme = "README.md" 14 | keywords = ["cruby", "ruby", "rutie"] 15 | license = "MIT" 16 | build = "build.rs" 17 | 18 | [features] 19 | link-ruby = ["rb-sys/link-ruby"] 20 | ruby-static = ["rb-sys/ruby-static"] 21 | no-link = [] # noop, for backwards compatibility. 22 | 23 | [dependencies] 24 | libc = "0.2.169" 25 | rb-sys = { version = "0.9.110", features = ["stable-api-compiled-fallback"] } 26 | rb-sys-env = "0.2.2" 27 | 28 | [dev-dependencies] 29 | rutie = { path = ".", features = ["link-ruby"] } 30 | lazy_static = "1.4.0" 31 | rb-sys-test-helpers = "0.2.0" 32 | 33 | [build-dependencies] 34 | rb-sys-env = "0.1" 35 | 36 | [badges] 37 | travis-ci = { repository = "danielpclark/rutie", branch = "master" } 38 | maintenance = { status = "actively-developed" } 39 | 40 | [lints.rust] 41 | unexpected_cfgs = { level = "allow", check-cfg = ['cfg(ruby_gte_3_0)', 'cfg(ruby_lte_3_1)'] } 42 | -------------------------------------------------------------------------------- /examples/rutie_ruby_example/rutie_ruby_example.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'rutie_ruby_example/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'rutie_ruby_example' 9 | spec.version = RutieRubyExample::VERSION 10 | spec.authors = ['Daniel P. Clark'] 11 | spec.email = ['6ftdan@gmail.com'] 12 | 13 | spec.summary = 'asdfq r' 14 | spec.description = 'asdf' 15 | spec.homepage = 'https://example.com' 16 | spec.license = 'MIT' 17 | 18 | # Specify which files should be added to the gem when it is released. 19 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 20 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 21 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 22 | end 23 | spec.bindir = 'exe' 24 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 25 | spec.require_paths = ['lib'] 26 | 27 | spec.add_dependency 'rutie', '~> 0.0.4' 28 | spec.add_development_dependency 'bundler' 29 | spec.add_development_dependency 'minitest', '~> 5.0' 30 | spec.add_development_dependency 'rake', '~> 12.0' 31 | end 32 | -------------------------------------------------------------------------------- /examples/rutie_ruby_gvl_example/rutie_ruby_gvl_example.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'rutie_ruby_gvl_example/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'rutie_ruby_gvl_example' 9 | spec.version = RutieRubyGvlExample::VERSION 10 | spec.authors = ['Daniel P. Clark'] 11 | spec.email = ['6ftdan@gmail.com'] 12 | 13 | spec.summary = 'asdfq r' 14 | spec.description = 'asdf' 15 | spec.homepage = 'https://example.com' 16 | spec.license = 'MIT' 17 | 18 | # Specify which files should be added to the gem when it is released. 19 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 20 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 21 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 22 | end 23 | spec.bindir = 'exe' 24 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 25 | spec.require_paths = ['lib'] 26 | 27 | spec.add_dependency 'rutie', '~> 0.0.4' 28 | spec.add_development_dependency 'bundler' 29 | spec.add_development_dependency 'minitest', '~> 5.0' 30 | spec.add_development_dependency 'rake', '~> 12.0' 31 | end 32 | -------------------------------------------------------------------------------- /src/binding/fixnum.rs: -------------------------------------------------------------------------------- 1 | use crate::{rubysys::fixnum, types::Value}; 2 | 3 | pub fn i32_to_num(num: i32) -> Value { 4 | unsafe { fixnum::rb_int2inum(num as isize) } 5 | } 6 | 7 | pub fn u32_to_num(num: u32) -> Value { 8 | unsafe { fixnum::rb_uint2inum(num as usize) } 9 | } 10 | 11 | #[allow(dead_code)] 12 | pub fn isize_to_num(num: isize) -> Value { 13 | unsafe { fixnum::rb_int2inum(num) } 14 | } 15 | 16 | #[allow(dead_code)] 17 | pub fn usize_to_num(num: usize) -> Value { 18 | unsafe { fixnum::rb_uint2inum(num) } 19 | } 20 | 21 | pub fn i64_to_num(num: i64) -> Value { 22 | unsafe { fixnum::rb_ll2inum(num) } 23 | } 24 | 25 | pub fn u64_to_num(num: u64) -> Value { 26 | unsafe { fixnum::rb_ull2inum(num) } 27 | } 28 | 29 | pub fn num_to_i32(num: Value) -> i32 { 30 | unsafe { fixnum::rb_num2int(num) as i32 } 31 | } 32 | 33 | pub fn num_to_u32(num: Value) -> u32 { 34 | unsafe { fixnum::rb_num2long(num) as u32 } 35 | } 36 | 37 | #[allow(dead_code)] 38 | pub fn num_to_isize(num: Value) -> isize { 39 | unsafe { fixnum::rb_num2long(num) as isize } 40 | } 41 | 42 | #[allow(dead_code)] 43 | pub fn num_to_usize(num: Value) -> usize { 44 | unsafe { fixnum::rb_num2ulong(num) as usize } 45 | } 46 | 47 | pub fn num_to_i64(num: Value) -> i64 { 48 | unsafe { fixnum::rb_num2ll(num) } 49 | } 50 | 51 | pub fn num_to_u64(num: Value) -> u64 { 52 | unsafe { fixnum::rb_num2ull(num) } 53 | } 54 | -------------------------------------------------------------------------------- /src/binding/gc.rs: -------------------------------------------------------------------------------- 1 | use crate::{rubysys::gc, types::Value, util}; 2 | 3 | pub fn adjust_memory_usage(diff: isize) { 4 | unsafe { gc::rb_gc_adjust_memory_usage(diff) }; 5 | } 6 | 7 | pub fn count() -> usize { 8 | unsafe { gc::rb_gc_count() } 9 | } 10 | 11 | pub fn disable() -> Value { 12 | unsafe { gc::rb_gc_disable() } 13 | } 14 | 15 | pub fn enable() -> Value { 16 | unsafe { gc::rb_gc_enable() } 17 | } 18 | 19 | pub fn force_recycle(obj: Value) { 20 | unsafe { gc::rb_gc_force_recycle(obj) } 21 | } 22 | 23 | pub fn mark(value: Value) { 24 | unsafe { gc::rb_gc_mark(value) }; 25 | } 26 | 27 | pub fn mark_maybe(value: Value) { 28 | unsafe { gc::rb_gc_mark_maybe(value) }; 29 | } 30 | 31 | pub fn register(obj: Value) { 32 | let addr = &obj as *const _ as *mut _; 33 | 34 | unsafe { gc::rb_gc_register_address(addr) } 35 | } 36 | 37 | pub fn register_mark(obj: Value) { 38 | unsafe { gc::rb_gc_register_mark_object(obj) } 39 | } 40 | 41 | pub fn start() { 42 | unsafe { gc::rb_gc_start() }; 43 | } 44 | 45 | pub fn stat(key: Value) -> usize { 46 | unsafe { gc::rb_gc_stat(key) } 47 | } 48 | 49 | pub fn unregister(obj: Value) { 50 | let addr = &obj as *const _ as *mut _; 51 | 52 | unsafe { gc::rb_gc_unregister_address(addr) } 53 | } 54 | 55 | pub unsafe fn is_marked(obj: Value) -> bool { 56 | let int = gc::rb_objspace_marked_object_p(obj); 57 | 58 | util::c_int_to_bool(int) 59 | } 60 | -------------------------------------------------------------------------------- /src/rubysys/fixnum.rs: -------------------------------------------------------------------------------- 1 | use super::types::Value; 2 | 3 | extern "C" { 4 | // VALUE 5 | // rb_int2inum(intptr_t n) 6 | pub fn rb_int2inum(num: libc::intptr_t) -> Value; 7 | // VALUE 8 | // rb_uint2inum(uintptr_t n) 9 | pub fn rb_uint2inum(num: libc::uintptr_t) -> Value; 10 | // VALUE 11 | // rb_ll2inum(LONG_LONG n) 12 | pub fn rb_ll2inum(num: libc::c_longlong) -> Value; 13 | // VALUE 14 | // rb_ull2inum(unsigned LONG_LONG n) 15 | pub fn rb_ull2inum(num: libc::c_ulonglong) -> Value; 16 | // short 17 | // rb_num2short(VALUE val) 18 | pub fn rb_num2short(num: Value) -> libc::c_short; 19 | // unsigned short 20 | // rb_num2ushort(VALUE val) 21 | pub fn rb_num2ushort(num: Value) -> libc::c_ushort; 22 | // long 23 | // rb_num2int(VALUE val) 24 | pub fn rb_num2int(num: Value) -> libc::c_long; 25 | // unsigned long 26 | // rb_num2uint(VALUE val) 27 | pub fn rb_num2uint(num: Value) -> libc::c_ulong; 28 | // long 29 | // rb_num2long(VALUE val) 30 | pub fn rb_num2long(num: Value) -> libc::c_long; 31 | // unsigned long 32 | // rb_num2ulong(VALUE val) 33 | pub fn rb_num2ulong(num: Value) -> libc::c_ulong; 34 | // LONG_LONG 35 | // rb_num2ll(VALUE val) 36 | pub fn rb_num2ll(num: Value) -> libc::c_longlong; 37 | // unsigned LONG_LONG 38 | // rb_num2ull(VALUE val) 39 | pub fn rb_num2ull(num: Value) -> libc::c_ulonglong; 40 | } 41 | -------------------------------------------------------------------------------- /src/binding/hash.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | binding::fixnum, 3 | rubysys::hash, 4 | types::{CallbackMutPtr, CallbackPtr, Value}, 5 | AnyObject, 6 | }; 7 | 8 | pub fn new() -> Value { 9 | unsafe { hash::rb_hash_new() } 10 | } 11 | 12 | pub fn aref(hash: Value, key: Value) -> Value { 13 | unsafe { hash::rb_hash_aref(hash, key) } 14 | } 15 | 16 | pub fn aset(hash: Value, key: Value, value: Value) -> Value { 17 | unsafe { hash::rb_hash_aset(hash, key, value) } 18 | } 19 | 20 | pub fn clear(hash: Value) { 21 | let _ = unsafe { hash::rb_hash_clear(hash) }; 22 | } 23 | 24 | pub fn delete(hash: Value, key: Value) -> Value { 25 | unsafe { hash::rb_hash_delete(hash, key) } 26 | } 27 | 28 | pub fn dup(hash: Value) -> Value { 29 | unsafe { hash::rb_hash_dup(hash) } 30 | } 31 | 32 | pub fn length(hash: Value) -> i64 { 33 | unsafe { 34 | let size = hash::rb_hash_size(hash); 35 | 36 | fixnum::num_to_i64(size) 37 | } 38 | } 39 | 40 | use crate::util::callback_call::hash_foreach_callback as each_callback; 41 | 42 | pub fn each(hash: Value, closure_callback: F) 43 | where 44 | F: FnMut(AnyObject, AnyObject), 45 | { 46 | let closure_ptr = &closure_callback as *const _ as CallbackMutPtr; 47 | 48 | unsafe { 49 | hash::rb_hash_foreach( 50 | hash, 51 | each_callback:: as CallbackPtr, 52 | closure_ptr, 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/binding/module.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | binding::{class as binding_class, global::rb_cObject}, 3 | rubysys::class, 4 | types::{Callback, Value, CallbackPtr}, 5 | util, Object, 6 | }; 7 | 8 | pub fn define_module(name: &str) -> Value { 9 | let name = util::str_to_cstring(name); 10 | 11 | unsafe { class::rb_define_module(name.as_ptr()) } 12 | } 13 | 14 | pub fn define_nested_module(outer: Value, name: &str) -> Value { 15 | let name = util::str_to_cstring(name); 16 | 17 | unsafe { class::rb_define_module_under(outer, name.as_ptr()) } 18 | } 19 | 20 | pub fn define_module_function( 21 | klass: Value, 22 | name: &str, 23 | callback: Callback, 24 | ) { 25 | let name = util::str_to_cstring(name); 26 | 27 | unsafe { 28 | class::rb_define_module_function(klass, name.as_ptr(), callback as CallbackPtr, -1); 29 | } 30 | } 31 | 32 | pub fn include_module(klass: Value, module: &str) { 33 | let object_module = unsafe { rb_cObject }; 34 | 35 | let module_value = binding_class::const_get(object_module.into(), module); 36 | 37 | unsafe { class::rb_include_module(klass, module_value) }; 38 | } 39 | 40 | pub fn prepend_module(klass: Value, module: &str) { 41 | let object_module = unsafe { rb_cObject }; 42 | 43 | let module_value = binding_class::const_get(object_module.into(), module); 44 | 45 | unsafe { class::rb_prepend_module(klass, module_value) }; 46 | } 47 | -------------------------------------------------------------------------------- /src/rubysys/gc.rs: -------------------------------------------------------------------------------- 1 | use super::types::{c_int, size_t, ssize_t, CallbackPtr, Value}; 2 | 3 | extern "C" { 4 | // void 5 | // rb_gc_adjust_memory_usage(ssize_t diff) 6 | pub fn rb_gc_adjust_memory_usage(diff: ssize_t); 7 | // size_t 8 | // rb_gc_count(void) 9 | pub fn rb_gc_count() -> size_t; 10 | // VALUE 11 | // rb_gc_disable(void) 12 | pub fn rb_gc_disable() -> Value; 13 | // VALUE 14 | // rb_gc_enable(void) 15 | pub fn rb_gc_enable() -> Value; 16 | // void 17 | // rb_gc_force_recycle(VALUE obj) 18 | pub fn rb_gc_force_recycle(obj: Value); 19 | // void 20 | // rb_gc_mark(VALUE ptr) 21 | pub fn rb_gc_mark(value: Value); 22 | // void 23 | // rb_gc_mark_maybe(VALUE obj) 24 | pub fn rb_gc_mark_maybe(obj: Value); 25 | // void 26 | // rb_gc_register_address(VALUE *addr) 27 | pub fn rb_gc_register_address(addr: CallbackPtr); 28 | // void 29 | // rb_gc_register_mark_object(VALUE obj) 30 | pub fn rb_gc_register_mark_object(obj: Value); 31 | // VALUE 32 | // rb_gc_start(void) 33 | pub fn rb_gc_start() -> Value; 34 | // size_t 35 | // rb_gc_stat(VALUE key) 36 | pub fn rb_gc_stat(key: Value) -> size_t; 37 | // void 38 | // rb_gc_unregister_address(VALUE *addr) 39 | pub fn rb_gc_unregister_address(addr: CallbackPtr); 40 | // int 41 | // rb_objspace_marked_object_p(VALUE obj) 42 | pub fn rb_objspace_marked_object_p(obj: Value) -> c_int; 43 | } 44 | -------------------------------------------------------------------------------- /src/rubysys/rproc.rs: -------------------------------------------------------------------------------- 1 | use crate::{AnyException, Exception}; 2 | 3 | use super::{ 4 | constant::UNLIMITED_ARGUMENTS, 5 | types::{c_int, Argc, Value}, 6 | }; 7 | 8 | // pub use rb_sys::{rb_binding_new, rb_obj_is_method, rb_obj_is_proc, rb_proc_call_with_block}; 9 | extern "C" { 10 | // VALUE 11 | // rb_proc_call_with_block(VALUE self, int argc, const VALUE *argv, VALUE passed_procval) 12 | pub fn rb_proc_call_with_block( 13 | rproc: Value, 14 | argc: Argc, 15 | argv: *const Value, 16 | pass_procval: Value, 17 | ) -> Value; 18 | // VALUE 19 | // rb_binding_new(void) 20 | pub fn rb_binding_new() -> Value; 21 | pub fn rb_obj_is_proc(obj: Value) -> Value; 22 | pub fn rb_obj_is_method(obj: Value) -> Value; 23 | } 24 | 25 | pub fn check_arity(argc: c_int, min: c_int, max: c_int) -> Result { 26 | if argc < min || (max != UNLIMITED_ARGUMENTS as c_int && argc > max) { 27 | let err_mess = if min == max { 28 | format!( 29 | "wrong number of arguments (given {}, expected {})", 30 | argc, min 31 | ) 32 | } else if max == UNLIMITED_ARGUMENTS as c_int { 33 | format!( 34 | "wrong number of arguments (given {}, expected {}+)", 35 | argc, min 36 | ) 37 | } else { 38 | format!( 39 | "wrong number of arguments (given {}, expected {}..{})", 40 | argc, min, max 41 | ) 42 | }; 43 | 44 | return Err(AnyException::new("ArgumentError", Some(&err_mess))); 45 | } 46 | 47 | Ok(argc) 48 | } 49 | -------------------------------------------------------------------------------- /src/helpers/codepoint_iterator.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | binding::{encoding, string}, 3 | rubysys::string::{rstring_end, rstring_ptr}, 4 | types::{c_char, c_int}, 5 | EncodingSupport, Object, RString, 6 | }; 7 | 8 | /// `CodepointIterator` 9 | #[derive(Debug)] 10 | pub struct CodepointIterator { 11 | rstring: RString, 12 | ptr: *const c_char, 13 | } 14 | 15 | impl CodepointIterator { 16 | /// Create new codepoint iterator 17 | /// 18 | /// ``` 19 | /// use rutie::{RString, VM, CodepointIterator}; 20 | /// # VM::init(); 21 | /// 22 | /// let string = RString::new_utf8("aeiou"); 23 | /// let ci = CodepointIterator::new(&string); 24 | /// 25 | /// let result: Vec = ci.into_iter().collect(); 26 | /// 27 | /// assert_eq!(vec![97, 101, 105, 111, 117], result); 28 | /// ``` 29 | pub fn new(rstring: &RString) -> Self { 30 | let fstring = string::new_frozen(rstring.value()); 31 | 32 | CodepointIterator { 33 | rstring: RString::from(fstring), 34 | ptr: unsafe { rstring_ptr(fstring) }, 35 | } 36 | } 37 | } 38 | 39 | impl Iterator for CodepointIterator { 40 | type Item = usize; 41 | 42 | fn next(&mut self) -> Option { 43 | let mut n: c_int = 0; 44 | 45 | let ptr = self.ptr; 46 | let end = unsafe { rstring_end(self.rstring.value()) }; 47 | let enc = self.rstring.encoding(); 48 | 49 | if ptr < end { 50 | let result = Some(encoding::next_codepoint(ptr, end, &mut n, enc.value())); 51 | self.ptr = unsafe { ptr.add(n as usize) }; 52 | result 53 | } else { 54 | None 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/class/binding.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | 3 | use crate::{binding::rproc, types::Value, AnyObject, Class, Object, VerifiedObject}; 4 | 5 | /// `Integer` 6 | #[derive(Debug)] 7 | pub struct Binding { 8 | value: Value, 9 | } 10 | 11 | impl Binding { 12 | /// Creates a new `Binding`. 13 | /// 14 | /// # Examples 15 | /// 16 | /// ``` 17 | /// use rutie::{Binding, VM}; 18 | /// # VM::init(); 19 | /// 20 | /// let _ = Binding::new(); 21 | /// ``` 22 | /// 23 | /// Ruby: 24 | /// 25 | /// ```ruby 26 | /// binding 27 | /// ``` 28 | pub fn new() -> Self { 29 | Binding { 30 | value: rproc::binding_new(), 31 | } 32 | } 33 | } 34 | 35 | impl Default for Binding { 36 | fn default() -> Self { 37 | Self::new() 38 | } 39 | } 40 | 41 | impl From for Binding { 42 | fn from(value: Value) -> Self { 43 | Binding { value } 44 | } 45 | } 46 | 47 | impl From for Value { 48 | fn from(val: Binding) -> Self { 49 | val.value 50 | } 51 | } 52 | 53 | impl From for AnyObject { 54 | fn from(val: Binding) -> Self { 55 | AnyObject::from(val.value) 56 | } 57 | } 58 | 59 | impl Object for Binding { 60 | #[inline] 61 | fn value(&self) -> Value { 62 | self.value 63 | } 64 | } 65 | 66 | impl VerifiedObject for Binding { 67 | fn is_correct_type(object: &T) -> bool { 68 | Class::from_existing("Binding").case_equals(object) 69 | } 70 | 71 | fn error_message() -> &'static str { 72 | "Error converting to Integer" 73 | } 74 | } 75 | 76 | impl PartialEq for Binding { 77 | fn eq(&self, other: &Self) -> bool { 78 | self.equals(other) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/rubysys/constant.rs: -------------------------------------------------------------------------------- 1 | use super::value::ValueType; 2 | 3 | pub const FL_WB_PROTECTED: isize = 1 << 5; 4 | pub const FL_PROMOTED0: isize = 1 << 5; 5 | pub const FL_PROMOTED1: isize = 1 << 6; 6 | pub const FL_PROMOTED: isize = FL_PROMOTED0 | FL_PROMOTED1; 7 | pub const FL_FINALIZE: isize = 1 << 7; 8 | pub const FL_TAINT: isize = 1 << 8; 9 | pub const FL_UNTRUSTED: isize = FL_TAINT; 10 | pub const FL_EXIVAR: isize = 1 << 10; 11 | pub const FL_FREEZE: isize = 1 << 11; 12 | 13 | pub const FL_USHIFT: isize = 12; 14 | 15 | pub const FL_USER_0: isize = 1 << FL_USHIFT; 16 | pub const FL_USER_1: isize = 1 << (FL_USHIFT + 1); 17 | pub const FL_USER_2: isize = 1 << (FL_USHIFT + 2); 18 | pub const FL_USER_3: isize = 1 << (FL_USHIFT + 3); 19 | pub const FL_USER_4: isize = 1 << (FL_USHIFT + 4); 20 | pub const FL_USER_5: isize = 1 << (FL_USHIFT + 5); 21 | pub const FL_USER_6: isize = 1 << (FL_USHIFT + 6); 22 | pub const FL_USER_7: isize = 1 << (FL_USHIFT + 7); 23 | pub const FL_USER_8: isize = 1 << (FL_USHIFT + 8); 24 | pub const FL_USER_9: isize = 1 << (FL_USHIFT + 9); 25 | pub const FL_USER_10: isize = 1 << (FL_USHIFT + 10); 26 | pub const FL_USER_11: isize = 1 << (FL_USHIFT + 11); 27 | pub const FL_USER_12: isize = 1 << (FL_USHIFT + 12); 28 | pub const FL_USER_13: isize = 1 << (FL_USHIFT + 13); 29 | pub const FL_USER_14: isize = 1 << (FL_USHIFT + 14); 30 | pub const FL_USER_15: isize = 1 << (FL_USHIFT + 15); 31 | pub const FL_USER_16: isize = 1 << (FL_USHIFT + 16); 32 | pub const FL_USER_17: isize = 1 << (FL_USHIFT + 17); 33 | pub const FL_USER_18: isize = 1 << (FL_USHIFT + 18); 34 | 35 | pub const ELTS_SHARED: isize = FL_USER_2; 36 | pub const FL_DUPPED: isize = ValueType::Mask as isize | FL_EXIVAR | FL_TAINT; 37 | pub const FL_SINGLETON: isize = FL_USER_0; 38 | 39 | pub const UNLIMITED_ARGUMENTS: isize = -1; 40 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Just doing FFI no special safety considerations. 2 | #![allow(clippy::missing_safety_doc)] 3 | 4 | mod binding; 5 | mod class; 6 | mod helpers; 7 | pub mod rubysys; 8 | 9 | #[macro_use] 10 | pub mod dsl; 11 | 12 | pub mod typed_data; 13 | pub mod types; 14 | pub mod util; 15 | 16 | pub use crate::class::{ 17 | any_exception::AnyException, any_object::AnyObject, array::Array, binding::Binding, 18 | boolean::Boolean, class::Class, encoding::Encoding, enumerator::Enumerator, fixnum::Fixnum, 19 | float::Float, gc::GC, hash::Hash, integer::Integer, module::Module, nil_class::NilClass, 20 | rproc::Proc, string::RString, symbol::Symbol, thread::Thread, vm::VM, 21 | }; 22 | 23 | pub use crate::class::traits::{ 24 | encoding_support::EncodingSupport, exception::Exception, object::Object, 25 | try_convert::TryConvert, verified_object::VerifiedObject, 26 | }; 27 | 28 | pub use crate::helpers::codepoint_iterator::CodepointIterator; 29 | 30 | #[cfg(test)] 31 | mod current_ruby { 32 | use super::{Object, RString, VM}; 33 | use rb_sys_test_helpers::ruby_test; 34 | use std::process::Command; 35 | 36 | #[ruby_test] 37 | fn is_linked_ruby() { 38 | let rv = RString::from(VM::eval("RUBY_VERSION").unwrap().value()).to_string(); 39 | let output = Command::new("ruby") 40 | .arg("-e") 41 | .arg("printf RUBY_VERSION") 42 | .output() 43 | .unwrap() 44 | .stdout; 45 | let crv = String::from_utf8_lossy(&output); 46 | 47 | assert_eq!( 48 | rv, crv, 49 | "\nCurrent console Ruby is version {} but the \ 50 | linked Ruby is version {} \ 51 | Please run `cargo clean` first to remove previously used symbolic link in \ 52 | the dependency directory.", 53 | crv, rv 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/class/nil_class.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::From, default::Default}; 2 | 3 | use crate::{ 4 | binding::global::RubySpecialConsts, 5 | types::{InternalValue, Value, ValueType}, 6 | AnyObject, Object, VerifiedObject, 7 | }; 8 | 9 | /// `NilClass` 10 | #[derive(Debug, Copy, Clone)] 11 | #[repr(C)] 12 | pub struct NilClass { 13 | value: Value, 14 | } 15 | 16 | impl NilClass { 17 | /// Creates a new instance of `NilClass` (`nil`). 18 | /// 19 | /// # Examples 20 | /// 21 | /// ``` 22 | /// use rutie::{NilClass, Object, VM}; 23 | /// # VM::init(); 24 | /// 25 | /// assert!(NilClass::new().is_nil()); 26 | /// ``` 27 | /// 28 | /// Ruby: 29 | /// 30 | /// ```ruby 31 | /// nil.nil? == true 32 | /// ``` 33 | pub fn new() -> Self { 34 | Self::from(Value::from(RubySpecialConsts::Nil as InternalValue)) 35 | } 36 | } 37 | 38 | impl Default for NilClass { 39 | fn default() -> Self { 40 | NilClass::new() 41 | } 42 | } 43 | 44 | impl From for NilClass { 45 | fn from(value: Value) -> Self { 46 | NilClass { value } 47 | } 48 | } 49 | 50 | impl From for Value { 51 | fn from(val: NilClass) -> Self { 52 | val.value 53 | } 54 | } 55 | 56 | impl From for AnyObject { 57 | fn from(val: NilClass) -> Self { 58 | AnyObject::from(val.value) 59 | } 60 | } 61 | 62 | impl Object for NilClass { 63 | #[inline] 64 | fn value(&self) -> Value { 65 | self.value 66 | } 67 | } 68 | 69 | impl VerifiedObject for NilClass { 70 | fn is_correct_type(object: &T) -> bool { 71 | object.value().ty() == ValueType::Nil 72 | } 73 | 74 | fn error_message() -> &'static str { 75 | "Error converting to NilClass" 76 | } 77 | } 78 | 79 | impl PartialEq for NilClass { 80 | fn eq(&self, other: &Self) -> bool { 81 | self.equals(other) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/binding/array.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | rubysys::array, 3 | types::{c_long, Value}, 4 | }; 5 | 6 | pub fn new() -> Value { 7 | unsafe { array::rb_ary_new() } 8 | } 9 | 10 | pub fn with_capacity(capacity: usize) -> Value { 11 | unsafe { array::rb_ary_new_capa(capacity as c_long) } 12 | } 13 | 14 | pub fn entry(array: Value, offset: i64) -> Value { 15 | unsafe { array::rb_ary_entry(array, offset as c_long) } 16 | } 17 | 18 | pub fn join(array: Value, separator: Value) -> Value { 19 | unsafe { array::rb_ary_join(array, separator) } 20 | } 21 | 22 | #[allow(clippy::useless_conversion)] // For w64-mingw32 rb_ary_len returns i32 23 | pub fn len(array: Value) -> i64 { 24 | unsafe { array::rb_ary_len(array).into() } 25 | } 26 | 27 | pub fn push(array: Value, item: Value) -> Value { 28 | unsafe { array::rb_ary_push(array, item) } 29 | } 30 | 31 | pub fn store(array: Value, offset: i64, item: Value) { 32 | unsafe { array::rb_ary_store(array, offset as c_long, item) } 33 | } 34 | 35 | pub fn pop(array: Value) -> Value { 36 | unsafe { array::rb_ary_pop(array) } 37 | } 38 | 39 | pub fn unshift(array: Value, item: Value) -> Value { 40 | unsafe { array::rb_ary_unshift(array, item) } 41 | } 42 | 43 | pub fn shift(array: Value) -> Value { 44 | unsafe { array::rb_ary_shift(array) } 45 | } 46 | 47 | pub fn dup(array: Value) -> Value { 48 | unsafe { array::rb_ary_dup(array) } 49 | } 50 | 51 | pub fn to_s(array: Value) -> Value { 52 | unsafe { array::rb_ary_to_s(array) } 53 | } 54 | 55 | pub fn reverse_bang(array: Value) -> Value { 56 | unsafe { array::rb_ary_reverse(array) } 57 | } 58 | 59 | pub fn concat(array: Value, other_array: Value) -> Value { 60 | unsafe { array::rb_ary_concat(array, other_array) } 61 | } 62 | 63 | pub fn sort(array: Value) -> Value { 64 | unsafe { array::rb_ary_sort(array) } 65 | } 66 | 67 | pub fn sort_bang(array: Value) -> Value { 68 | unsafe { array::rb_ary_sort_bang(array) } 69 | } 70 | -------------------------------------------------------------------------------- /src/rubysys/array.rs: -------------------------------------------------------------------------------- 1 | use super::types::{c_long, Value}; 2 | 3 | extern "C" { 4 | // VALUE 5 | // rb_ary_concat(VALUE x, VALUE y) 6 | pub fn rb_ary_concat(array: Value, other_array: Value) -> Value; 7 | // VALUE 8 | // rb_ary_dup(VALUE ary) 9 | pub fn rb_ary_dup(array: Value) -> Value; 10 | // VALUE 11 | // rb_ary_entry(VALUE ary, long offset) 12 | pub fn rb_ary_entry(array: Value, offset: c_long) -> Value; 13 | // VALUE 14 | // rb_ary_join(VALUE ary, VALUE sep) 15 | pub fn rb_ary_join(array: Value, separator: Value) -> Value; 16 | // VALUE 17 | // rb_ary_new(void) 18 | pub fn rb_ary_new() -> Value; 19 | // VALUE 20 | // rb_ary_new_from_values(long n, const VALUE *elts) 21 | pub fn rb_ary_new_from_values(count: c_long, elements: *const Value) -> Value; 22 | // VALUE 23 | // rb_ary_new_capa(long capa) 24 | pub fn rb_ary_new_capa(capacity: c_long) -> Value; 25 | // VALUE 26 | // rb_ary_pop(VALUE ary) 27 | pub fn rb_ary_pop(array: Value) -> Value; 28 | // VALUE 29 | // rb_ary_push(VALUE ary, VALUE item) 30 | pub fn rb_ary_push(array: Value, item: Value) -> Value; 31 | // VALUE 32 | // rb_ary_reverse(VALUE ary) 33 | pub fn rb_ary_reverse(array: Value) -> Value; 34 | // VALUE 35 | // rb_ary_shift(VALUE ary) 36 | pub fn rb_ary_shift(array: Value) -> Value; 37 | // VALUE 38 | // rb_ary_sort_bang(VALUE ary) 39 | pub fn rb_ary_sort_bang(array: Value) -> Value; 40 | // VALUE 41 | // rb_ary_sort(VALUE ary) 42 | pub fn rb_ary_sort(array: Value) -> Value; 43 | // void 44 | // rb_ary_store(VALUE ary, long idx, VALUE val) 45 | pub fn rb_ary_store(array: Value, index: c_long, item: Value); 46 | // VALUE 47 | // rb_ary_to_s(VALUE ary) 48 | pub fn rb_ary_to_s(array: Value) -> Value; 49 | // VALUE 50 | // rb_ary_unshift(VALUE ary, VALUE item) 51 | pub fn rb_ary_unshift(array: Value, item: Value) -> Value; 52 | } 53 | 54 | pub unsafe fn rb_ary_len(value: Value) -> c_long { 55 | rb_sys::RARRAY_LEN(value) as c_long 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - "**.md" 9 | pull_request: 10 | branches: 11 | - master 12 | paths-ignore: 13 | - "**.md" 14 | 15 | env: 16 | TEST_ALL: false # Set this to true for testing the entire matrix 17 | 18 | jobs: 19 | ci: 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | os: ["ubuntu-latest", "macos-latest", "windows-latest"] 25 | ruby: ["2.7", "3.0", "3.1", "3.2", "3.3", "3.4"] 26 | rust: ["stable"] 27 | include: 28 | - os: windows-latest 29 | ruby-version: mingw 30 | rustup-toolchain: stable 31 | ruby_static: true 32 | env: 33 | RUST_BACKTRACE: full 34 | CI_STDERR_LOG: true 35 | BUILD_RUBY_VERSION: ${{ matrix.ruby_version }} 36 | steps: 37 | - uses: actions/checkout@v3 38 | 39 | - uses: oxidize-rb/actions/setup-ruby-and-rust@v1 40 | with: 41 | ruby-version: ${{ matrix.ruby }} 42 | rustup-toolchain: ${{ matrix.rust }} 43 | cache-version: v2 44 | bundler-cache: true 45 | cargo-cache: true 46 | cargo-cache-extra-path: | 47 | examples/rutie_ruby_example/tmp/ 48 | examples/rutie_ruby_gvl_example/tmp/ 49 | examples/rutie_rust_example/tmp/ 50 | 51 | - name: Run Rust crate tests 52 | run: cargo test 53 | 54 | - name: Run Clippy on Rust crate 55 | run: cargo clippy 56 | 57 | - name: Run Ruby gem tests 58 | run: | 59 | git submodule update --init 60 | cd gem && rake test 61 | 62 | - name: Example tests (rutie_ruby_example) 63 | working-directory: examples/rutie_ruby_example 64 | run: bundle && bundle exec rake test 65 | 66 | - name: Example tests (rutie_ruby_gvl_example) 67 | if: matrix.os != 'windows-latest' 68 | working-directory: examples/rutie_ruby_gvl_example 69 | run: bundle && bundle exec rake test 70 | 71 | - name: Example tests (rutie_rust_example) 72 | working-directory: examples/rutie_rust_example 73 | run: cargo test 74 | -------------------------------------------------------------------------------- /src/binding/encoding.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | rubysys::encoding, 3 | types::{c_char, c_int, EncodingIndex, Value, ValueType}, 4 | }; 5 | use std::ffi::CString; 6 | 7 | pub fn default_external() -> Value { 8 | unsafe { encoding::rb_enc_default_external() } 9 | } 10 | 11 | pub fn default_internal() -> Value { 12 | unsafe { encoding::rb_enc_default_internal() } 13 | } 14 | 15 | pub fn force_encoding(s: Value, enc: Value) -> Value { 16 | unsafe { encoding::rb_enc_associate(s, encoding::rb_to_encoding(enc)) } 17 | } 18 | 19 | pub fn coderange_clear(obj: Value) { 20 | unsafe { encoding::coderange_clear(obj) } 21 | } 22 | 23 | // best str1/str2 encoding or nil if incompatible 24 | pub fn compatible_encoding(str1: Value, str2: Value) -> Value { 25 | unsafe { encoding::rb_enc_from_encoding(encoding::rb_enc_compatible(str1, str2)) } 26 | } 27 | 28 | pub fn is_compatible_encoding(str1: Value, str2: Value) -> bool { 29 | compatible_encoding(str1, str2).ty() != ValueType::Nil 30 | } 31 | 32 | pub fn from_encoding_index(idx: EncodingIndex) -> Value { 33 | unsafe { encoding::rb_enc_from_encoding(encoding::rb_enc_from_index(idx)) } 34 | } 35 | 36 | pub fn usascii_encoding() -> Value { 37 | unsafe { from_encoding_index(encoding::rb_usascii_encindex()) } 38 | } 39 | 40 | pub fn utf8_encoding() -> Value { 41 | unsafe { from_encoding_index(encoding::rb_utf8_encindex()) } 42 | } 43 | 44 | pub fn enc_get_index(s: Value) -> EncodingIndex { 45 | unsafe { encoding::rb_enc_get_index(s) } 46 | } 47 | 48 | pub fn find_encoding_index(name: &str) -> EncodingIndex { 49 | let cstr = CString::new(name).unwrap(); 50 | unsafe { encoding::rb_enc_find_index(cstr.as_ptr()) } 51 | } 52 | 53 | pub fn encode(str: Value, to: Value, ecflags: c_int, ecopts: Value) -> Value { 54 | unsafe { encoding::rb_str_encode(str, to, ecflags, ecopts) } 55 | } 56 | 57 | pub fn econv_prepare_opts(opthash: Value, opts: *mut Value) -> c_int { 58 | unsafe { encoding::rb_econv_prepare_opts(opthash, opts as *mut _) } 59 | } 60 | 61 | // ptr - pointer for current point in string starting from the beginning 62 | // end - pointer for the end of the string 63 | // len_p - a mutable integer pointer for Ruby to give us how much we need to add on to `ptr` 64 | // enc - the encoding the codepoints will be based on 65 | pub fn next_codepoint( 66 | ptr: *const c_char, 67 | end: *const c_char, 68 | len_p: *mut c_int, 69 | enc: Value, 70 | ) -> usize { 71 | unsafe { encoding::rb_enc_codepoint_len(ptr, end, len_p, encoding::rb_to_encoding(enc)) } 72 | } 73 | -------------------------------------------------------------------------------- /src/class/boolean.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | 3 | use crate::{ 4 | types::{InternalValue, Value}, 5 | util, AnyObject, Object, VerifiedObject, 6 | }; 7 | 8 | /// `TrueClass` and `FalseClass` 9 | #[derive(Debug)] 10 | #[repr(C)] 11 | pub struct Boolean { 12 | value: Value, 13 | } 14 | 15 | impl Boolean { 16 | /// Creates a new instance boolean value from `bool`. 17 | /// 18 | /// # Examples 19 | /// 20 | /// ``` 21 | /// use rutie::{Boolean, VM}; 22 | /// # VM::init(); 23 | /// 24 | /// assert_eq!(Boolean::new(true).to_bool(), true); 25 | /// ``` 26 | /// 27 | /// Ruby: 28 | /// 29 | /// ```ruby 30 | /// true == true 31 | /// ``` 32 | pub fn new(state: bool) -> Self { 33 | Self::from(util::bool_to_value(state)) 34 | } 35 | 36 | /// Retrieves a `bool` value from `Boolean`. 37 | /// 38 | /// # Examples 39 | /// 40 | /// ``` 41 | /// use rutie::{Boolean, VM}; 42 | /// # VM::init(); 43 | /// 44 | /// assert_eq!(Boolean::new(true).to_bool(), true); 45 | /// ``` 46 | /// 47 | /// Ruby: 48 | /// 49 | /// ```ruby 50 | /// true == true 51 | /// ``` 52 | pub fn to_bool(&self) -> bool { 53 | self.value().is_true() 54 | } 55 | } 56 | 57 | impl From for Boolean { 58 | fn from(value: Value) -> Self { 59 | Boolean { value } 60 | } 61 | } 62 | 63 | impl From for Boolean { 64 | fn from(internal_value: InternalValue) -> Self { 65 | Boolean { 66 | value: Value::from(internal_value), 67 | } 68 | } 69 | } 70 | 71 | impl From for Value { 72 | fn from(val: Boolean) -> Self { 73 | val.value 74 | } 75 | } 76 | 77 | impl From for AnyObject { 78 | fn from(val: Boolean) -> Self { 79 | AnyObject::from(val.value) 80 | } 81 | } 82 | 83 | impl Object for Boolean { 84 | #[inline] 85 | fn value(&self) -> Value { 86 | self.value 87 | } 88 | } 89 | 90 | impl VerifiedObject for Boolean { 91 | fn is_correct_type(object: &T) -> bool { 92 | let value = object.value(); 93 | 94 | value.is_true() || value.is_false() 95 | } 96 | 97 | fn error_message() -> &'static str { 98 | "Error converting to Boolean" 99 | } 100 | } 101 | 102 | impl PartialEq for Boolean { 103 | fn eq(&self, other: &Self) -> bool { 104 | self.equals(other) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/class/any_exception.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | types::Value, AnyObject, Class, Exception, NilClass, Object, TryConvert, VerifiedObject, 3 | }; 4 | use std::{ 5 | borrow::Borrow, 6 | fmt, 7 | fmt::{Display, Formatter}, 8 | ops::Deref, 9 | }; 10 | 11 | pub struct AnyException { 12 | value: Value, 13 | } 14 | 15 | impl From for AnyException { 16 | fn from(value: Value) -> Self { 17 | AnyException { value } 18 | } 19 | } 20 | 21 | impl From for Value { 22 | fn from(val: AnyException) -> Self { 23 | val.value 24 | } 25 | } 26 | 27 | impl From for AnyObject { 28 | fn from(val: AnyException) -> Self { 29 | AnyObject::from(val.value) 30 | } 31 | } 32 | 33 | impl Borrow for AnyException { 34 | fn borrow(&self) -> &Value { 35 | &self.value 36 | } 37 | } 38 | 39 | impl AsRef for AnyException { 40 | fn as_ref(&self) -> &Value { 41 | &self.value 42 | } 43 | } 44 | 45 | impl AsRef for AnyException { 46 | #[inline] 47 | fn as_ref(&self) -> &Self { 48 | self 49 | } 50 | } 51 | 52 | impl Object for AnyException { 53 | #[inline] 54 | fn value(&self) -> Value { 55 | self.value 56 | } 57 | } 58 | 59 | impl Deref for AnyException { 60 | type Target = Value; 61 | 62 | fn deref(&self) -> &Value { 63 | &self.value 64 | } 65 | } 66 | 67 | impl Exception for AnyException {} 68 | 69 | impl TryConvert for AnyException { 70 | type Nil = NilClass; 71 | 72 | fn try_convert(obj: AnyObject) -> Result { 73 | obj.try_convert_to::() 74 | .map_err(|_| NilClass::new()) 75 | } 76 | } 77 | 78 | impl VerifiedObject for AnyException { 79 | fn is_correct_type(object: &T) -> bool { 80 | Class::from_existing("Exception").case_equals(object) 81 | } 82 | 83 | fn error_message() -> &'static str { 84 | "Error converting to AnyException" 85 | } 86 | } 87 | 88 | impl Display for AnyException { 89 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 90 | write!(f, "{}", self.inspect()) 91 | } 92 | } 93 | 94 | impl fmt::Debug for AnyException { 95 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 96 | write!(f, "{}", self.inspect()) 97 | } 98 | } 99 | 100 | impl PartialEq for AnyException { 101 | fn eq(&self, other: &Self) -> bool { 102 | self.equals(other) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/rubysys/typed_data.rs: -------------------------------------------------------------------------------- 1 | use super::types::{c_char, c_int, c_void, Value}; 2 | 3 | extern "C" { 4 | // void * 5 | // rb_check_typeddata(VALUE obj, const rb_data_type_t *data_type) 6 | pub fn rb_check_typeddata(object: Value, data_type: *const RbDataType) -> *mut c_void; 7 | // int 8 | // rb_typeddata_inherited_p(const rb_data_type_t *child, const rb_data_type_t *parent) 9 | pub fn rb_typeddata_inherited_p(child: *const RbDataType, parent: *const RbDataType) -> c_int; 10 | // int 11 | // rb_typeddata_is_kind_of(VALUE obj, const rb_data_type_t *data_type) 12 | pub fn rb_typeddata_is_kind_of(object: Value, data_type: *const RbDataType) -> c_int; 13 | // VALUE 14 | // rb_data_typed_object_wrap(VALUE klass, void *datap, const rb_data_type_t *type) 15 | pub fn rb_data_typed_object_wrap( 16 | klass: Value, 17 | data: *mut c_void, 18 | data_type: *const RbDataType, 19 | ) -> Value; 20 | } 21 | 22 | #[repr(C)] 23 | pub struct RbDataTypeFunction { 24 | pub dmark: Option, 25 | pub dfree: Option, 26 | pub dsize: Option u64>, 27 | pub compact: Option, 28 | pub reserved: [*mut c_void; 1], 29 | } 30 | 31 | impl Copy for RbDataTypeFunction {} 32 | impl Clone for RbDataTypeFunction { 33 | fn clone(&self) -> Self { 34 | *self 35 | } 36 | } 37 | unsafe impl Send for RbDataTypeFunction {} 38 | unsafe impl Sync for RbDataTypeFunction {} 39 | 40 | #[repr(C)] 41 | pub struct RbDataType { 42 | pub wrap_struct_name: *const c_char, 43 | pub function: RbDataTypeFunction, 44 | pub parent: *const RbDataType, 45 | pub data: *mut c_void, 46 | pub flags: Value, 47 | } 48 | 49 | unsafe impl Send for RbDataType {} 50 | unsafe impl Sync for RbDataType {} 51 | 52 | impl From for rb_sys::bindings::rb_data_type_struct__bindgen_ty_1 { 53 | fn from(rb_data_type_fn: RbDataTypeFunction) -> Self { 54 | rb_sys::bindings::rb_data_type_struct__bindgen_ty_1 { 55 | dmark: rb_data_type_fn.dmark, 56 | dfree: rb_data_type_fn.dfree, 57 | dsize: rb_data_type_fn.dsize, 58 | dcompact: rb_data_type_fn.compact, 59 | reserved: rb_data_type_fn.reserved, 60 | } 61 | } 62 | } 63 | 64 | impl From<&RbDataType> for rb_sys::rb_data_type_struct { 65 | fn from(rb_data_type: &RbDataType) -> Self { 66 | rb_sys::rb_data_type_struct { 67 | wrap_struct_name: rb_data_type.wrap_struct_name, 68 | function: rb_data_type.function.into(), 69 | parent: rb_data_type.parent as *const _, 70 | data: rb_data_type.data, 71 | flags: rb_data_type.flags.into(), 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/class/float.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | 3 | use crate::{ 4 | binding::float, 5 | types::{Value, ValueType}, 6 | AnyException, AnyObject, Object, VerifiedObject, 7 | }; 8 | 9 | /// `Float` 10 | #[derive(Debug)] 11 | #[repr(C)] 12 | pub struct Float { 13 | value: Value, 14 | } 15 | 16 | impl Float { 17 | /// Creates a new `Float`. 18 | /// 19 | /// # Examples 20 | /// 21 | /// ``` 22 | /// use rutie::{Float, VM}; 23 | /// # VM::init(); 24 | /// 25 | /// let float = Float::new(1.23); 26 | /// 27 | /// assert_eq!(float.to_f64(), 1.23); 28 | /// ``` 29 | /// 30 | /// Ruby: 31 | /// 32 | /// ```ruby 33 | /// 1.23 == 1.23 34 | /// ``` 35 | pub fn new(num: f64) -> Self { 36 | Self::from(float::float_to_num(num)) 37 | } 38 | 39 | /// Retrieves an `f64` value from `Float`. 40 | /// 41 | /// # Examples 42 | /// 43 | /// ``` 44 | /// use rutie::{Float, VM}; 45 | /// # VM::init(); 46 | /// 47 | /// let float = Float::new(1.23); 48 | /// 49 | /// assert_eq!(float.to_f64(), 1.23); 50 | /// ``` 51 | /// 52 | /// Ruby: 53 | /// 54 | /// ```ruby 55 | /// 1.23 == 1.23 56 | /// ``` 57 | pub fn to_f64(&self) -> f64 { 58 | float::num_to_float(self.value()) 59 | } 60 | 61 | /// Cast any object to a `Float` implicitly, otherwise 62 | /// returns an `AnyException` 63 | /// 64 | /// # Examples 65 | /// 66 | /// ``` 67 | /// use rutie::{Integer, Float, Object, VM}; 68 | /// # VM::init(); 69 | /// 70 | /// let integer = Integer::new(3); 71 | /// 72 | /// assert_eq!(Float::implicit_to_f(integer), Ok(Float::new(3.0))); 73 | /// ``` 74 | /// 75 | /// Ruby: 76 | /// 77 | /// ```ruby 78 | /// Float(3) == 3.0 79 | /// ``` 80 | pub fn implicit_to_f(object: impl Object) -> Result { 81 | float::implicit_to_f(object.value()) 82 | } 83 | } 84 | 85 | impl From for Float { 86 | fn from(value: Value) -> Self { 87 | Float { value } 88 | } 89 | } 90 | 91 | impl From for Value { 92 | fn from(val: Float) -> Self { 93 | val.value 94 | } 95 | } 96 | 97 | impl From for AnyObject { 98 | fn from(val: Float) -> Self { 99 | AnyObject::from(val.value) 100 | } 101 | } 102 | 103 | impl Object for Float { 104 | #[inline] 105 | fn value(&self) -> Value { 106 | self.value 107 | } 108 | } 109 | 110 | impl VerifiedObject for Float { 111 | fn is_correct_type(object: &T) -> bool { 112 | object.value().ty() == ValueType::Float 113 | } 114 | 115 | fn error_message() -> &'static str { 116 | "Error converting to Float" 117 | } 118 | } 119 | 120 | impl PartialEq for Float { 121 | fn eq(&self, other: &Self) -> bool { 122 | self.equals(other) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/binding/string.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | rubysys::{encoding, string}, 3 | types::{c_char, c_long, Value}, 4 | util, 5 | }; 6 | 7 | pub fn new(string: &str) -> Value { 8 | let str = string.as_ptr() as *const c_char; 9 | let len = string.len() as c_long; 10 | 11 | unsafe { string::rb_str_new(str, len) } 12 | } 13 | 14 | pub fn new_utf8(string: &str) -> Value { 15 | let str = string.as_ptr() as *const c_char; 16 | let len = string.len() as c_long; 17 | 18 | unsafe { string::rb_utf8_str_new(str, len) } 19 | } 20 | 21 | pub fn new_from_bytes(bytes: &[u8], enc: Value) -> Value { 22 | let bts = bytes.as_ptr() as *const c_char; 23 | let len = bytes.len() as c_long; 24 | 25 | unsafe { string::rb_enc_str_new(bts, len, encoding::rb_to_encoding(enc) as *mut _) } 26 | } 27 | 28 | pub fn new_frozen(value: Value) -> Value { 29 | unsafe { string::rb_str_new_frozen(value) } 30 | } 31 | 32 | // Returns RString Value or NilClass Value 33 | // same as method `String.try_convert` 34 | pub fn method_to_str(str: Value) -> Value { 35 | unsafe { string::rb_check_string_type(str) } 36 | } 37 | 38 | pub fn value_to_string(value: Value) -> String { 39 | unsafe { 40 | let str = string::rb_string_value_cstr(&value as *const _); 41 | 42 | util::cstr_to_string(str) 43 | } 44 | } 45 | 46 | pub fn value_to_string_unchecked(value: Value) -> String { 47 | unsafe { 48 | let vec = value_to_bytes_unchecked(value).to_vec(); 49 | 50 | String::from_utf8_unchecked(vec) 51 | } 52 | } 53 | 54 | pub fn value_to_str<'a>(value: Value) -> &'a str { 55 | unsafe { 56 | let str = string::rb_string_value_cstr(&value as *const _); 57 | 58 | util::cstr_to_str(str) 59 | } 60 | } 61 | 62 | pub fn value_to_bytes_unchecked<'a>(value: Value) -> &'a [u8] { 63 | unsafe { 64 | let str = string::rb_string_value_ptr(&value) as *const u8; 65 | let len = string::rstring_len(value) as usize; 66 | ::std::slice::from_raw_parts(str, len) 67 | } 68 | } 69 | 70 | pub fn value_to_str_unchecked<'a>(value: Value) -> &'a str { 71 | unsafe { 72 | let slice = value_to_bytes_unchecked(value); 73 | 74 | ::std::str::from_utf8_unchecked(slice) 75 | } 76 | } 77 | 78 | #[allow(clippy::useless_conversion)] // For w64-mingw32 rb_string_len returns i32 79 | pub fn bytesize(value: Value) -> i64 { 80 | unsafe { string::rstring_len(value).into() } 81 | } 82 | 83 | #[allow(clippy::useless_conversion)] // For w64-mingw32 rb_str_strlen returns i32 84 | pub fn count_chars(value: Value) -> i64 { 85 | unsafe { string::rb_str_strlen(value).into() } 86 | } 87 | 88 | pub fn concat(value: Value, bytes: &[u8]) -> Value { 89 | let str = bytes.as_ptr() as *const c_char; 90 | let len = bytes.len() as c_long; 91 | 92 | unsafe { string::rb_str_cat(value, str, len) } 93 | } 94 | 95 | pub fn is_lockedtmp(str: Value) -> bool { 96 | unsafe { string::is_lockedtmp(str) } 97 | } 98 | 99 | #[allow(dead_code)] 100 | pub fn locktmp(str: Value) -> Value { 101 | unsafe { string::rb_str_locktmp(str) } 102 | } 103 | 104 | #[allow(dead_code)] 105 | pub fn unlocktmp(str: Value) -> Value { 106 | unsafe { string::rb_str_unlocktmp(str) } 107 | } 108 | -------------------------------------------------------------------------------- /src/rubysys/vm.rs: -------------------------------------------------------------------------------- 1 | use super::types::{c_char, c_int, c_void, Argc, CallbackPtr, Id, Value, VmPointer}; 2 | 3 | extern "C" { 4 | // void 5 | // ruby_init(void) 6 | pub fn ruby_init(); 7 | // void 8 | // ruby_init_loadpath(void) 9 | pub fn ruby_init_loadpath(); 10 | // void 11 | // ruby_vm_at_exit(void(*func)(ruby_vm_t *)) 12 | pub fn ruby_vm_at_exit(func: VmPointer); 13 | // VALUE 14 | // rb_block_proc(void) 15 | pub fn rb_block_proc() -> Value; 16 | // int 17 | // rb_block_given_p(void) 18 | pub fn rb_block_given_p() -> c_int; 19 | // VALUE 20 | // rb_errinfo(void) 21 | pub fn rb_errinfo() -> Value; 22 | // VALUE 23 | // rb_eval_string(const char *str) 24 | pub fn rb_eval_string(string: *const c_char) -> Value; 25 | // VALUE 26 | // rb_eval_string_protect(const char *str, int *pstate) 27 | pub fn rb_eval_string_protect(string: *const c_char, state: *mut c_int) -> Value; 28 | // VALUE 29 | // rb_f_abort(int argc, const VALUE *argv) 30 | pub fn rb_f_abort(argc: Argc, argv: *const Value) -> Value; 31 | // //////////////// UNAVAILABLE METHOD //////////////// 32 | // // VALUE 33 | // // rb_f_eval(int argc, const VALUE *argv, VALUE self) 34 | // pub fn rb_f_eval(argc: c_int, argv: *const Value, self_: Value) -> Value; 35 | // ///////////////// ///////////////// /////////////// 36 | // void 37 | // rb_exc_raise(VALUE mesg) 38 | pub fn rb_exc_raise(exception: Value); 39 | // void 40 | // rb_exit(int status) 41 | pub fn rb_exit(status: c_int); 42 | // void 43 | // rb_set_end_proc(void (*func)(VALUE arg), VALUE arg) 44 | pub fn rb_set_end_proc(func: CallbackPtr, arg: VmPointer); 45 | // void 46 | // rb_raise(VALUE exc, const char *fmt, ...) 47 | pub fn rb_raise(exception: Value, message: *const c_char); 48 | // VALUE 49 | // rb_require(const char *fname) 50 | pub fn rb_require(name: *const c_char) -> Value; 51 | // void 52 | // rb_set_errinfo(VALUE err) 53 | pub fn rb_set_errinfo(err: Value); 54 | // VALUE 55 | // rb_protect(VALUE (* proc) (VALUE), VALUE data, int *pstate) 56 | pub fn rb_protect(func: CallbackPtr, args: *const c_void, state: *mut c_int) -> Value; 57 | // VALUE 58 | // rb_funcallv(VALUE recv, ID mid, int argc, const VALUE *argv) 59 | pub fn rb_funcallv(receiver: Value, method: Id, argc: Argc, argv: *const Value) -> Value; 60 | // VALUE 61 | // rb_funcallv_public(VALUE recv, ID mid, int argc, const VALUE *argv) 62 | pub fn rb_funcallv_public(receiver: Value, method: Id, argc: Argc, argv: *const Value) 63 | -> Value; 64 | // VALUE 65 | // rb_block_call(VALUE obj, ID mid, int argc, const VALUE * argv, 66 | // VALUE (*bl_proc) (ANYARGS), VALUE data2) 67 | pub fn rb_block_call( 68 | obj: Value, 69 | method_id: Id, 70 | argc: Argc, 71 | argv: *const Value, 72 | block: extern "C" fn(Value, Value, Argc, *const Value) -> Value, 73 | outer_scope: Value, 74 | ) -> Value; 75 | // VALUE 76 | // rb_yield_splat(VALUE values) 77 | pub fn rb_yield_splat(values: Value) -> Value; 78 | // VALUE 79 | // rb_yield(VALUE val) 80 | pub fn rb_yield(value: Value) -> Value; 81 | // VALUE 82 | // rb_call_super(int argc, const VALUE *argv) 83 | pub fn rb_call_super(argc: Argc, argv: *const Value) -> Value; 84 | } 85 | -------------------------------------------------------------------------------- /src/binding/thread.rs: -------------------------------------------------------------------------------- 1 | use std::{mem::transmute, ptr}; 2 | 3 | use crate::{ 4 | rubysys::thread, 5 | types::{CallbackMutPtr, CallbackPtr, Value}, 6 | util, Object, 7 | }; 8 | 9 | #[cfg(unix)] 10 | use crate::types::RawFd; 11 | 12 | pub fn create(func: F) -> Value 13 | where 14 | F: FnMut() -> R, 15 | R: Object, 16 | { 17 | let fnbox = Box::new(func) as Box R>; 18 | 19 | let closure_ptr = Box::into_raw(Box::new(fnbox)) as CallbackMutPtr; 20 | 21 | unsafe { 22 | thread::rb_thread_create( 23 | transmute(thread_create_callbox:: as CallbackMutPtr), 24 | closure_ptr, 25 | ) 26 | } 27 | } 28 | 29 | #[cfg(unix)] 30 | pub fn wait_fd(fd: RawFd) { 31 | unsafe { thread::rb_thread_wait_fd(fd) }; 32 | } 33 | 34 | pub fn call_without_gvl(func: F, unblock_func: Option) -> R 35 | where 36 | F: FnMut() -> R, 37 | G: FnMut(), 38 | { 39 | unsafe { 40 | let ptr = if let Some(ubf) = unblock_func { 41 | thread::rb_thread_call_without_gvl( 42 | thread_call_callbox as CallbackPtr, 43 | util::closure_to_ptr(func), 44 | thread_call_callbox as CallbackPtr, 45 | util::closure_to_ptr(ubf), 46 | ) 47 | } else { 48 | thread::rb_thread_call_without_gvl( 49 | thread_call_callbox as CallbackPtr, 50 | util::closure_to_ptr(func), 51 | ptr::null(), 52 | ptr::null_mut() as CallbackMutPtr, 53 | ) 54 | }; 55 | 56 | util::ptr_to_data(ptr) 57 | } 58 | } 59 | 60 | pub fn call_without_gvl2(func: F, unblock_func: Option) -> R 61 | where 62 | F: FnMut() -> R, 63 | G: FnMut(), 64 | { 65 | unsafe { 66 | let ptr = if let Some(ubf) = unblock_func { 67 | thread::rb_thread_call_without_gvl2( 68 | thread_call_callbox as CallbackPtr, 69 | util::closure_to_ptr(func), 70 | thread_call_callbox as CallbackPtr, 71 | util::closure_to_ptr(ubf), 72 | ) 73 | } else { 74 | thread::rb_thread_call_without_gvl2( 75 | thread_call_callbox as CallbackPtr, 76 | util::closure_to_ptr(func), 77 | ptr::null(), 78 | ptr::null_mut() as CallbackMutPtr, 79 | ) 80 | }; 81 | 82 | util::ptr_to_data(ptr) 83 | } 84 | } 85 | 86 | pub fn call_with_gvl(func: F) -> R 87 | where 88 | F: FnMut() -> R, 89 | { 90 | unsafe { 91 | let ptr = thread::rb_thread_call_with_gvl( 92 | thread_call_callbox as CallbackPtr, 93 | util::closure_to_ptr(func), 94 | ); 95 | 96 | util::ptr_to_data(ptr) 97 | } 98 | } 99 | 100 | extern "C" fn thread_create_callbox(boxptr: CallbackMutPtr) -> Value 101 | where 102 | R: Object, 103 | { 104 | let mut fnbox: Box R>> = 105 | unsafe { Box::from_raw(boxptr as *mut Box R>) }; 106 | 107 | fnbox().value() 108 | } 109 | 110 | extern "C" fn thread_call_callbox(boxptr: CallbackMutPtr) -> CallbackPtr { 111 | let mut fnbox: Box CallbackPtr>> = 112 | unsafe { Box::from_raw(boxptr as *mut Box CallbackPtr>) }; 113 | 114 | fnbox() 115 | } 116 | -------------------------------------------------------------------------------- /src/class/rproc.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | 3 | use crate::{ 4 | binding::rproc, types::Value, util, AnyObject, Boolean, Class, Object, VerifiedObject, 5 | }; 6 | 7 | /// `Proc` (works with `Lambda` as well) 8 | #[derive(Debug)] 9 | #[repr(C)] 10 | pub struct Proc { 11 | value: Value, 12 | } 13 | 14 | impl Proc { 15 | /// Calls a proc with given arguments 16 | /// 17 | /// # Examples 18 | /// 19 | /// ```no_run 20 | /// use rutie::{Class, Object, Proc, RString, class, methods}; 21 | /// 22 | /// class!(Greeter); 23 | /// 24 | /// methods!( 25 | /// Greeter, 26 | /// rtself, 27 | /// 28 | /// fn greet_rust_with(greeting_template: Proc) -> RString { 29 | /// let name = RString::new_utf8("Rust").to_any_object(); 30 | /// let rendered_template = greeting_template.unwrap().call(&[name]); 31 | /// 32 | /// rendered_template.try_convert_to::().unwrap() 33 | /// } 34 | /// ); 35 | /// 36 | /// Class::new("Greeter", None).define(|klass| { 37 | /// klass.def_self("greet_rust_with", greet_rust_with); 38 | /// }); 39 | /// ``` 40 | /// 41 | /// Ruby: 42 | /// 43 | /// ```ruby 44 | /// class Greeter 45 | /// def self.greet_rust_with(greeting_template) 46 | /// greeting_template.call('Rust') 47 | /// end 48 | /// end 49 | /// 50 | /// greeting_template = -> (name) { "Hello, #{name}!" } 51 | /// 52 | /// Greeter.greet_rust_with(greeting_template) # => "Hello, Rust!" 53 | /// ``` 54 | pub fn call(&self, arguments: &[AnyObject]) -> AnyObject { 55 | let arguments = util::arguments_to_values(arguments); 56 | let result = rproc::call(self.value(), &arguments); 57 | 58 | AnyObject::from(result) 59 | } 60 | 61 | /// Check if Proc is a lambda 62 | /// 63 | /// # Examples 64 | /// 65 | /// ``` 66 | /// use rutie::{Object, Proc, VM, VerifiedObject}; 67 | /// # VM::init(); 68 | /// 69 | /// let procish = VM::eval("lambda {|a,b| a + b }").unwrap(); 70 | /// 71 | /// assert!(Proc::is_correct_type(&procish), "not Proc!"); 72 | /// ``` 73 | /// 74 | /// Ruby: 75 | /// 76 | /// ```ruby 77 | /// procish = lambda {|a,b| a + b } 78 | /// 79 | /// procish.lambda? # => true 80 | /// ``` 81 | pub fn is_lambda(&self) -> bool { 82 | Boolean::from(unsafe { self.send("lambda?", &[]) }.value()).to_bool() 83 | } 84 | } 85 | 86 | impl From for Proc { 87 | fn from(value: Value) -> Self { 88 | Proc { value } 89 | } 90 | } 91 | 92 | impl From for Value { 93 | fn from(val: Proc) -> Self { 94 | val.value 95 | } 96 | } 97 | 98 | impl From for AnyObject { 99 | fn from(val: Proc) -> Self { 100 | AnyObject::from(val.value) 101 | } 102 | } 103 | 104 | impl Object for Proc { 105 | #[inline] 106 | fn value(&self) -> Value { 107 | self.value 108 | } 109 | } 110 | 111 | impl VerifiedObject for Proc { 112 | fn is_correct_type(object: &T) -> bool { 113 | Class::from_existing("Proc").case_equals(object) 114 | } 115 | 116 | fn error_message() -> &'static str { 117 | "Error converting to Proc" 118 | } 119 | } 120 | 121 | impl PartialEq for Proc { 122 | fn eq(&self, other: &Self) -> bool { 123 | self.equals(other) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/class/any_object.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | types::{InternalValue, Value}, 3 | Object, VerifiedObject, 4 | }; 5 | use std::{borrow::Borrow, convert::AsRef, ops::Deref}; 6 | 7 | /// Representation of any Ruby object while its type is unknown 8 | /// 9 | /// As Ruby is a dynamically typed language, at some points Rutie does not know the exact Ruby type 10 | /// of the object, for example: 11 | /// 12 | /// - Retrieving an object from array; 13 | /// 14 | /// - Retrieving an object from hash; 15 | /// 16 | /// - Receiving arguments to a method; 17 | /// 18 | /// - Initializing a new instance of a non-built-in class. 19 | /// 20 | /// In these cases you should cast `AnyObject` to the required type. 21 | /// 22 | /// # Examples 23 | /// 24 | /// ### Retrieving an object from `Array` 25 | /// 26 | /// ``` 27 | /// use rutie::{Array, Fixnum, Object, VM}; 28 | /// # VM::init(); 29 | /// 30 | /// let array = Array::new().push(Fixnum::new(1)); 31 | /// let value = array.at(0).try_convert_to::(); // `Array::at()` returns `AnyObject` 32 | /// 33 | /// assert_eq!(value, Ok(Fixnum::new(1))); 34 | /// ``` 35 | /// 36 | /// ### Retrieving an object from `Hash` 37 | /// 38 | /// ``` 39 | /// use rutie::{Fixnum, Hash, Object, Symbol, VM}; 40 | /// # VM::init(); 41 | /// 42 | /// let mut hash = Hash::new(); 43 | /// 44 | /// hash.store(Symbol::new("key"), Fixnum::new(1)); 45 | /// 46 | /// // `Hash::at()` returns `AnyObject` 47 | /// let value = hash.at(&Symbol::new("key")).try_convert_to::(); 48 | /// 49 | /// assert_eq!(value, Ok(Fixnum::new(1))); 50 | /// ``` 51 | /// 52 | /// You can find more examples in `Class`, `Object` and `VerifiedObject` documentation. 53 | #[derive(Clone, Debug)] 54 | #[repr(C)] 55 | pub struct AnyObject { 56 | value: Value, 57 | } 58 | 59 | impl From for AnyObject { 60 | fn from(value: Value) -> Self { 61 | AnyObject { value } 62 | } 63 | } 64 | 65 | impl From for AnyObject { 66 | fn from(value: InternalValue) -> Self { 67 | AnyObject { 68 | value: Value::from(value), 69 | } 70 | } 71 | } 72 | 73 | impl From for Value { 74 | fn from(val: AnyObject) -> Self { 75 | val.value 76 | } 77 | } 78 | 79 | impl Borrow for AnyObject { 80 | fn borrow(&self) -> &Value { 81 | &self.value 82 | } 83 | } 84 | 85 | impl AsRef for AnyObject { 86 | fn as_ref(&self) -> &Value { 87 | &self.value 88 | } 89 | } 90 | 91 | impl AsRef for AnyObject { 92 | #[inline] 93 | fn as_ref(&self) -> &Self { 94 | self 95 | } 96 | } 97 | 98 | impl From<&T> for AnyObject { 99 | fn from(value: &T) -> Self { 100 | value.to_any_object() 101 | } 102 | } 103 | 104 | impl Object for AnyObject { 105 | #[inline] 106 | fn value(&self) -> Value { 107 | self.value 108 | } 109 | } 110 | 111 | impl Deref for AnyObject { 112 | type Target = Value; 113 | 114 | fn deref(&self) -> &Value { 115 | &self.value 116 | } 117 | } 118 | 119 | // Any object can be safely converted to `AnyObject` :) 120 | impl VerifiedObject for AnyObject { 121 | fn is_correct_type(_: &T) -> bool { 122 | true 123 | } 124 | 125 | fn error_message() -> &'static str { 126 | unreachable!() 127 | } 128 | } 129 | 130 | impl PartialEq for AnyObject { 131 | fn eq(&self, other: &Self) -> bool { 132 | self.equals(other) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/class/fixnum.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | 3 | use crate::{ 4 | binding::fixnum, 5 | types::{Value, ValueType}, 6 | AnyObject, Object, VerifiedObject, 7 | }; 8 | 9 | /// `Fixnum` 10 | #[derive(Debug)] 11 | #[repr(C)] 12 | pub struct Fixnum { 13 | value: Value, 14 | } 15 | 16 | impl Fixnum { 17 | /// Creates a new `Fixnum`. 18 | /// 19 | /// # Examples 20 | /// 21 | /// ``` 22 | /// use rutie::{Fixnum, VM}; 23 | /// # VM::init(); 24 | /// 25 | /// let fixnum = Fixnum::new(1); 26 | /// 27 | /// assert_eq!(fixnum.to_i64(), 1); 28 | /// ``` 29 | /// 30 | /// Ruby: 31 | /// 32 | /// ```ruby 33 | /// 1 == 1 34 | /// ``` 35 | pub fn new(num: i64) -> Self { 36 | Self::from(fixnum::i64_to_num(num)) 37 | } 38 | 39 | /// Retrieves an `i64` value from `Fixnum`. 40 | /// 41 | /// # Examples 42 | /// 43 | /// ``` 44 | /// use rutie::{Fixnum, VM}; 45 | /// # VM::init(); 46 | /// 47 | /// let fixnum = Fixnum::new(1); 48 | /// 49 | /// assert_eq!(fixnum.to_i64(), 1); 50 | /// ``` 51 | /// 52 | /// Ruby: 53 | /// 54 | /// ```ruby 55 | /// 1 == 1 56 | /// ``` 57 | pub fn to_i64(&self) -> i64 { 58 | fixnum::num_to_i64(self.value()) 59 | } 60 | 61 | /// Retrieves an `u64` value from `Fixnum`. 62 | /// 63 | /// # Examples 64 | /// 65 | /// ``` 66 | /// use rutie::{Fixnum, VM}; 67 | /// # VM::init(); 68 | /// 69 | /// let fixnum = Fixnum::new(1); 70 | /// 71 | /// assert_eq!(fixnum.to_u64(), 1); 72 | /// ``` 73 | /// 74 | /// Ruby: 75 | /// 76 | /// ```ruby 77 | /// 1 == 1 78 | /// ``` 79 | pub fn to_u64(&self) -> u64 { 80 | fixnum::num_to_u64(self.value()) 81 | } 82 | 83 | /// Retrieves an `i32` value from `Fixnum`. 84 | /// 85 | /// # Examples 86 | /// 87 | /// ``` 88 | /// use rutie::{Fixnum, VM}; 89 | /// # VM::init(); 90 | /// 91 | /// let fixnum = Fixnum::new(1); 92 | /// 93 | /// assert_eq!(fixnum.to_i32(), 1); 94 | /// ``` 95 | /// 96 | /// Ruby: 97 | /// 98 | /// ```ruby 99 | /// 1 == 1 100 | /// ``` 101 | pub fn to_i32(&self) -> i32 { 102 | fixnum::num_to_i32(self.value()) 103 | } 104 | 105 | /// Retrieves a `u32` value from `Fixnum`. 106 | /// 107 | /// # Examples 108 | /// 109 | /// ``` 110 | /// use rutie::{Fixnum, VM}; 111 | /// # VM::init(); 112 | /// 113 | /// let fixnum = Fixnum::new(1); 114 | /// 115 | /// assert_eq!(fixnum.to_u32(), 1); 116 | /// ``` 117 | /// 118 | /// Ruby: 119 | /// 120 | /// ```ruby 121 | /// 1 == 1 122 | /// ``` 123 | pub fn to_u32(&self) -> u32 { 124 | fixnum::num_to_u32(self.value()) 125 | } 126 | } 127 | 128 | impl From for Fixnum { 129 | fn from(value: Value) -> Self { 130 | Fixnum { value } 131 | } 132 | } 133 | 134 | impl From for Value { 135 | fn from(val: Fixnum) -> Self { 136 | val.value 137 | } 138 | } 139 | 140 | impl From for AnyObject { 141 | fn from(val: Fixnum) -> Self { 142 | AnyObject::from(val.value) 143 | } 144 | } 145 | 146 | impl Object for Fixnum { 147 | #[inline] 148 | fn value(&self) -> Value { 149 | self.value 150 | } 151 | } 152 | 153 | impl VerifiedObject for Fixnum { 154 | fn is_correct_type(object: &T) -> bool { 155 | object.value().ty() == ValueType::Fixnum 156 | } 157 | 158 | fn error_message() -> &'static str { 159 | "Error converting to Fixnum" 160 | } 161 | } 162 | 163 | impl PartialEq for Fixnum { 164 | fn eq(&self, other: &Self) -> bool { 165 | self.equals(other) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /examples/rutie_ruby_gvl_example/src/lib.rs: -------------------------------------------------------------------------------- 1 | use rutie::{class, methods, AnyObject, Class, Fixnum, NilClass, Object, RString, Thread}; 2 | 3 | #[cfg(unix)] 4 | use std::os::unix::{io::AsRawFd, net::UnixStream}; 5 | use std::sync::mpsc; 6 | 7 | class!(RutieExample); 8 | 9 | methods! { 10 | RutieExample, 11 | _rtself, 12 | fn heap_allocated_returning_input() -> RString { 13 | let input = "Object".to_string(); 14 | let handler = move || { 15 | assert_eq!("Object", &input); 16 | input.clone() 17 | }; 18 | let ret = Thread::call_without_gvl(handler, Some(|| {})); 19 | RString::new_utf8(&ret) 20 | } 21 | 22 | fn stack_allocated_returning_input() -> Fixnum { 23 | let input = 42; 24 | let handler = move || { 25 | assert_eq!(42, input); 26 | input 27 | }; 28 | let ret = Thread::call_without_gvl(handler, Some(|| {})); 29 | Fixnum::new(ret) 30 | } 31 | 32 | fn heap_allocated_returning_from_closure(n: Fixnum) -> Fixnum { 33 | let input = n.unwrap().to_i64() as u32; 34 | let input2 = "Object".to_string(); 35 | let handler = move || { 36 | assert_eq!(5, input); 37 | assert_eq!("Object", &input2); 38 | fibonacci(input) 39 | }; 40 | let ret = Thread::call_without_gvl(handler, Some(|| {})); 41 | Fixnum::new(ret as i64) 42 | } 43 | 44 | fn stack_allocated_returning_from_closure(n: Fixnum) -> RString { 45 | let input = n.unwrap().to_i64() as u32; 46 | let handler = move || { 47 | assert_eq!(5, input); 48 | fibonacci(input).to_string() 49 | }; 50 | let ret = Thread::call_without_gvl(handler, Some(|| {})); 51 | RString::new_utf8(&ret) 52 | } 53 | 54 | fn call_ruby_in_call_with_gvl() -> AnyObject { 55 | let class = "Object".to_string(); 56 | let b = Thread::call_without_gvl( 57 | move || { 58 | let _n = fibonacci(5); 59 | let class = class.clone(); 60 | Thread::call_with_gvl(move || { 61 | let ruby_class = Class::from_existing(&class); 62 | unsafe { ruby_class.send("name", &[]) } 63 | }) 64 | }, 65 | Some(|| {}), 66 | ); 67 | b 68 | } 69 | 70 | fn create_thread() -> AnyObject { 71 | let (tx, rx) = mpsc::channel(); 72 | Thread::new(move || { 73 | let ruby_class = Class::from_existing("Object"); 74 | let name = unsafe { ruby_class.send("name", &[]) }; 75 | tx.send(name).unwrap(); 76 | NilClass::new() 77 | }); 78 | let (unix_socket, _) = UnixStream::pair().unwrap(); 79 | loop { 80 | if let Ok(ret) = rx.try_recv() { 81 | return ret; 82 | } else { 83 | Thread::wait_fd(unix_socket.as_raw_fd()); 84 | } 85 | } 86 | } 87 | } 88 | 89 | fn fibonacci(n: u32) -> u32 { 90 | match n { 91 | 0 => 1, 92 | 1 => 1, 93 | _ => fibonacci(n - 1) + fibonacci(n - 2), 94 | } 95 | } 96 | 97 | #[allow(non_snake_case)] 98 | #[no_mangle] 99 | pub extern "C" fn Init_rutie_ruby_gvl_example() { 100 | Class::new("RutieExample", None).define(|klass| { 101 | klass.def_self( 102 | "stack_allocated_returning_input", 103 | stack_allocated_returning_input, 104 | ); 105 | klass.def_self( 106 | "stack_allocated_returning_from_closure", 107 | stack_allocated_returning_from_closure, 108 | ); 109 | klass.def_self( 110 | "heap_allocated_returning_input", 111 | heap_allocated_returning_input, 112 | ); 113 | klass.def_self( 114 | "heap_allocated_returning_from_closure", 115 | heap_allocated_returning_from_closure, 116 | ); 117 | klass.def_self("call_ruby_in_call_with_gvl", call_ruby_in_call_with_gvl); 118 | klass.def_self("create_thread", create_thread); 119 | }); 120 | } 121 | -------------------------------------------------------------------------------- /src/class/symbol.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | convert::From, 3 | hash::{Hash, Hasher}, 4 | }; 5 | 6 | use crate::{ 7 | binding::symbol, 8 | types::{Value, ValueType}, 9 | AnyObject, Object, Proc, VerifiedObject, 10 | }; 11 | 12 | /// `Symbol` 13 | #[derive(Debug)] 14 | #[repr(C)] 15 | pub struct Symbol { 16 | value: Value, 17 | } 18 | 19 | impl Symbol { 20 | /// Creates a new instance of Ruby `Symbol`. 21 | /// 22 | /// # Examples 23 | /// 24 | /// ``` 25 | /// use rutie::{Symbol, VM}; 26 | /// # VM::init(); 27 | /// 28 | /// let symbol = Symbol::new("hello"); 29 | /// 30 | /// assert_eq!(symbol.to_str(), "hello"); 31 | /// ``` 32 | /// 33 | /// Ruby: 34 | /// 35 | /// ```ruby 36 | /// sym = :hello 37 | /// 38 | /// sym.to_s == 'hello' 39 | /// ``` 40 | pub fn new(string: &str) -> Self { 41 | let id = symbol::internal_id(string); 42 | 43 | Self::from(symbol::id_to_sym(id)) 44 | } 45 | 46 | /// Retrieves the Rust `&str` corresponding to `Symbol` object (Ruby `Symbol#to_s`). 47 | /// 48 | /// # Examples 49 | /// 50 | /// ``` 51 | /// use rutie::{Symbol, VM}; 52 | /// # VM::init(); 53 | /// 54 | /// let symbol = Symbol::new("hello"); 55 | /// 56 | /// assert_eq!(symbol.to_str(), "hello"); 57 | /// ``` 58 | /// 59 | /// Ruby: 60 | /// 61 | /// ```ruby 62 | /// sym = :hello 63 | /// 64 | /// sym.to_s == 'hello' 65 | /// ``` 66 | pub fn to_str(&self) -> &str { 67 | symbol::value_to_str(self.value()) 68 | } 69 | 70 | /// Retrieves the Rust `String` corresponding to `Symbol` object (Ruby `Symbol#to_s`). 71 | /// 72 | /// # Examples 73 | /// 74 | /// ``` 75 | /// use rutie::{Symbol, VM}; 76 | /// # VM::init(); 77 | /// 78 | /// let symbol = Symbol::new("hello"); 79 | /// 80 | /// assert_eq!(symbol.to_string(), "hello"); 81 | /// ``` 82 | /// 83 | /// Ruby: 84 | /// 85 | /// ```ruby 86 | /// sym = :hello 87 | /// 88 | /// sym.to_s == 'hello' 89 | /// ``` 90 | #[allow(clippy::inherent_to_string)] // We want this instead of implementing Display. 91 | pub fn to_string(&self) -> String { 92 | symbol::value_to_string(self.value()) 93 | } 94 | 95 | /// Converts `Symbol` to `Proc` 96 | /// 97 | /// # Examples 98 | /// 99 | /// ``` 100 | /// use rutie::{Symbol, Proc, VM, VerifiedObject}; 101 | /// # VM::init(); 102 | /// 103 | /// let symbol = Symbol::new("hello"); 104 | /// 105 | /// assert!(Proc::is_correct_type(&symbol.to_proc()), "not correct type!"); 106 | /// ``` 107 | /// 108 | /// Ruby: 109 | /// 110 | /// ```ruby 111 | /// sym = :hello 112 | /// 113 | /// sym.to_s == 'hello' 114 | /// ``` 115 | pub fn to_proc(&self) -> Proc { 116 | Proc::from(unsafe { self.send("to_proc", &[]) }.value()) 117 | } 118 | } 119 | 120 | impl From for Symbol { 121 | fn from(value: Value) -> Self { 122 | Symbol { value } 123 | } 124 | } 125 | 126 | impl From for Value { 127 | fn from(val: Symbol) -> Self { 128 | val.value 129 | } 130 | } 131 | 132 | impl From for AnyObject { 133 | fn from(val: Symbol) -> Self { 134 | AnyObject::from(val.value) 135 | } 136 | } 137 | 138 | impl Object for Symbol { 139 | #[inline] 140 | fn value(&self) -> Value { 141 | self.value 142 | } 143 | } 144 | 145 | impl VerifiedObject for Symbol { 146 | fn is_correct_type(object: &T) -> bool { 147 | object.value().ty() == ValueType::Symbol 148 | } 149 | 150 | fn error_message() -> &'static str { 151 | "Error converting to Symbol" 152 | } 153 | } 154 | 155 | impl PartialEq for Symbol { 156 | fn eq(&self, other: &Self) -> bool { 157 | self.equals(other) 158 | } 159 | } 160 | 161 | impl Eq for Symbol {} 162 | 163 | impl Hash for Symbol { 164 | fn hash(&self, state: &mut H) 165 | where 166 | H: Hasher, 167 | { 168 | self.value().value.hash(state); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/rubysys/string.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | constant::FL_USER_7, 3 | types::{c_char, c_long, EncodingType, InternalValue, Value}, 4 | }; 5 | use rb_sys::{RString, VALUE}; 6 | pub const STR_TMPLOCK: isize = FL_USER_7; 7 | 8 | extern "C" { 9 | // VALUE 10 | // rb_str_new(const char *ptr, long len) 11 | pub fn rb_str_new(str: *const c_char, len: c_long) -> Value; 12 | // VALUE 13 | // rb_str_new_cstr(const char *ptr) 14 | pub fn rb_str_new_cstr(str: *const c_char) -> Value; 15 | // VALUE 16 | // rb_utf8_str_new(const char *ptr, long len) 17 | pub fn rb_utf8_str_new(str: *const c_char, len: c_long) -> Value; 18 | // VALUE 19 | // rb_utf8_str_new_cstr(const char *ptr) 20 | pub fn rb_utf8_str_new_cstr(str: *const c_char) -> Value; 21 | // char * 22 | // rb_string_value_cstr(volatile VALUE *ptr) 23 | pub fn rb_string_value_cstr(str: *const Value) -> *const c_char; 24 | // char * 25 | // rb_string_value_ptr(volatile VALUE *ptr) 26 | pub fn rb_string_value_ptr(str: *const Value) -> *const c_char; 27 | // long 28 | // rb_str_strlen(VALUE str) 29 | pub fn rb_str_strlen(str: Value) -> c_long; 30 | // int 31 | // rb_enc_str_asciionly_p(VALUE str) 32 | pub fn rb_enc_str_asciionly_p(str: Value) -> bool; 33 | // VALUE 34 | // rb_enc_str_new(const char *ptr, long len, rb_encoding *enc) 35 | pub fn rb_enc_str_new(str: *const c_char, len: c_long, enc: EncodingType) -> Value; 36 | // VALUE 37 | // rb_str_export_locale(VALUE str) 38 | pub fn rb_str_export_locale(str: Value) -> Value; 39 | // static VALUE 40 | // rb_str_valid_encoding_p(VALUE str) 41 | pub fn rb_str_valid_encoding_p(str: Value) -> bool; 42 | // VALUE 43 | // rb_str_cat(VALUE str, const char *ptr, long len) 44 | pub fn rb_str_cat(str: Value, ptr: *const c_char, len: c_long) -> Value; 45 | // VALUE 46 | // rb_check_string_type(VALUE str) 47 | pub fn rb_check_string_type(str: Value) -> Value; 48 | //------------------------------------------------------------- 49 | // LINKER CANNOT FIND 50 | // // 51 | // // call-seq: 52 | // // str.force_encoding(encoding) -> str 53 | // // 54 | // // Changes the encoding to +encoding+ and returns self. 55 | // // 56 | // // static VALUE 57 | // // rb_str_force_encoding(VALUE str, VALUE enc) 58 | // pub fn rb_str_force_encoding(s: Value, enc: Value) -> Value; 59 | //------------------------------------------------------------- 60 | // VALUE 61 | // rb_str_locktmp(VALUE str) 62 | pub fn rb_str_locktmp(str: Value) -> Value; 63 | // VALUE 64 | // rb_str_unlocktmp(VALUE str) 65 | pub fn rb_str_unlocktmp(str: Value) -> Value; 66 | // VALUE 67 | // rb_str_new_frozen(VALUE orig) 68 | pub fn rb_str_new_frozen(orig: Value) -> Value; 69 | } 70 | 71 | unsafe fn rstring_and_flags(value: Value) -> (*const RString, InternalValue) { 72 | let rstring: *const RString = value.value as _; 73 | let flags = (*rstring).basic.flags; 74 | 75 | (rstring, flags) 76 | } 77 | 78 | pub unsafe fn rstring_len(value: Value) -> c_long { 79 | rb_sys::RSTRING_LEN::(value.into()) 80 | } 81 | 82 | pub unsafe fn rstring_ptr(value: Value) -> *const c_char { 83 | rb_sys::RSTRING_PTR::(value.into()) 84 | } 85 | 86 | pub unsafe fn rstring_end(value: Value) -> *const c_char { 87 | let ptr = rstring_ptr(value) as *const c_char; 88 | let len = rstring_len(value) as usize; 89 | 90 | &*ptr.add(len) 91 | } 92 | 93 | // ``` 94 | // use rutie::VM; 95 | // # VM::init(); 96 | // 97 | // use rutie::binding::string::*; // binding not public 98 | // 99 | // let word = new_utf8("word"); 100 | // unsafe { 101 | // assert!(!is_locktmp(word), "word should not be locktmp but is"); 102 | // locktmp(word); 103 | // assert!(is_locktmp(word), "word should be locktmp but is not"); 104 | // unlocktmp(word); 105 | // assert!(!is_locktmp(word), "word should not be locktmp but is"); 106 | // } 107 | // ``` 108 | pub unsafe fn is_lockedtmp(value: Value) -> bool { 109 | let (_, flags) = rstring_and_flags(value); 110 | 111 | flags & STR_TMPLOCK as u64 != 0 112 | } 113 | -------------------------------------------------------------------------------- /src/rubysys/class.rs: -------------------------------------------------------------------------------- 1 | use super::types::{c_char, c_int, Argc, CallbackPtr, Id, Value}; 2 | 3 | extern "C" { 4 | // VALUE 5 | // rb_class_new_instance(int argc, const VALUE *argv, VALUE klass) 6 | pub fn rb_class_new_instance(argc: Argc, argv: *const Value, klass: Value) -> Value; 7 | // VALUE 8 | // rb_class_superclass(VALUE klass) 9 | pub fn rb_class_superclass(klass: Value) -> Value; 10 | // VALUE 11 | // rb_const_get(VALUE obj, ID id) 12 | pub fn rb_const_get(klass: Value, name: Id) -> Value; 13 | // void 14 | // rb_define_attr(VALUE klass, const char *name, int read, int write) 15 | pub fn rb_define_attr(klass: Value, name: *const c_char, read: c_int, write: c_int); 16 | // VALUE 17 | // rb_define_class(const char *name, VALUE super) 18 | pub fn rb_define_class(name: *const c_char, superclass: Value) -> Value; 19 | // VALUE 20 | // rb_define_class_under(VALUE outer, const char *name, VALUE super) 21 | pub fn rb_define_class_under(outer: Value, name: *const c_char, superclass: Value) -> Value; 22 | // void 23 | // rb_define_const(VALUE klass, const char *name, VALUE val) 24 | pub fn rb_define_const(klass: Value, name: *const c_char, value: Value); 25 | // void 26 | // rb_define_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc) 27 | pub fn rb_define_method(klass: Value, name: *const c_char, callback: CallbackPtr, argc: Argc); 28 | // VALUE 29 | // rb_define_module(const char *name) 30 | pub fn rb_define_module(name: *const c_char) -> Value; 31 | // void 32 | // rb_define_module_function(VALUE module, const char *name, VALUE (*func)(ANYARGS), int argc) 33 | pub fn rb_define_module_function( 34 | klass: Value, 35 | name: *const c_char, 36 | callback: CallbackPtr, 37 | argc: Argc, 38 | ); 39 | // VALUE 40 | // rb_define_module_under(VALUE outer, const char *name) 41 | pub fn rb_define_module_under(outer: Value, name: *const c_char) -> Value; 42 | // void 43 | // rb_define_private_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc) 44 | pub fn rb_define_private_method( 45 | klass: Value, 46 | name: *const c_char, 47 | callback: CallbackPtr, 48 | argc: Argc, 49 | ); 50 | // void 51 | // rb_define_singleton_method(VALUE obj, const char *name, VALUE (*func)(ANYARGS), int argc) 52 | pub fn rb_define_singleton_method( 53 | klass: Value, 54 | name: *const c_char, 55 | callback: CallbackPtr, 56 | argc: Argc, 57 | ); 58 | 59 | // int 60 | // rb_eql(VALUE obj1, VALUE obj2) 61 | pub fn rb_eql(obj1: Value, obj2: Value) -> c_int; 62 | // VALUE 63 | // rb_equal(VALUE obj1, VALUE obj2) 64 | pub fn rb_equal(obj1: Value, obj2: Value) -> Value; 65 | // void 66 | // rb_extend_object(VALUE object, VALUE module) 67 | pub fn rb_extend_object(object: Value, module: Value); 68 | // void 69 | // rb_include_module(VALUE klass, VALUE module) 70 | pub fn rb_include_module(klass: Value, module: Value); 71 | // VALUE 72 | // rb_ivar_get(VALUE obj, ID id) 73 | pub fn rb_ivar_get(object: Value, name: Id) -> Value; 74 | // VALUE 75 | // rb_ivar_set(VALUE obj, ID id, VALUE val) 76 | pub fn rb_ivar_set(object: Value, name: Id, value: Value) -> Value; 77 | // VALUE 78 | // rb_mod_ancestors(VALUE mod) 79 | pub fn rb_mod_ancestors(module: Value) -> Value; 80 | // VALUE 81 | // rb_obj_class(VALUE obj) 82 | pub fn rb_obj_class(object: Value) -> Value; 83 | // VALUE 84 | // rb_obj_freeze(VALUE obj) 85 | pub fn rb_obj_freeze(object: Value) -> Value; 86 | // VALUE 87 | // rb_obj_frozen_p(VALUE obj) 88 | pub fn rb_obj_frozen_p(object: Value) -> Value; 89 | // void 90 | // rb_prepend_module(VALUE klass, VALUE module) 91 | pub fn rb_prepend_module(klass: Value, module: Value); 92 | // int 93 | // rb_respond_to(VALUE obj, ID id) 94 | pub fn rb_respond_to(object: Value, id: Id) -> c_int; 95 | // VALUE 96 | // rb_singleton_class(VALUE obj) 97 | pub fn rb_singleton_class(object: Value) -> Value; 98 | // int 99 | // rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...) 100 | pub fn rb_scan_args(argc: Argc, argv: *const Value, fmt: *const c_char, ...) -> c_int; 101 | } 102 | -------------------------------------------------------------------------------- /src/rubysys/encoding.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | constant::{FL_USER_8, FL_USER_9}, 3 | types::{c_char, c_int, size_t, EncodingIndex, EncodingType, InternalValue, RBasic, Value}, 4 | }; 5 | 6 | pub const ENC_DUMMY_FLAG: isize = 1 << 24; 7 | pub const ENC_INDEX_MASK: isize = !(!0 << 24); 8 | pub const ENC_CODERANGE_UNKNOWN: isize = 0; 9 | pub const ENC_CODERANGE_7BIT: isize = FL_USER_8; 10 | pub const ENC_CODERANGE_VALID: isize = FL_USER_9; 11 | pub const ENC_CODERANGE_BROKEN: isize = FL_USER_8 | FL_USER_9; 12 | pub const ENC_CODERANGE_MASK: isize = 13 | ENC_CODERANGE_7BIT | ENC_CODERANGE_VALID | ENC_CODERANGE_BROKEN; 14 | 15 | extern "C" { 16 | // VALUE 17 | // rb_enc_associate(VALUE obj, rb_encoding *enc) 18 | pub fn rb_enc_associate(obj: Value, enc: EncodingType) -> Value; 19 | // VALUE 20 | // rb_enc_associate_index(VALUE obj, int idx) 21 | pub fn rb_enc_associate_index(obj: Value, idx: c_int) -> Value; 22 | // rb_encoding* 23 | // rb_enc_compatible(VALUE str1, VALUE str2) 24 | pub fn rb_enc_compatible(str1: Value, str2: Value) -> EncodingType; 25 | // VALUE 26 | // rb_enc_default_external(void) 27 | pub fn rb_enc_default_external() -> Value; 28 | // VALUE 29 | // rb_enc_default_internal(void) 30 | pub fn rb_enc_default_internal() -> Value; 31 | // int 32 | // rb_enc_find_index(const char *name) 33 | pub fn rb_enc_find_index(name: *const c_char) -> EncodingIndex; 34 | // ------------------------------------------------------ 35 | // LINKER CANNOT FIND 36 | // // static VALUE 37 | // // rb_enc_from_encoding_index(int idx) 38 | // pub fn rb_enc_from_encoding_index(idx: c_int) -> Value; 39 | // ------------------------------------------------------ 40 | // VALUE 41 | // rb_enc_from_encoding(rb_encoding *encoding) 42 | pub fn rb_enc_from_encoding(encoding: EncodingType) -> Value; 43 | // rb_encoding * 44 | // rb_enc_from_index(int index) 45 | pub fn rb_enc_from_index(index: EncodingIndex) -> EncodingType; 46 | // int 47 | // rb_enc_get_index(VALUE obj) 48 | pub fn rb_enc_get_index(obj: Value) -> EncodingIndex; 49 | // void 50 | // rb_enc_set_index(VALUE obj, int idx) 51 | pub fn rb_enc_set_index(obj: Value, encindex: EncodingIndex); 52 | // void 53 | // rb_enc_set_default_external(VALUE encoding) 54 | pub fn rb_enc_set_default_external(encoding: Value); 55 | // void 56 | // rb_enc_set_default_internal(VALUE encoding) 57 | pub fn rb_enc_set_default_internal(encoding: Value); 58 | // int 59 | // rb_filesystem_encindex(void) 60 | pub fn rb_filesystem_encindex() -> EncodingIndex; 61 | // int 62 | // rb_locale_encindex(void) 63 | pub fn rb_locale_encindex() -> EncodingIndex; 64 | // VALUE 65 | // rb_obj_encoding(VALUE obj) 66 | pub fn rb_obj_encoding(obj: Value) -> Value; 67 | // rb_encoding * 68 | // rb_to_encoding(VALUE enc) 69 | pub fn rb_to_encoding(enc: Value) -> EncodingType; 70 | // int 71 | // rb_to_encoding_index(VALUE enc) 72 | pub fn rb_to_encoding_index(obj: Value) -> EncodingIndex; 73 | // int 74 | // rb_usascii_encindex(void) 75 | pub fn rb_usascii_encindex() -> EncodingIndex; 76 | // int 77 | // rb_utf8_encindex(void) 78 | pub fn rb_utf8_encindex() -> EncodingIndex; 79 | // VALUE 80 | // rb_str_export_to_enc(VALUE str, rb_encoding *enc) 81 | pub fn rb_str_export_to_enc(str: Value, enc: EncodingType) -> Value; 82 | // VALUE 83 | // rb_str_encode(VALUE str, VALUE to, int ecflags, VALUE ecopts) 84 | pub fn rb_str_encode(str: Value, to: Value, ecflags: c_int, ecopts: Value) -> Value; 85 | // int 86 | // rb_econv_prepare_opts(VALUE opthash, VALUE *opts) 87 | pub fn rb_econv_prepare_opts(opthash: Value, opts: *const Value) -> c_int; 88 | // unsigned int 89 | // rb_enc_codepoint_len(const char *p, const char *e, int *len_p, rb_encoding *enc) 90 | pub fn rb_enc_codepoint_len( 91 | ptr: *const c_char, 92 | end: *const c_char, 93 | len_p: *mut c_int, 94 | enc: EncodingType, 95 | ) -> size_t; 96 | } 97 | 98 | pub unsafe fn coderange_set(obj: Value, code_range: InternalValue) { 99 | let basic: *mut RBasic = obj.value as _; 100 | (*basic).flags = ((*basic).flags & !(ENC_CODERANGE_MASK as InternalValue)) | code_range 101 | } 102 | 103 | pub unsafe fn coderange_clear(obj: Value) { 104 | coderange_set(obj, 0) 105 | } 106 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | binding::{ 3 | class::const_get, 4 | global::{rb_cObject, RubySpecialConsts}, 5 | }, 6 | rubysys::rproc::{rb_obj_is_method, rb_obj_is_proc}, 7 | types::{c_char, c_int, c_void, Argc, InternalValue, Value}, 8 | AnyObject, Boolean, Object, 9 | }; 10 | 11 | use std::{ 12 | ffi::{CStr, CString}, 13 | slice, 14 | }; 15 | 16 | pub unsafe fn cstr_to_string(str: *const c_char) -> String { 17 | CStr::from_ptr(str).to_string_lossy().into_owned() 18 | } 19 | 20 | pub unsafe fn cstr_to_str<'a>(str: *const c_char) -> &'a str { 21 | CStr::from_ptr(str).to_str().unwrap() 22 | } 23 | 24 | pub fn str_to_cstring(str: &str) -> CString { 25 | CString::new(str).unwrap() 26 | } 27 | 28 | pub fn bool_to_value(state: bool) -> Value { 29 | let internal_value = if state { 30 | RubySpecialConsts::True 31 | } else { 32 | RubySpecialConsts::False 33 | } as InternalValue; 34 | 35 | Value::from(internal_value) 36 | } 37 | 38 | #[inline] 39 | pub fn c_int_to_bool(int: c_int) -> bool { 40 | int != 0 41 | } 42 | 43 | #[inline] 44 | pub fn bool_to_c_int(state: bool) -> c_int { 45 | state as c_int 46 | } 47 | 48 | pub fn arguments_to_values(arguments: &[AnyObject]) -> Vec { 49 | arguments.as_ref().iter().map(Object::value).collect() 50 | } 51 | 52 | pub fn process_arguments(arguments: &[Value]) -> (Argc, *const Value) { 53 | (arguments.len() as Argc, arguments.as_ptr()) 54 | } 55 | 56 | pub fn option_to_slice(option: &Option) -> &[T] { 57 | match option { 58 | Some(v) => unsafe { slice::from_raw_parts(v, 1) }, 59 | None => &[], 60 | } 61 | } 62 | 63 | /// Converts a pointer to array of `AnyObject`s to `Vec`. 64 | /// 65 | /// This function is a helper for callbacks, do not use it directly. 66 | /// 67 | /// It will be moved to other struct, because it is not related to VM itself. 68 | /// 69 | /// # Examples 70 | /// 71 | /// ``` 72 | /// use rutie::types::Argc; 73 | /// use rutie::{AnyObject, Boolean, Class, Object, RString, util, VM}; 74 | /// 75 | /// #[no_mangle] 76 | /// pub extern fn string_eq(argc: Argc, argv: *const AnyObject, rtself: RString) -> Boolean { 77 | /// let argv = unsafe { util::parse_arguments(argc, argv) }; 78 | /// let other_string = argv[0].try_convert_to::().unwrap(); 79 | /// 80 | /// Boolean::new(rtself.to_str() == other_string.to_str()) 81 | /// } 82 | /// 83 | /// fn main() { 84 | /// # VM::init(); 85 | /// Class::from_existing("String").define_method("==", string_eq); 86 | /// } 87 | /// ``` 88 | pub unsafe fn parse_arguments(argc: Argc, arguments: *const AnyObject) -> Vec { 89 | unsafe { slice::from_raw_parts(arguments, argc as usize).to_vec() } 90 | } 91 | 92 | pub fn closure_to_ptr(mut func: F) -> *mut c_void 93 | where 94 | F: FnMut() -> R, 95 | { 96 | let wrap_return = move || { 97 | let r = func(); 98 | Box::into_raw(Box::new(r)) as *const c_void 99 | }; 100 | 101 | let fnbox = Box::new(wrap_return) as Box *const c_void>; 102 | 103 | Box::into_raw(Box::new(fnbox)) as *mut c_void 104 | } 105 | 106 | pub unsafe fn ptr_to_data(ptr: *mut c_void) -> R { 107 | *Box::from_raw(ptr as *mut R) 108 | } 109 | 110 | pub fn is_proc(obj: Value) -> bool { 111 | Boolean::from(unsafe { rb_obj_is_proc(obj) }).to_bool() 112 | } 113 | 114 | pub fn is_method(obj: Value) -> bool { 115 | Boolean::from(unsafe { rb_obj_is_method(obj) }).to_bool() 116 | } 117 | 118 | // Recurses to the deepest ruby object. 119 | // 120 | // Given `"A::B::C"` it will return the object instance of `C`. 121 | pub fn inmost_rb_object(klass: &str) -> Value { 122 | let object = unsafe { rb_cObject }; 123 | 124 | klass.split("::").fold(object.into(), const_get) 125 | } 126 | 127 | pub mod callback_call { 128 | use crate::types::{st_retval, CallbackMutPtr}; 129 | 130 | pub fn no_parameters R, R>(ptr: CallbackMutPtr) -> R { 131 | let f = ptr as *mut F; 132 | unsafe { (*f)() } 133 | } 134 | 135 | pub fn one_parameter R, A, R>(a: A, ptr: CallbackMutPtr) -> R { 136 | let f = ptr as *mut F; 137 | unsafe { (*f)(a) } 138 | } 139 | 140 | pub fn hash_foreach_callback( 141 | a: A, 142 | b: B, 143 | ptr: CallbackMutPtr, 144 | ) -> st_retval { 145 | let f = ptr as *mut F; 146 | unsafe { 147 | (*f)(a, b); 148 | } 149 | st_retval::Continue 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/class/thread.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | 3 | use crate::{binding::thread, types::Value}; 4 | 5 | #[cfg(unix)] 6 | use crate::types::RawFd; 7 | 8 | use crate::{AnyObject, Class, Object, VerifiedObject}; 9 | 10 | /// `Thread` 11 | #[derive(Debug)] 12 | #[repr(C)] 13 | pub struct Thread { 14 | value: Value, 15 | } 16 | 17 | impl Thread { 18 | /// Creates a new green thread. 19 | /// 20 | /// The returning value of the closure will be available as `#value` of the thread 21 | /// 22 | /// # Examples 23 | /// 24 | /// ``` 25 | /// use rutie::{Fixnum, Thread, VM}; 26 | /// # VM::init(); 27 | /// 28 | /// Thread::new(|| { 29 | /// let computation_result = 1 + 2; 30 | /// 31 | /// Fixnum::new(computation_result) 32 | /// }); 33 | /// ``` 34 | /// 35 | /// Ruby: 36 | /// 37 | /// ```ruby 38 | /// Thread.new do 39 | /// computation_result = 1 + 2 40 | /// 41 | /// computation_result 42 | /// end 43 | /// ``` 44 | pub fn new(func: F) -> Self 45 | where 46 | F: FnMut() -> R, 47 | R: Object, 48 | { 49 | Self::from(thread::create(func)) 50 | } 51 | 52 | /// Tells scheduler to switch to other threads while current thread is waiting for a 53 | /// readable event on the given file descriptor. 54 | /// 55 | /// # Examples 56 | /// 57 | /// ``` 58 | /// use std::os::unix::io::AsRawFd; 59 | /// use std::os::unix::net::UnixStream; 60 | /// 61 | /// use rutie::{Thread, VM}; 62 | /// # VM::init(); 63 | /// 64 | /// let (unix_socket, _) = UnixStream::pair().unwrap(); 65 | /// 66 | /// Thread::wait_fd(unix_socket.as_raw_fd()); 67 | /// ``` 68 | #[cfg(unix)] 69 | pub fn wait_fd(fd: RawFd) { 70 | thread::wait_fd(fd); 71 | } 72 | 73 | /// Release GVL for current thread. 74 | /// 75 | /// **Warning!** Due to MRI limitations, interaction with Ruby objects is not allowed while 76 | /// GVL is released, it may cause unexpected behaviour. 77 | /// [Read more at Ruby documentation](https://github.com/ruby/ruby/blob/2fc5210f31ad23463d7b0a0e36bcfbeee7b41b3e/thread.c#L1314-L1398) 78 | /// 79 | /// You should extract all the information from Ruby world before invoking 80 | /// `thread_call_without_gvl`. 81 | /// 82 | /// GVL will be re-acquired when the closure is finished. 83 | /// 84 | /// # Examples 85 | /// 86 | /// ```no_run 87 | /// use rutie::{class, methods, {Class, Fixnum, Object, Thread}}; 88 | /// 89 | /// class!(Calculator); 90 | /// 91 | /// methods!( 92 | /// Calculator, 93 | /// rtself, 94 | /// 95 | /// fn heavy_computation() -> Fixnum { 96 | /// let computation = || { 2 * 2 }; 97 | /// let unblocking_function = || {}; 98 | /// 99 | /// // release GVL for current thread until `computation` is completed 100 | /// let result = Thread::call_without_gvl( 101 | /// computation, 102 | /// Some(unblocking_function) 103 | /// ); 104 | /// 105 | /// // GVL is re-acquired, we can interact with Ruby-world 106 | /// Fixnum::new(result) 107 | /// } 108 | /// ); 109 | /// 110 | /// Class::new("Calculator", None).define(|klass| { 111 | /// klass.def("heavy_computation", heavy_computation); 112 | /// }); 113 | /// ``` 114 | pub fn call_without_gvl(func: F, unblock_func: Option) -> R 115 | where 116 | F: FnMut() -> R, 117 | G: FnMut(), 118 | { 119 | thread::call_without_gvl(func, unblock_func) 120 | } 121 | 122 | pub fn call_without_gvl2(func: F, unblock_func: Option) -> R 123 | where 124 | F: FnMut() -> R, 125 | G: FnMut(), 126 | { 127 | thread::call_without_gvl2(func, unblock_func) 128 | } 129 | 130 | pub fn call_with_gvl(func: F) -> R 131 | where 132 | F: FnMut() -> R, 133 | { 134 | thread::call_with_gvl(func) 135 | } 136 | } 137 | 138 | impl From for Thread { 139 | fn from(value: Value) -> Self { 140 | Thread { value } 141 | } 142 | } 143 | 144 | impl From for Value { 145 | fn from(val: Thread) -> Self { 146 | val.value 147 | } 148 | } 149 | 150 | impl From for AnyObject { 151 | fn from(val: Thread) -> Self { 152 | AnyObject::from(val.value) 153 | } 154 | } 155 | 156 | impl Object for Thread { 157 | #[inline] 158 | fn value(&self) -> Value { 159 | self.value 160 | } 161 | } 162 | 163 | impl VerifiedObject for Thread { 164 | fn is_correct_type(object: &T) -> bool { 165 | object.class() == Class::from_existing("Thread") 166 | } 167 | 168 | fn error_message() -> &'static str { 169 | "Error converting to Thread" 170 | } 171 | } 172 | 173 | impl PartialEq for Thread { 174 | fn eq(&self, other: &Self) -> bool { 175 | self.equals(other) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/class/traits/verified_object.rs: -------------------------------------------------------------------------------- 1 | use crate::{types::Value, NilClass, Object}; 2 | 3 | /// Interface for safe conversions between types 4 | /// 5 | /// This trait is required by `Object::convert_to()` function. 6 | /// 7 | /// All built-in types like `Hash`, `RString` and others implement it. 8 | /// 9 | /// **You should implement this trait for custom classes which you receive from Ruby 10 | /// if at least one of the following statements is false**: 11 | /// 12 | /// - you own the Ruby code which passes the object to Rust; 13 | /// - you are sure that the object always has correct type; 14 | /// - your Ruby code has a good test coverage. 15 | /// 16 | /// Various techniques can be used to check if the object has correct type: 17 | /// 18 | /// - check the class of the object; 19 | /// - check ancestors of the object's class; 20 | /// - check if object is one of built-in objects (when it is inherited from one of those); 21 | /// - use duck typing to check if object responds to required methods; 22 | /// - etc 23 | /// 24 | /// # Examples 25 | /// 26 | /// ``` 27 | /// #[macro_use] 28 | /// extern crate rutie; 29 | /// 30 | /// use rutie::types::ValueType; 31 | /// use rutie::{Class, Object, RString, VerifiedObject, VM}; 32 | /// 33 | /// // Check the class of the object 34 | /// class!(Server); 35 | /// 36 | /// impl VerifiedObject for Server { 37 | /// fn is_correct_type(object: &T) -> bool { 38 | /// object.class() == Class::from_existing("Server") 39 | /// } 40 | /// 41 | /// fn error_message() -> &'static str { 42 | /// "Error converting to Server" 43 | /// } 44 | /// } 45 | /// 46 | /// // Check presence of required methods (duck typing) 47 | /// class!(Request); 48 | /// 49 | /// methods!( 50 | /// Request, 51 | /// rtself, 52 | /// 53 | /// fn protocol() -> RString { RString::new_utf8("HTTP") } 54 | /// fn body() -> RString { RString::new_utf8("request body") } 55 | /// ); 56 | /// 57 | /// impl VerifiedObject for Request { 58 | /// fn is_correct_type(object: &T) -> bool { 59 | /// object.respond_to("protocol") && object.respond_to("body") 60 | /// } 61 | /// 62 | /// fn error_message() -> &'static str { 63 | /// "Error converting to Request" 64 | /// } 65 | /// } 66 | /// 67 | /// // Check if class inherits/includes some class or module 68 | /// class!(Response); 69 | /// 70 | /// impl VerifiedObject for Response { 71 | /// fn is_correct_type(object: &T) -> bool { 72 | /// object.class().ancestors().iter() 73 | /// .any(|class| *class == Class::from_existing("BasicResponse")) 74 | /// } 75 | /// 76 | /// fn error_message() -> &'static str { 77 | /// "Error converting to Response" 78 | /// } 79 | /// } 80 | /// 81 | /// // Check if class was inherited from built-in classes 82 | /// class!(Headers); 83 | /// 84 | /// impl VerifiedObject for Headers { 85 | /// fn is_correct_type(object: &T) -> bool { 86 | /// object.value().ty() == ValueType::Hash 87 | /// } 88 | /// 89 | /// fn error_message() -> &'static str { 90 | /// "Error converting to Headers" 91 | /// } 92 | /// } 93 | /// 94 | /// fn main() { 95 | /// # VM::init(); 96 | /// Class::new("Server", None); 97 | /// Class::new("Response", Some(&Class::new("BasicResponse", None))); 98 | /// Class::new("Headers", Some(&Class::from_existing("Hash"))); 99 | /// Class::new("Request", None).define(|klass| { 100 | /// klass.def("protocol", protocol); 101 | /// klass.def("body", body); 102 | /// }); 103 | /// 104 | /// // Create new instances of classes and convert them to `AnyObject`s 105 | /// // (make their type unknown) 106 | /// let server = Class::from_existing("Server").new_instance(&[]).to_any_object(); 107 | /// let request = Class::from_existing("Request").new_instance(&[]).to_any_object(); 108 | /// let response = Class::from_existing("Response").new_instance(&[]).to_any_object(); 109 | /// let headers = Class::from_existing("Headers").new_instance(&[]).to_any_object(); 110 | /// 111 | /// assert!(server.try_convert_to::().is_ok()); 112 | /// assert!(request.try_convert_to::().is_ok()); 113 | /// assert!(response.try_convert_to::().is_ok()); 114 | /// assert!(headers.try_convert_to::().is_ok()); 115 | /// 116 | /// // P.S. 117 | /// // The following is possible to compile, but the program will panic 118 | /// // if you perform any actions with these objects. 119 | /// // Try to avoid unsafe conversions. 120 | /// let bad_request = unsafe { server.to::() }; 121 | /// let bad_server = unsafe { headers.to::() }; 122 | /// } 123 | /// ``` 124 | pub trait VerifiedObject: Object { 125 | fn is_correct_type(object: &T) -> bool; 126 | fn error_message() -> &'static str; 127 | } 128 | 129 | impl VerifiedObject for Option 130 | where 131 | Option: From, 132 | { 133 | fn is_correct_type(object: &T) -> bool { 134 | ::is_correct_type(object) 135 | || ::is_correct_type(object) 136 | } 137 | fn error_message() -> &'static str { 138 | ::error_message() 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/binding/class.rs: -------------------------------------------------------------------------------- 1 | use libc::c_void; 2 | 3 | use crate::types::CallbackPtr; 4 | use crate::util::bool_to_value; 5 | use crate::util::c_int_to_bool; 6 | use crate::{ 7 | binding::symbol, 8 | rubysys::{class, typed_data}, 9 | typed_data::DataTypeWrapper, 10 | types::{Callback, Value}, 11 | util, Object, 12 | }; 13 | 14 | pub fn define_class(name: &str, superclass: Value) -> Value { 15 | let name = util::str_to_cstring(name); 16 | 17 | unsafe { class::rb_define_class(name.as_ptr(), superclass) } 18 | } 19 | 20 | pub fn define_nested_class(outer: Value, name: &str, superclass: Value) -> Value { 21 | let name = util::str_to_cstring(name); 22 | 23 | unsafe { class::rb_define_class_under(outer, name.as_ptr(), superclass) } 24 | } 25 | 26 | pub fn const_get(klass: Value, name: &str) -> Value { 27 | unsafe { class::rb_const_get(klass, symbol::internal_id(name)) } 28 | } 29 | 30 | pub fn const_set(klass: Value, name: &str, value: Value) { 31 | let name = util::str_to_cstring(name); 32 | 33 | unsafe { class::rb_define_const(klass, name.as_ptr(), value) }; 34 | } 35 | 36 | pub fn object_class(object: Value) -> Value { 37 | unsafe { class::rb_obj_class(object) } 38 | } 39 | 40 | pub fn superclass(klass: Value) -> Value { 41 | unsafe { class::rb_class_superclass(klass) } 42 | } 43 | 44 | pub fn singleton_class(object: Value) -> Value { 45 | unsafe { class::rb_singleton_class(object) } 46 | } 47 | 48 | pub fn ancestors(klass: Value) -> Value { 49 | unsafe { class::rb_mod_ancestors(klass) } 50 | } 51 | 52 | pub fn new_instance(klass: Value, arguments: &[Value]) -> Value { 53 | let (argc, argv) = util::process_arguments(arguments); 54 | 55 | unsafe { class::rb_class_new_instance(argc, argv, klass) } 56 | } 57 | 58 | pub fn instance_variable_get(object: Value, name: &str) -> Value { 59 | unsafe { class::rb_ivar_get(object, symbol::internal_id(name)) } 60 | } 61 | 62 | pub fn instance_variable_set(object: Value, name: &str, value: Value) -> Value { 63 | unsafe { class::rb_ivar_set(object, symbol::internal_id(name), value) } 64 | } 65 | 66 | pub fn define_attribute(object: Value, name: &str, reader: bool, writer: bool) { 67 | let name = util::str_to_cstring(name); 68 | let reader = util::bool_to_c_int(reader); 69 | let writer = util::bool_to_c_int(writer); 70 | 71 | unsafe { class::rb_define_attr(object, name.as_ptr(), reader, writer) }; 72 | } 73 | 74 | pub fn respond_to(object: Value, method: &str) -> bool { 75 | let result = unsafe { class::rb_respond_to(object, symbol::internal_id(method)) }; 76 | 77 | c_int_to_bool(result) 78 | } 79 | 80 | pub fn define_method(klass: Value, name: &str, callback: Callback) { 81 | let name = util::str_to_cstring(name); 82 | 83 | unsafe { 84 | class::rb_define_method(klass, name.as_ptr(), callback as CallbackPtr, -1) 85 | } 86 | } 87 | 88 | pub fn define_private_method( 89 | klass: Value, 90 | name: &str, 91 | callback: Callback, 92 | ) { 93 | let name = util::str_to_cstring(name); 94 | 95 | unsafe { 96 | class::rb_define_private_method(klass, name.as_ptr(), callback as CallbackPtr, -1); 97 | } 98 | } 99 | 100 | pub fn define_singleton_method( 101 | klass: Value, 102 | name: &str, 103 | callback: Callback, 104 | ) { 105 | let name = util::str_to_cstring(name); 106 | 107 | unsafe { 108 | class::rb_define_singleton_method(klass, name.as_ptr(), callback as CallbackPtr, -1); 109 | } 110 | } 111 | 112 | pub fn wrap_data(klass: Value, data: T, wrapper: &dyn DataTypeWrapper) -> Value { 113 | let data = Box::into_raw(Box::new(data)) as *mut c_void; 114 | 115 | unsafe { typed_data::rb_data_typed_object_wrap(klass, data, wrapper.data_type()) } 116 | } 117 | 118 | // TODO: Skipped the lint, but this function takes an immutable reference and returns a mutable 119 | // one. Changing the signature is a breaking change. What do we do? 120 | #[allow(clippy::mut_from_ref)] 121 | pub fn get_data(object: Value, wrapper: &dyn DataTypeWrapper) -> &mut T { 122 | unsafe { 123 | let data = typed_data::rb_check_typeddata(object, wrapper.data_type()); 124 | 125 | &mut *(data as *mut T) 126 | } 127 | } 128 | 129 | #[allow(dead_code)] 130 | pub fn is_frozen(object: Value) -> Value { 131 | unsafe { class::rb_obj_frozen_p(object) } 132 | } 133 | 134 | pub fn freeze(object: Value) -> Value { 135 | unsafe { class::rb_obj_freeze(object) } 136 | } 137 | 138 | pub fn is_eql(object1: Value, object2: Value) -> Value { 139 | let result = unsafe { class::rb_eql(object1, object2) }; 140 | // In 3.1 and earlier we get Qtrue/Qfalse 141 | if cfg!(ruby_lte_3_1) { 142 | result.into() 143 | } else { 144 | // After 3.1 we get TRUE/true|FALSE/false 145 | bool_to_value(c_int_to_bool(result)) 146 | } 147 | } 148 | 149 | pub fn equals(object1: Value, object2: Value) -> Value { 150 | unsafe { class::rb_equal(object1, object2) } 151 | } 152 | 153 | 154 | #[cfg(test)] 155 | mod tests { 156 | use rb_sys_test_helpers::ruby_test; 157 | use super::*; 158 | use crate::Integer; 159 | 160 | #[ruby_test] 161 | fn test_class_eql() { 162 | let obj1 = Integer::new(1); 163 | let obj2 = Integer::new(1); 164 | let obj3 = Integer::new(2); 165 | let obj4 = Integer::new(3); 166 | 167 | assert!(is_eql(obj1.into(), obj2.into()).is_true()); 168 | assert!(is_eql(obj3.into(), obj4.into()).is_false()); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/rubysys/value.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::From, mem}; 2 | 3 | use super::{ 4 | constant, 5 | types::{InternalValue, RBasic}, 6 | }; 7 | 8 | const SPECIAL_SHIFT: usize = 8; 9 | 10 | use rb_sys::{ruby_special_consts, ruby_value_type}; 11 | 12 | // I assume pointer width is handled by rb_sys; 13 | pub enum RubySpecialConsts { 14 | False = ruby_special_consts::RUBY_Qfalse as isize, 15 | True = ruby_special_consts::RUBY_Qtrue as isize, 16 | Nil = ruby_special_consts::RUBY_Qnil as isize, 17 | Undef = ruby_special_consts::RUBY_Qundef as isize, 18 | } 19 | 20 | pub enum RubySpecialFlags { 21 | ImmediateMask = ruby_special_consts::RUBY_IMMEDIATE_MASK as isize, 22 | FixnumFlag = ruby_special_consts::RUBY_FIXNUM_FLAG as isize, 23 | FlonumMask = ruby_special_consts::RUBY_FLONUM_MASK as isize, 24 | FlonumFlag = ruby_special_consts::RUBY_FLONUM_FLAG as isize, 25 | SymbolFlag = ruby_special_consts::RUBY_SYMBOL_FLAG as isize, 26 | } 27 | 28 | #[derive(Debug, PartialEq)] 29 | #[repr(u32)] 30 | pub enum ValueType { 31 | None = ruby_value_type::RUBY_T_NONE as u32, 32 | 33 | Object = ruby_value_type::RUBY_T_OBJECT as u32, 34 | Class = ruby_value_type::RUBY_T_CLASS as u32, 35 | Module = ruby_value_type::RUBY_T_MODULE as u32, 36 | Float = ruby_value_type::RUBY_T_FLOAT as u32, 37 | RString = ruby_value_type::RUBY_T_STRING as u32, 38 | Regexp = ruby_value_type::RUBY_T_REGEXP as u32, 39 | Array = ruby_value_type::RUBY_T_ARRAY as u32, 40 | Hash = ruby_value_type::RUBY_T_HASH as u32, 41 | Struct = ruby_value_type::RUBY_T_STRUCT as u32, 42 | Bignum = ruby_value_type::RUBY_T_BIGNUM as u32, 43 | File = ruby_value_type::RUBY_T_FILE as u32, 44 | Data = ruby_value_type::RUBY_T_DATA as u32, 45 | Match = ruby_value_type::RUBY_T_MATCH as u32, 46 | Complex = ruby_value_type::RUBY_T_COMPLEX as u32, 47 | Rational = ruby_value_type::RUBY_T_RATIONAL as u32, 48 | 49 | Nil = ruby_value_type::RUBY_T_NIL as u32, 50 | True = ruby_value_type::RUBY_T_TRUE as u32, 51 | False = ruby_value_type::RUBY_T_FALSE as u32, 52 | Symbol = ruby_value_type::RUBY_T_SYMBOL as u32, 53 | Fixnum = ruby_value_type::RUBY_T_FIXNUM as u32, 54 | Undef = ruby_value_type::RUBY_T_UNDEF as u32, 55 | 56 | IMemo = ruby_value_type::RUBY_T_IMEMO as u32, 57 | Node = ruby_value_type::RUBY_T_NODE as u32, 58 | IClass = ruby_value_type::RUBY_T_ICLASS as u32, 59 | Zombie = ruby_value_type::RUBY_T_ZOMBIE as u32, 60 | 61 | Mask = ruby_value_type::RUBY_T_MASK as u32, 62 | } 63 | 64 | #[repr(C)] 65 | #[derive(Copy, Clone, Debug, PartialEq)] 66 | pub struct Value { 67 | pub value: InternalValue, 68 | } 69 | 70 | impl Value { 71 | pub fn is_true(&self) -> bool { 72 | self.value == (RubySpecialConsts::True as InternalValue) 73 | } 74 | 75 | pub fn is_false(&self) -> bool { 76 | self.value == (RubySpecialConsts::False as InternalValue) 77 | } 78 | 79 | pub fn is_nil(&self) -> bool { 80 | self.value == (RubySpecialConsts::Nil as InternalValue) 81 | } 82 | 83 | pub fn is_node(&self) -> bool { 84 | self.builtin_type() == ValueType::Node 85 | } 86 | 87 | pub fn is_undef(&self) -> bool { 88 | self.value == (RubySpecialConsts::Undef as InternalValue) 89 | } 90 | 91 | pub fn is_symbol(&self) -> bool { 92 | (self.value & !((!0) << SPECIAL_SHIFT)) == (RubySpecialFlags::SymbolFlag as InternalValue) 93 | } 94 | 95 | pub fn is_fixnum(&self) -> bool { 96 | (self.value & (RubySpecialFlags::FixnumFlag as InternalValue)) != 0 97 | } 98 | 99 | pub fn is_flonum(&self) -> bool { 100 | (self.value & (RubySpecialFlags::FlonumMask as InternalValue)) 101 | == (RubySpecialFlags::FlonumFlag as InternalValue) 102 | } 103 | 104 | pub fn is_frozen(&self) -> bool { 105 | !self.is_fl_able() || self.is_obj_frozen_raw() 106 | } 107 | 108 | pub fn ty(&self) -> ValueType { 109 | if self.is_immediate() { 110 | if self.is_fixnum() { 111 | ValueType::Fixnum 112 | } else if self.is_flonum() { 113 | ValueType::Float 114 | } else if self.is_true() { 115 | ValueType::True 116 | } else if self.is_symbol() { 117 | ValueType::Symbol 118 | } else if self.is_undef() { 119 | ValueType::Undef 120 | } else { 121 | self.builtin_type() 122 | } 123 | } else if !self.is_test() { 124 | if self.is_nil() { 125 | ValueType::Nil 126 | } else if self.is_false() { 127 | ValueType::False 128 | } else { 129 | self.builtin_type() 130 | } 131 | } else { 132 | self.builtin_type() 133 | } 134 | } 135 | 136 | fn is_fl_able(&self) -> bool { 137 | !self.is_special_const() && !self.is_node() 138 | } 139 | 140 | fn is_special_const(&self) -> bool { 141 | self.is_immediate() || !self.is_test() 142 | } 143 | 144 | fn is_immediate(&self) -> bool { 145 | (self.value & (RubySpecialFlags::ImmediateMask as InternalValue)) != 0 146 | } 147 | 148 | fn is_test(&self) -> bool { 149 | (self.value & !(RubySpecialConsts::Nil as InternalValue)) != 0 150 | } 151 | 152 | fn is_obj_frozen_raw(&self) -> bool { 153 | unsafe { 154 | let basic: *const RBasic = self.value as *const _; 155 | (*basic).flags & (constant::FL_FREEZE as InternalValue) != 0 156 | } 157 | } 158 | 159 | fn builtin_type(&self) -> ValueType { 160 | unsafe { 161 | let basic: *const RBasic = self.value as *const _; 162 | let masked = (*basic).flags & (ValueType::Mask as InternalValue); 163 | mem::transmute(masked as u32) 164 | } 165 | } 166 | } 167 | 168 | impl From for Value { 169 | fn from(internal_value: InternalValue) -> Self { 170 | Value { 171 | value: internal_value, 172 | } 173 | } 174 | } 175 | 176 | impl From for Value { 177 | fn from(value: i32) -> Self { 178 | Value { 179 | value: value as InternalValue, 180 | } 181 | } 182 | } 183 | 184 | impl From for u64 { 185 | fn from(value: Value) -> Self { 186 | value.value 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/class/encoding.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | binding::encoding, 3 | types::{Value, ValueType}, 4 | AnyException, AnyObject, Class, Exception, NilClass, Object, RString, VerifiedObject, 5 | }; 6 | 7 | #[derive(Debug)] 8 | #[repr(C)] 9 | pub struct Encoding { 10 | value: Value, 11 | } 12 | 13 | impl Encoding { 14 | /// Creates a UTF-8 instance of `Encoding`. 15 | /// 16 | /// # Examples 17 | /// 18 | /// ``` 19 | /// use rutie::{Encoding, VM}; 20 | /// # VM::init(); 21 | /// 22 | /// Encoding::utf8(); 23 | /// ``` 24 | /// 25 | /// Ruby: 26 | /// 27 | /// ```ruby 28 | /// Encoding::UTF_8 29 | /// ``` 30 | pub fn utf8() -> Self { 31 | Self::from(encoding::utf8_encoding()) 32 | } 33 | 34 | /// Creates a US-ASCII instance of `Encoding`. 35 | /// 36 | /// # Examples 37 | /// 38 | /// ``` 39 | /// use rutie::{Encoding, VM}; 40 | /// # VM::init(); 41 | /// 42 | /// Encoding::us_ascii(); 43 | /// ``` 44 | /// 45 | /// Ruby: 46 | /// 47 | /// ```ruby 48 | /// Encoding::US_ASCII 49 | /// ``` 50 | pub fn us_ascii() -> Self { 51 | Self::from(encoding::usascii_encoding()) 52 | } 53 | 54 | /// Creates a new instance of `Encoding` from the default external encoding. 55 | /// 56 | /// # Examples 57 | /// 58 | /// ``` 59 | /// use rutie::{Encoding, VM}; 60 | /// # VM::init(); 61 | /// 62 | /// Encoding::default_external(); 63 | /// ``` 64 | /// 65 | /// Ruby: 66 | /// 67 | /// ```ruby 68 | /// Encoding.default_external 69 | /// ``` 70 | pub fn default_external() -> Self { 71 | Self::from(encoding::default_external()) 72 | } 73 | 74 | /// Creates an instance of `Ok(Encoding)` from the default internal encoding 75 | /// if there is one, otherwise it returns `Err(NilClass)`. 76 | /// 77 | /// # Examples 78 | /// 79 | /// ``` 80 | /// use rutie::{Encoding, VM}; 81 | /// # VM::init(); 82 | /// 83 | /// Encoding::default_internal(); 84 | /// ``` 85 | /// 86 | /// Ruby: 87 | /// 88 | /// ```ruby 89 | /// Encoding.default_internal 90 | /// ``` 91 | pub fn default_internal() -> Result { 92 | let result = encoding::default_internal(); 93 | 94 | if result.is_nil() { 95 | Err(NilClass::from(result)) 96 | } else { 97 | Ok(Self::from(result)) 98 | } 99 | } 100 | 101 | /// Returns encoding name. 102 | /// 103 | /// # Examples 104 | /// 105 | /// ``` 106 | /// use rutie::{RString, Encoding, VM}; 107 | /// # VM::init(); 108 | /// 109 | /// let enc = Encoding::utf8(); 110 | /// 111 | /// assert_eq!(enc.name(), "UTF-8") 112 | /// ``` 113 | /// 114 | /// Ruby: 115 | /// 116 | /// ```ruby 117 | /// enc = Encoding::UTF_8 118 | /// 119 | /// enc.name == "UTF-8" 120 | /// ``` 121 | pub fn name(&self) -> String { 122 | let name = unsafe { self.send("name", &[]) }; 123 | 124 | RString::from(name.value()).to_string() 125 | } 126 | 127 | /// Find an `Ok(Encoding)` for given string name or return an `Err(AnyException)`. 128 | /// 129 | /// # Examples 130 | /// 131 | /// ``` 132 | /// use rutie::{VM, Encoding}; 133 | /// # VM::init(); 134 | /// 135 | /// let encoding = Encoding::find("UTF-8"); 136 | /// 137 | /// match encoding { 138 | /// Ok(enc) => assert_eq!(enc.name(), "UTF-8"), 139 | /// Err(_) => unreachable!() 140 | /// } 141 | /// ``` 142 | /// 143 | /// Ruby: 144 | /// 145 | /// ```ruby 146 | /// encoding = Encoding.find("UTF-8") 147 | /// 148 | /// encoding.name == "UTF-8" 149 | /// ``` 150 | /// 151 | /// The following is an example where a Ruby exception object of `ArgumentError` is returned. 152 | /// 153 | /// ``` 154 | /// use rutie::{VM, Encoding, Exception}; 155 | /// # VM::init(); 156 | /// 157 | /// let encoding = Encoding::find("UTF8"); 158 | /// 159 | /// match encoding { 160 | /// Ok(_) => unreachable!(), 161 | /// Err(e) => assert_eq!(e.message(), "unknown encoding name - UTF8") 162 | /// } 163 | /// ``` 164 | pub fn find(s: &str) -> Result { 165 | let idx = encoding::find_encoding_index(s); 166 | 167 | if idx < 0 { 168 | Err(AnyException::new( 169 | "ArgumentError", 170 | Some(&format!("unknown encoding name - {}", s)), 171 | )) 172 | } else { 173 | Ok(Encoding::from(encoding::from_encoding_index(idx))) 174 | } 175 | } 176 | 177 | /// Returns an instance of `Ok(Encoding)` if the objects are 178 | /// compatible encodings, otherwise it returns `Err(NilClass)`. 179 | /// 180 | /// # Examples 181 | /// 182 | /// ``` 183 | /// use rutie::{Encoding, VM, RString, NilClass}; 184 | /// # VM::init(); 185 | /// 186 | /// let utf8 = RString::new_utf8("asdf"); 187 | /// let us_ascii= RString::new_usascii_unchecked("qwerty"); 188 | /// 189 | /// let result = Encoding::is_compatible(&utf8, &us_ascii); 190 | /// 191 | /// assert!(result.is_ok()); 192 | /// 193 | /// let result = Encoding::is_compatible(&utf8, &NilClass::new()); 194 | /// 195 | /// assert!(result.is_err()); 196 | /// ``` 197 | pub fn is_compatible(obj1: &impl Object, obj2: &impl Object) -> Result { 198 | let result = encoding::compatible_encoding(obj1.value(), obj2.value()); 199 | 200 | if result.is_nil() { 201 | Err(NilClass::from(result)) 202 | } else { 203 | Ok(Self::from(result)) 204 | } 205 | } 206 | } 207 | 208 | impl Default for Encoding { 209 | fn default() -> Self { 210 | Encoding::default_external() 211 | } 212 | } 213 | 214 | impl From for Encoding { 215 | fn from(value: Value) -> Self { 216 | Encoding { value } 217 | } 218 | } 219 | 220 | impl From for Value { 221 | fn from(val: Encoding) -> Self { 222 | val.value 223 | } 224 | } 225 | 226 | impl From for AnyObject { 227 | fn from(val: Encoding) -> Self { 228 | AnyObject::from(val.value) 229 | } 230 | } 231 | 232 | impl Object for Encoding { 233 | #[inline] 234 | fn value(&self) -> Value { 235 | self.value 236 | } 237 | } 238 | 239 | impl VerifiedObject for Encoding { 240 | fn is_correct_type(object: &T) -> bool { 241 | object.value().ty() == ValueType::Class 242 | && Class::from_existing("Encoding").case_equals(object) 243 | } 244 | 245 | fn error_message() -> &'static str { 246 | "Error converting to Encoding" 247 | } 248 | } 249 | 250 | impl PartialEq for Encoding { 251 | fn eq(&self, other: &Self) -> bool { 252 | self.equals(other) 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/binding/vm.rs: -------------------------------------------------------------------------------- 1 | use std::ptr; 2 | 3 | use crate::{ 4 | binding::symbol::internal_id, 5 | rubysys::{thread, vm}, 6 | types::{c_int, c_void, CallbackPtr, Value, VmPointer}, 7 | util, AnyObject, 8 | }; 9 | 10 | pub fn block_proc() -> Value { 11 | unsafe { vm::rb_block_proc() } 12 | } 13 | 14 | pub fn is_block_given() -> bool { 15 | let result = unsafe { vm::rb_block_given_p() }; 16 | 17 | util::c_int_to_bool(result) 18 | } 19 | 20 | pub fn yield_object(value: Value) -> Value { 21 | unsafe { vm::rb_yield(value) } 22 | } 23 | 24 | pub fn yield_splat(values: Value) -> Value { 25 | unsafe { vm::rb_yield_splat(values) } 26 | } 27 | 28 | pub fn init() { 29 | unsafe { 30 | // Ancient knowledge, that solves windows VM startup crashes. 31 | #[cfg(windows)] 32 | { 33 | let mut argc = 0; 34 | let mut argv: [*mut std::os::raw::c_char; 0] = []; 35 | let mut argv = argv.as_mut_ptr(); 36 | rb_sys::rb_w32_sysinit(&mut argc, &mut argv); 37 | } 38 | vm::ruby_init(); 39 | } 40 | } 41 | 42 | pub fn init_loadpath() { 43 | unsafe { 44 | vm::ruby_init_loadpath(); 45 | } 46 | } 47 | 48 | pub fn require(name: &str) { 49 | let name = util::str_to_cstring(name); 50 | 51 | unsafe { 52 | vm::rb_require(name.as_ptr()); 53 | } 54 | } 55 | 56 | pub fn call_method(receiver: Value, method: &str, arguments: &[Value]) -> Value { 57 | let (argc, argv) = util::process_arguments(arguments); 58 | let method_id = internal_id(method); 59 | 60 | // TODO: Update the signature of `rb_funcallv` in ruby-sys to receive an `Option` 61 | unsafe { vm::rb_funcallv(receiver, method_id, argc, argv as *const _) } 62 | } 63 | 64 | pub fn call_public_method(receiver: Value, method: &str, arguments: &[Value]) -> Value { 65 | let (argc, argv) = util::process_arguments(arguments); 66 | let method_id = internal_id(method); 67 | 68 | // TODO: Update the signature of `rb_funcallv_public` in ruby-sys to receive an `Option` 69 | unsafe { vm::rb_funcallv_public(receiver, method_id, argc, argv as *const _) } 70 | } 71 | 72 | pub fn call_super(arguments: &[Value]) -> Value { 73 | let (argc, argv) = util::process_arguments(arguments); 74 | 75 | unsafe { vm::rb_call_super(argc, argv as *const _) } 76 | } 77 | 78 | // "evaluation can raise an exception." 79 | pub fn eval_string(string: &str) -> Value { 80 | let s = util::str_to_cstring(string); 81 | 82 | unsafe { vm::rb_eval_string(s.as_ptr()) } 83 | } 84 | 85 | pub fn eval_string_protect(string: &str) -> Result { 86 | let s = util::str_to_cstring(string); 87 | let mut state = 0; 88 | let value = unsafe { vm::rb_eval_string_protect(s.as_ptr(), &mut state as *mut c_int) }; 89 | if state == 0 { 90 | Ok(value) 91 | } else { 92 | Err(state) 93 | } 94 | } 95 | 96 | pub fn raise(exception: Value, message: &str) { 97 | let message = util::str_to_cstring(message); 98 | 99 | unsafe { 100 | vm::rb_raise(exception, message.as_ptr()); 101 | } 102 | } 103 | 104 | pub fn raise_ex(exception: Value) { 105 | unsafe { 106 | vm::rb_exc_raise(exception); 107 | } 108 | } 109 | 110 | pub fn errinfo() -> Value { 111 | unsafe { vm::rb_errinfo() } 112 | } 113 | 114 | pub fn set_errinfo(err: Value) { 115 | unsafe { vm::rb_set_errinfo(err) } 116 | } 117 | 118 | #[allow(dead_code)] 119 | pub fn thread_call_without_gvl(func: F, unblock_func: Option) -> R 120 | where 121 | F: FnMut() -> R, 122 | G: FnMut(), 123 | { 124 | unsafe { 125 | let ptr = if let Some(ubf) = unblock_func { 126 | thread::rb_thread_call_without_gvl( 127 | callbox as CallbackPtr, 128 | util::closure_to_ptr(func), 129 | callbox as CallbackPtr, 130 | util::closure_to_ptr(ubf), 131 | ) 132 | } else { 133 | thread::rb_thread_call_without_gvl( 134 | callbox as CallbackPtr, 135 | util::closure_to_ptr(func), 136 | ptr::null(), 137 | ptr::null(), 138 | ) 139 | }; 140 | 141 | util::ptr_to_data(ptr) 142 | } 143 | } 144 | 145 | #[allow(dead_code)] 146 | pub fn thread_call_without_gvl2(func: F, unblock_func: Option) -> R 147 | where 148 | F: FnMut() -> R, 149 | G: FnMut(), 150 | { 151 | unsafe { 152 | let ptr = if let Some(ubf) = unblock_func { 153 | thread::rb_thread_call_without_gvl2( 154 | callbox as CallbackPtr, 155 | util::closure_to_ptr(func), 156 | callbox as CallbackPtr, 157 | util::closure_to_ptr(ubf), 158 | ) 159 | } else { 160 | thread::rb_thread_call_without_gvl2( 161 | callbox as CallbackPtr, 162 | util::closure_to_ptr(func), 163 | ptr::null(), 164 | ptr::null(), 165 | ) 166 | }; 167 | 168 | util::ptr_to_data(ptr) 169 | } 170 | } 171 | 172 | #[allow(dead_code)] 173 | pub fn thread_call_with_gvl(func: F) -> R 174 | where 175 | F: FnMut() -> R, 176 | { 177 | unsafe { 178 | let ptr = 179 | thread::rb_thread_call_with_gvl(callbox as CallbackPtr, util::closure_to_ptr(func)); 180 | 181 | util::ptr_to_data(ptr) 182 | } 183 | } 184 | 185 | extern "C" fn callbox(boxptr: *mut c_void) -> *const c_void { 186 | let mut fnbox: Box *const c_void>> = 187 | unsafe { Box::from_raw(boxptr as *mut Box *const c_void>) }; 188 | 189 | fnbox() 190 | } 191 | 192 | use crate::util::callback_call::no_parameters as callback_protect; 193 | 194 | pub fn protect(func: F) -> Result 195 | where 196 | F: FnMut() -> AnyObject, 197 | { 198 | let mut state = 0; 199 | let value = unsafe { 200 | let closure = &func as *const F as *const c_void; 201 | vm::rb_protect( 202 | callback_protect:: as CallbackPtr, 203 | closure as CallbackPtr, 204 | &mut state as *mut c_int, 205 | ) 206 | }; 207 | if state == 0 { 208 | Ok(value.into()) 209 | } else { 210 | Err(state) 211 | } 212 | } 213 | 214 | pub fn exit(status: i32) { 215 | unsafe { vm::rb_exit(status as c_int) } 216 | } 217 | 218 | pub fn abort(arguments: &[Value]) { 219 | let (argc, argv) = util::process_arguments(arguments); 220 | 221 | unsafe { vm::rb_f_abort(argc, argv as *const _) }; 222 | } 223 | 224 | use crate::util::callback_call::one_parameter as at_exit_callback; 225 | 226 | pub fn at_exit(func: F) 227 | where 228 | F: FnMut(VmPointer), 229 | { 230 | unsafe { 231 | let closure = &func as *const F as *const c_void; 232 | vm::rb_set_end_proc( 233 | at_exit_callback:: as CallbackPtr, 234 | closure as CallbackPtr 235 | ) 236 | }; 237 | } 238 | -------------------------------------------------------------------------------- /src/rubysys/thread.rs: -------------------------------------------------------------------------------- 1 | use super::types::{c_int, c_void, CallbackPtr, Value}; 2 | 3 | #[cfg(unix)] 4 | use super::types::RawFd; 5 | 6 | // rb_thread_call_without_gvl - permit concurrent/parallel execution. 7 | // rb_thread_call_without_gvl2 - permit concurrent/parallel execution 8 | // without interrupt process. 9 | // 10 | // rb_thread_call_without_gvl() does: 11 | // (1) Check interrupts. 12 | // (2) release GVL. 13 | // Other Ruby threads may run in parallel. 14 | // (3) call func with data1 15 | // (4) acquire GVL. 16 | // Other Ruby threads can not run in parallel any more. 17 | // (5) Check interrupts. 18 | // 19 | // rb_thread_call_without_gvl2() does: 20 | // (1) Check interrupt and return if interrupted. 21 | // (2) release GVL. 22 | // (3) call func with data1 and a pointer to the flags. 23 | // (4) acquire GVL. 24 | // 25 | // If another thread interrupts this thread (Thread#kill, signal delivery, 26 | // VM-shutdown request, and so on), `ubf()' is called (`ubf()' means 27 | // "un-blocking function"). `ubf()' should interrupt `func()' execution by 28 | // toggling a cancellation flag, canceling the invocation of a call inside 29 | // `func()' or similar. Note that `ubf()' may not be called with the GVL. 30 | // 31 | // There are built-in ubfs and you can specify these ubfs: 32 | // 33 | // * RUBY_UBF_IO: ubf for IO operation 34 | // * RUBY_UBF_PROCESS: ubf for process operation 35 | // 36 | // However, we can not guarantee our built-in ubfs interrupt your `func()' 37 | // correctly. Be careful to use rb_thread_call_without_gvl(). If you don't 38 | // provide proper ubf(), your program will not stop for Control+C or other 39 | // shutdown events. 40 | // 41 | // "Check interrupts" on above list means checking asynchronous 42 | // interrupt events (such as Thread#kill, signal delivery, VM-shutdown 43 | // request, and so on) and calling corresponding procedures 44 | // (such as `trap' for signals, raise an exception for Thread#raise). 45 | // If `func()' finished and received interrupts, you may skip interrupt 46 | // checking. For example, assume the following func() it reads data from file. 47 | // 48 | // read_func(...) { 49 | // // (a) before read 50 | // read(buffer); // (b) reading 51 | // // (c) after read 52 | // } 53 | // 54 | // If an interrupt occurs at (a) or (b), then `ubf()' cancels this 55 | // `read_func()' and interrupts are checked. However, if an interrupt occurs 56 | // at (c), after *read* operation is completed, checking interrupts is harmful 57 | // because it causes irrevocable side-effect, the read data will vanish. To 58 | // avoid such problem, the `read_func()' should be used with 59 | // `rb_thread_call_without_gvl2()'. 60 | // 61 | // If `rb_thread_call_without_gvl2()' detects interrupt, it returns 62 | // immediately. This function does not show when the execution was interrupted. 63 | // For example, there are 4 possible timing (a), (b), (c) and before calling 64 | // read_func(). You need to record progress of a read_func() and check 65 | // the progress after `rb_thread_call_without_gvl2()'. You may need to call 66 | // `rb_thread_check_ints()' correctly or your program can not process proper 67 | // process such as `trap' and so on. 68 | // 69 | // NOTE: You can not execute most of Ruby C API and touch Ruby 70 | // objects in `func()' and `ubf()', including raising an 71 | // exception, because current thread doesn't acquire GVL 72 | // (it causes synchronization problems). If you need to 73 | // call ruby functions either use rb_thread_call_with_gvl() 74 | // or read source code of C APIs and confirm safety by 75 | // yourself. 76 | // 77 | // NOTE: In short, this API is difficult to use safely. I recommend you 78 | // use other ways if you have. We lack experiences to use this API. 79 | // Please report your problem related on it. 80 | // 81 | // NOTE: Releasing GVL and re-acquiring GVL may be expensive operations 82 | // for a short running `func()'. Be sure to benchmark and use this 83 | // mechanism when `func()' consumes enough time. 84 | // 85 | // Safe C API: 86 | // * rb_thread_interrupted() - check interrupt flag 87 | // * ruby_xmalloc(), ruby_xrealloc(), ruby_xfree() - 88 | // they will work without GVL, and may acquire GVL when GC is needed. 89 | // 90 | extern "C" { 91 | // void * 92 | // rb_thread_call_without_gvl(void *(*func)(void *data), void *data1, 93 | // rb_unblock_function_t *ubf, void *data2) 94 | pub fn rb_thread_call_without_gvl( 95 | func: CallbackPtr, 96 | args: *const c_void, 97 | unblock_func: CallbackPtr, 98 | unblock_args: *const c_void, 99 | ) -> *mut c_void; 100 | 101 | // void * 102 | // rb_thread_call_without_gvl2(void *(*func)(void *), void *data1, 103 | // rb_unblock_function_t *ubf, void *data2) 104 | pub fn rb_thread_call_without_gvl2( 105 | func: CallbackPtr, 106 | args: *const c_void, 107 | unblock_func: CallbackPtr, 108 | unblock_args: *const c_void, 109 | ) -> *mut c_void; 110 | 111 | // rb_thread_call_with_gvl - re-enter the Ruby world after GVL release. 112 | // 113 | // After releasing GVL using 114 | // rb_thread_call_without_gvl() you can not access Ruby values or invoke 115 | // methods. If you need to access Ruby you must use this function 116 | // rb_thread_call_with_gvl(). 117 | // 118 | // This function rb_thread_call_with_gvl() does: 119 | // (1) acquire GVL. 120 | // (2) call passed function `func'. 121 | // (3) release GVL. 122 | // (4) return a value which is returned at (2). 123 | // 124 | // NOTE: You should not return Ruby object at (2) because such Object 125 | // will not be marked. 126 | // 127 | // NOTE: If an exception is raised in `func', this function DOES NOT 128 | // protect (catch) the exception. If you have any resources 129 | // which should free before throwing exception, you need use 130 | // rb_protect() in `func' and return a value which represents 131 | // exception was raised. 132 | // 133 | // NOTE: This function should not be called by a thread which was not 134 | // created as Ruby thread (created by Thread.new or so). In other 135 | // words, this function *DOES NOT* associate or convert a NON-Ruby 136 | // thread to a Ruby thread. 137 | // 138 | // void * 139 | // rb_thread_call_with_gvl(void *(*func)(void *), void *data1) 140 | pub fn rb_thread_call_with_gvl(func: CallbackPtr, args: *const c_void) -> *mut c_void; 141 | 142 | // VALUE 143 | // rb_thread_create(VALUE (*fn)(ANYARGS), void *arg) 144 | pub fn rb_thread_create( 145 | function: extern "C" fn(*mut c_void) -> Value, 146 | data: *mut c_void, 147 | ) -> Value; 148 | 149 | // void 150 | // rb_thread_wait_fd(int fd) 151 | #[cfg(unix)] 152 | pub fn rb_thread_wait_fd(fd: RawFd); 153 | 154 | // This function can be called in blocking region. 155 | // 156 | // int 157 | // rb_thread_interrupted(VALUE thval) 158 | pub fn rb_thread_interrupted(thread: Value) -> c_int; 159 | } 160 | -------------------------------------------------------------------------------- /src/class/gc.rs: -------------------------------------------------------------------------------- 1 | use crate::{binding::gc, Object, Symbol}; 2 | 3 | /// Garbage collection 4 | pub struct GC; 5 | 6 | impl GC { 7 | /// Notify memory usage to the GC engine by extension libraries, to trigger GC 8 | /// This is useful when you wrap large rust objects using wrap_data, 9 | /// when you do so, ruby is unaware of the allocated memory and might not run GC 10 | /// 11 | /// # Examples 12 | /// 13 | /// ``` 14 | /// use rutie::{GC, VM}; 15 | /// # VM::init(); 16 | /// 17 | /// 18 | /// GC::adjust_memory_usage(25_000); // Tell ruby that we somehow allocated 25_000 bytes of mem 19 | /// GC::adjust_memory_usage(-15_000); // Tell ruby that freed 15_000 bytes of mem 20 | /// ``` 21 | pub fn adjust_memory_usage(diff: isize) { 22 | gc::adjust_memory_usage(diff) 23 | } 24 | 25 | /// The number of times GC occurred. 26 | /// 27 | /// It returns the number of times GC occurred since the process started. 28 | /// 29 | /// # Examples 30 | /// 31 | /// ``` 32 | /// use rutie::{GC, VM}; 33 | /// # VM::init(); 34 | /// 35 | /// GC::count(); 36 | /// ``` 37 | pub fn count() -> usize { 38 | gc::count() 39 | } 40 | 41 | /// Disable the garbage collector 42 | /// 43 | /// # Examples 44 | /// 45 | /// ``` 46 | /// use rutie::{GC, VM}; 47 | /// # VM::init(); 48 | /// 49 | /// let _ = GC::disable(); 50 | /// ``` 51 | pub fn disable() -> bool { 52 | gc::disable().is_true() 53 | } 54 | 55 | /// Enable the garbage collector 56 | /// 57 | /// # Examples 58 | /// 59 | /// ``` 60 | /// use rutie::{GC, VM}; 61 | /// # VM::init(); 62 | /// 63 | /// let _ = GC::enable(); 64 | /// ``` 65 | pub fn enable() -> bool { 66 | gc::enable().is_true() 67 | } 68 | 69 | /// Forcibly GC object. 70 | /// 71 | /// # Examples 72 | /// 73 | /// ``` 74 | /// use rutie::{RString, GC, VM}; 75 | /// # VM::init(); 76 | /// 77 | /// let obj = RString::new_utf8("asdf"); 78 | /// 79 | /// GC::force_recycle(obj); 80 | /// ``` 81 | pub fn force_recycle(object: impl Object) { 82 | gc::force_recycle(object.value()) 83 | } 84 | 85 | /// Check if object is marked 86 | /// 87 | /// CAUTION: THIS FUNCTION IS ENABLED *ONLY BEFORE* SWEEPING. 88 | /// This function is only for GC_END_MARK timing. 89 | /// 90 | /// # Examples 91 | /// 92 | /// ``` 93 | /// use rutie::{RString, GC, VM}; 94 | /// # VM::init(); 95 | /// 96 | /// let obj = RString::new_utf8("asdf"); 97 | /// 98 | /// GC::mark(&obj); 99 | /// assert!(unsafe {GC::is_marked(&obj) }, "Object was not marked"); 100 | /// ``` 101 | /// 102 | #[cfg(not(ruby_gte_3_0))] 103 | pub unsafe fn is_marked(object: &impl Object) -> bool { 104 | gc::is_marked(object.value()) 105 | } 106 | 107 | /// Mark an object for Ruby to avoid garbage collecting item. 108 | /// 109 | /// If the wrapped struct in Rust references Ruby objects, then 110 | /// you'll have to mark those in the mark callback you are passing 111 | /// to wrapped struct. 112 | /// 113 | /// # Examples 114 | /// 115 | /// ``` 116 | /// use rutie::{RString, GC, VM}; 117 | /// # VM::init(); 118 | /// 119 | /// let object = RString::new_utf8("1"); 120 | /// 121 | /// GC::mark(&object); 122 | /// ``` 123 | #[cfg(not(ruby_gte_3_0))] 124 | pub fn mark(object: &impl Object) { 125 | gc::mark(object.value()); 126 | } 127 | 128 | /// Mark all of the object from `start` to `end` of the array for the GC. 129 | /// 130 | /// # Examples 131 | /// 132 | /// ``` 133 | /// use rutie::{RString, GC, VM, AnyObject}; 134 | /// # VM::init(); 135 | /// 136 | /// let arr = [ 137 | /// RString::new_utf8("1"), 138 | /// RString::new_utf8("2"), 139 | /// RString::new_utf8("3"), 140 | /// RString::new_utf8("4"), 141 | /// ]; 142 | /// 143 | /// GC::mark_locations(&arr); 144 | /// ``` 145 | #[cfg(not(ruby_gte_3_0))] 146 | pub fn mark_locations(range: &[impl Object]) { 147 | for object in range { 148 | GC::mark_maybe(object) 149 | } 150 | } 151 | 152 | /// Maybe mark an object for Ruby to avoid garbage collecting item. 153 | /// 154 | /// If the wrapped struct in Rust references Ruby objects, then 155 | /// you'll have to mark those in the mark callback you are passing 156 | /// to wrapped struct. 157 | /// 158 | /// # Examples 159 | /// 160 | /// ``` 161 | /// use rutie::{RString, GC, VM}; 162 | /// # VM::init(); 163 | /// 164 | /// let object = RString::new_utf8("1"); 165 | /// 166 | /// GC::mark_maybe(&object); 167 | /// ``` 168 | #[cfg(not(ruby_gte_3_0))] 169 | pub fn mark_maybe(object: &impl Object) { 170 | gc::mark_maybe(object.value()); 171 | } 172 | 173 | /// Registers the objects address with the GC 174 | /// 175 | /// # Examples 176 | /// 177 | /// ``` 178 | /// use rutie::{RString, GC, VM}; 179 | /// # VM::init(); 180 | /// 181 | /// let object = RString::new_utf8("1"); 182 | /// 183 | /// GC::register(&object); 184 | /// ``` 185 | pub fn register(object: &impl Object) { 186 | gc::register(object.value()) 187 | } 188 | 189 | /// Mark an object as in use for Ruby to avoid garbage collecting item. 190 | /// 191 | /// If the wrapped struct in Rust references Ruby objects, then 192 | /// you'll have to mark those in the mark callback you are passing 193 | /// to wrapped struct. 194 | /// 195 | /// # Examples 196 | /// 197 | /// ``` 198 | /// use rutie::{RString, GC, VM}; 199 | /// # VM::init(); 200 | /// 201 | /// let object = RString::new_utf8("1"); 202 | /// 203 | /// GC::register_mark(&object); 204 | /// ``` 205 | pub fn register_mark(object: &impl Object) { 206 | gc::register_mark(object.value()); 207 | } 208 | 209 | /// Start the garbage collector 210 | /// 211 | /// # Examples 212 | /// 213 | /// ``` 214 | /// use rutie::{GC, VM}; 215 | /// # VM::init(); 216 | /// 217 | /// GC::start(); 218 | /// ``` 219 | pub fn start() { 220 | gc::start() 221 | } 222 | 223 | /// Get the GC stats for a specific key 224 | /// 225 | /// Note: Will panic if provided an invalid key. 226 | /// 227 | /// # Examples 228 | /// 229 | /// ``` 230 | /// use rutie::{GC, VM}; 231 | /// # VM::init(); 232 | /// 233 | /// let result = GC::stat("heap_allocated_pages"); 234 | /// ``` 235 | pub fn stat(key: &str) -> usize { 236 | let key = Symbol::new(key); 237 | 238 | gc::stat(key.value()) 239 | } 240 | 241 | /// Unregisters the objects address with the GC 242 | /// 243 | /// # Examples 244 | /// 245 | /// ``` 246 | /// use rutie::{RString, GC, VM}; 247 | /// # VM::init(); 248 | /// 249 | /// let object = RString::new_utf8("1"); 250 | /// 251 | /// GC::unregister(&object); 252 | /// ``` 253 | pub fn unregister(object: &impl Object) { 254 | gc::unregister(object.value()) 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/class/traits/exception.rs: -------------------------------------------------------------------------------- 1 | use crate::{binding::vm, util, AnyObject, Array, Object, RString}; 2 | 3 | /// Descendants of class Exception are used to communicate between Kernel#raise 4 | /// and rescue statements in `begin ... end` blocks. Exception objects carry 5 | /// information about the exception – its type (the exception's class name), an 6 | /// optional descriptive string, and optional traceback information. Exception 7 | /// subclasses may add additional information like NameError#name. 8 | /// 9 | /// Programs may make subclasses of Exception, typically of StandardError or 10 | /// RuntimeError, to provide custom classes and add additional information. 11 | /// See the subclass list below for defaults for `raise` and `rescue`. 12 | pub trait Exception: Object { 13 | /// Construct a new Exception object, optionally passing in a message. 14 | /// 15 | /// # Examples 16 | /// ``` 17 | /// use rutie::{AnyException, Exception, Object, VM}; 18 | /// # VM::init(); 19 | /// 20 | /// assert_eq!( 21 | /// AnyException::new("StandardError", None).to_s(), 22 | /// "StandardError" 23 | /// ); 24 | /// ``` 25 | /// 26 | /// A nested exception 27 | /// 28 | /// ``` 29 | /// use rutie::{AnyException, Exception, Object, VM, Class}; 30 | /// # VM::init(); 31 | /// 32 | /// let mut klass = Class::new("MyGem", None); 33 | /// let se = Class::from_existing("StandardError"); 34 | /// let _ = klass.define_nested_class("MyError", Some(&se)); 35 | /// 36 | /// assert_eq!( 37 | /// AnyException::new("MyGem::MyError", None).to_s(), 38 | /// "MyGem::MyError" 39 | /// ); 40 | /// ``` 41 | fn new(class: &str, msg: Option<&str>) -> Self { 42 | let class = util::inmost_rb_object(class); 43 | let msg = msg.map(|s| RString::new_utf8(s).value()); 44 | 45 | Self::from(vm::call_method(class, "new", util::option_to_slice(&msg))) 46 | } 47 | 48 | /// With no argument, or if the argument is the same as the receiver, 49 | /// return the receiver. Otherwise, create a new exception object of 50 | /// the same class as the receiver, but with a message equal 51 | /// to `string.to_str`. 52 | /// 53 | /// # Examples 54 | /// ``` 55 | /// use rutie::{AnyException, Exception, Object, VM}; 56 | /// # VM::init(); 57 | /// 58 | /// assert_eq!( 59 | /// AnyException::new("StandardError", Some("something went wrong")).exception(None), 60 | /// AnyException::new("StandardError", Some("something went wrong")) 61 | /// ); 62 | /// ``` 63 | fn exception(&self, string: Option<&str>) -> Self { 64 | let string = string.map(|s| RString::new_utf8(s).value()); 65 | 66 | Self::from(vm::call_method( 67 | self.value(), 68 | "exception", 69 | util::option_to_slice(&string), 70 | )) 71 | } 72 | 73 | /// Returns any backtrace associated with the exception. The 74 | /// backtrace is an array of strings, each containing either 75 | /// “filename:lineNo: in `method''' or “filename:lineNo.'' 76 | /// 77 | /// # Examples 78 | /// ``` 79 | /// use rutie::{AnyException, Exception, Object, VM, RString}; 80 | /// # VM::init(); 81 | /// 82 | /// let x = AnyException::new("StandardError", Some("something went wrong")); 83 | /// 84 | /// assert!(x.backtrace().is_none()); 85 | /// ``` 86 | fn backtrace(&self) -> Option { 87 | let result = vm::call_method(self.value(), "backtrace", &[]); 88 | 89 | if result.is_nil() { 90 | return None; 91 | } 92 | 93 | Some(Array::from(result)) 94 | } 95 | 96 | /// Returns any backtrace associated with the exception. This 97 | /// method is similar to #backtrace, but the backtrace is an 98 | /// array of Thread::Backtrace::Location. 99 | /// 100 | /// Now, this method is not affected by #set_backtrace. 101 | /// 102 | /// # Examples 103 | /// ``` 104 | /// use rutie::{AnyException, Exception, Object, VM, RString}; 105 | /// # VM::init(); 106 | /// 107 | /// let x = AnyException::new("StandardError", Some("something went wrong")); 108 | /// 109 | /// assert!(x.backtrace_locations().is_none()); 110 | /// ``` 111 | fn backtrace_locations(&self) -> Option { 112 | let result = vm::call_method(self.value(), "backtrace_locations", &[]); 113 | 114 | if result.is_nil() { 115 | return None; 116 | } 117 | 118 | Some(Array::from(result)) 119 | } 120 | 121 | /// Returns the previous exception at the time this 122 | /// exception was raised. This is useful for wrapping exceptions 123 | /// and retaining the original exception information. 124 | /// 125 | /// # Examples 126 | /// ``` 127 | /// use rutie::{AnyException, Exception, Object, VM, RString}; 128 | /// # VM::init(); 129 | /// 130 | /// let x = AnyException::new("StandardError", Some("something went wrong")); 131 | /// 132 | /// assert!(x.cause().is_none()); 133 | /// ``` 134 | fn cause(&self) -> Option { 135 | let result = vm::call_method(self.value(), "cause", &[]); 136 | 137 | if result.is_nil() { 138 | return None; 139 | } 140 | 141 | Some(Self::from(result)) 142 | } 143 | 144 | /// Return this exception's class name and message 145 | /// 146 | /// # Examples 147 | /// ``` 148 | /// use rutie::{AnyException, Exception, Object, VM}; 149 | /// # VM::init(); 150 | /// 151 | /// assert_eq!( 152 | /// AnyException::new("StandardError", Some("oops")).inspect(), 153 | /// "#" 154 | /// ); 155 | /// ``` 156 | fn inspect(&self) -> String { 157 | RString::from(vm::call_method(self.value(), "inspect", &[])).to_string() 158 | } 159 | 160 | /// Returns the result of invoking `exception.to_s`. Normally this 161 | /// returns the exception's message or name. 162 | /// 163 | /// # Examples 164 | /// ``` 165 | /// use rutie::{AnyException, Exception, Object, VM}; 166 | /// # VM::init(); 167 | /// 168 | /// assert_eq!( 169 | /// AnyException::new("StandardError", Some("oops")).message(), 170 | /// "oops" 171 | /// ); 172 | /// ``` 173 | fn message(&self) -> String { 174 | RString::from(vm::call_method(self.value(), "message", &[])).to_string() 175 | } 176 | 177 | /// Sets the backtrace information associated with exc. The backtrace 178 | /// must be an array of String objects or a single String in the format 179 | /// described in #backtrace. 180 | /// 181 | /// # Examples 182 | /// ``` 183 | /// use rutie::{AnyException, Exception, Object, VM, RString, Array}; 184 | /// # VM::init(); 185 | /// 186 | /// let x = AnyException::new("StandardError", Some("something went wrong")); 187 | /// 188 | /// let mut arr = Array::new(); 189 | /// arr.push(RString::new_utf8("prog.rb:10")); 190 | /// 191 | /// x.set_backtrace(arr.to_any_object()); 192 | /// 193 | /// assert_eq!( 194 | /// x.backtrace(). 195 | /// unwrap(). 196 | /// pop(). 197 | /// try_convert_to::(). 198 | /// unwrap(). 199 | /// to_string(), 200 | /// "prog.rb:10" 201 | /// ); 202 | /// ``` 203 | fn set_backtrace(&self, backtrace: AnyObject) -> Option { 204 | let result = vm::call_method(self.value(), "set_backtrace", &[backtrace.value()]); 205 | 206 | if result.is_nil() { 207 | return None; 208 | } 209 | 210 | Some(Array::from(result)) 211 | } 212 | 213 | /// Returns exception's message (or the name of the exception if no message is set). 214 | /// 215 | /// # Examples 216 | /// ``` 217 | /// use rutie::{AnyException, Exception, Object, VM}; 218 | /// # VM::init(); 219 | /// 220 | /// assert_eq!( 221 | /// AnyException::new("StandardError", Some("oops")).to_s(), 222 | /// "oops" 223 | /// ); 224 | /// ``` 225 | fn to_s(&self) -> String { 226 | RString::from(vm::call_method(self.value(), "to_s", &[])).to_string() 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/class/enumerator.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | 3 | use crate::{types::Value, AnyException, AnyObject, Array, Class, Object, VerifiedObject}; 4 | 5 | /// `Enumerator` 6 | #[derive(Debug)] 7 | #[repr(C)] 8 | pub struct Enumerator { 9 | value: Value, 10 | } 11 | 12 | impl Enumerator { 13 | /// Advances the iterator and returns the next value. 14 | /// 15 | /// Returns [`Err`] when iteration is finished. 16 | /// 17 | /// # Examples 18 | /// 19 | /// ``` 20 | /// use rutie::{Array, Fixnum, Object, VM, VerifiedObject, Enumerator}; 21 | /// # VM::init(); 22 | /// 23 | /// let mut iter = Array::new().push(Fixnum::new(2)).push(Fixnum::new(1)).to_enum(); 24 | /// 25 | /// // A call to next() returns the next value... 26 | /// assert_eq!(Ok(Fixnum::new(2).to_any_object()), iter.next()); 27 | /// assert_eq!(Ok(Fixnum::new(1).to_any_object()), iter.next()); 28 | /// 29 | /// // ... and then Err once it's over. 30 | /// assert!(iter.next().is_err(), "not error!"); 31 | /// 32 | /// // More calls will always return Err. 33 | /// assert!(iter.next().is_err(), "not error!"); 34 | /// assert!(iter.next().is_err(), "not error!"); 35 | /// ``` 36 | #[allow(clippy::should_implement_trait)] // We don't want to implement Rust's Iterator here. 37 | pub fn next(&mut self) -> Result { 38 | self.protect_send("next", &[]) 39 | } 40 | 41 | /// Advances the iterator and returns the next values. 42 | /// 43 | /// Returns [`Err`] when iteration is finished. 44 | /// 45 | /// # Examples 46 | /// 47 | /// ``` 48 | /// use rutie::{Array, Fixnum, Object, VM, VerifiedObject, Enumerator}; 49 | /// # VM::init(); 50 | /// 51 | /// let mut array = Array::with_capacity(2); 52 | /// 53 | /// array.push(Fixnum::new(1)); 54 | /// array.push(Fixnum::new(2)); 55 | /// 56 | /// let mut iter = array.to_enum(); 57 | /// 58 | /// // A call to next_values() returns the next values... 59 | /// let mut result1 = Array::with_capacity(1); 60 | /// result1.push(Fixnum::new(1)); 61 | /// assert_eq!(Ok(result1), iter.next_values()); 62 | /// let mut result2 = Array::with_capacity(1); 63 | /// result2.push(Fixnum::new(2)); 64 | /// assert_eq!(Ok(result2), iter.next_values()); 65 | /// 66 | /// // ... and then Err once it's over. 67 | /// assert!(iter.next_values().is_err(), "not error!"); 68 | /// 69 | /// // More calls will always retirn Err. 70 | /// assert!(iter.next_values().is_err(), "not error!"); 71 | /// assert!(iter.next_values().is_err(), "not error!"); 72 | /// ``` 73 | pub fn next_values(&mut self) -> Result { 74 | self.protect_send("next_values", &[]) 75 | .map(|v| Array::from(v.value())) 76 | } 77 | 78 | /// Peeks into the iterator and returns the next value. 79 | /// 80 | /// Returns [`Err`] when iteration is finished. 81 | /// 82 | /// # Examples 83 | /// 84 | /// ``` 85 | /// use rutie::{Array, Fixnum, Object, VM, VerifiedObject, Enumerator}; 86 | /// # VM::init(); 87 | /// 88 | /// let mut iter = Array::new().push(Fixnum::new(2)).push(Fixnum::new(1)).to_enum(); 89 | /// 90 | /// // A call to peek() returns the next value without progressing the iteration 91 | /// assert_eq!(Ok(Fixnum::new(2).to_any_object()), iter.peek()); 92 | /// assert_eq!(Ok(Fixnum::new(2).to_any_object()), iter.peek()); 93 | /// ``` 94 | pub fn peek(&self) -> Result { 95 | self.protect_send("peek", &[]) 96 | } 97 | 98 | /// Peeks into the iterator and returns the next values. 99 | /// 100 | /// Returns [`Err`] when iteration is finished. 101 | /// 102 | /// # Examples 103 | /// 104 | /// ``` 105 | /// use rutie::{Array, Fixnum, Object, VM, VerifiedObject, Enumerator}; 106 | /// # VM::init(); 107 | /// 108 | /// let mut array = Array::with_capacity(2); 109 | /// 110 | /// array.push(Fixnum::new(1)); 111 | /// array.push(Fixnum::new(2)); 112 | /// 113 | /// let mut iter = array.to_enum(); 114 | /// 115 | /// // A call to peek_values() returns the next values without progressing the iteration 116 | /// let mut result1 = Array::with_capacity(1); 117 | /// result1.push(Fixnum::new(1)); 118 | /// assert_eq!(Ok(result1.dup()), iter.peek_values()); 119 | /// assert_eq!(Ok(result1), iter.peek_values()); 120 | /// ``` 121 | pub fn peek_values(&self) -> Result { 122 | self.protect_send("peek_values", &[]) 123 | .map(|v| Array::from(v.value())) 124 | } 125 | 126 | /// Rewind the iteration back to the beginning. 127 | /// 128 | /// Returns [`Err`] when iteration is finished. 129 | /// 130 | /// # Examples 131 | /// 132 | /// ``` 133 | /// use rutie::{Array, Fixnum, Object, VM, VerifiedObject, Enumerator}; 134 | /// # VM::init(); 135 | /// 136 | /// let mut iter = Array::new().push(Fixnum::new(2)).push(Fixnum::new(1)).to_enum(); 137 | /// 138 | /// // A call to next() returns the next value... 139 | /// assert_eq!(Ok(Fixnum::new(2).to_any_object()), iter.next()); 140 | /// assert_eq!(Ok(Fixnum::new(1).to_any_object()), iter.next()); 141 | /// assert!(iter.next().is_err(), "not error!"); 142 | /// 143 | /// iter.rewind(); 144 | /// 145 | /// // A call to next() returns the next value... 146 | /// assert_eq!(Ok(Fixnum::new(2).to_any_object()), iter.next()); 147 | /// assert_eq!(Ok(Fixnum::new(1).to_any_object()), iter.next()); 148 | /// assert!(iter.next().is_err(), "not error!"); 149 | /// ``` 150 | pub fn rewind(&mut self) -> &mut Self { 151 | unsafe { self.send("rewind", &[]) }; 152 | self 153 | } 154 | 155 | /// Feed a return value back in to internal yield inside enumerator. 156 | /// 157 | /// # Examples 158 | /// 159 | /// ``` 160 | /// use rutie::{Array, Fixnum, Object, VM, VerifiedObject, Enumerator, Class}; 161 | /// # VM::init(); 162 | /// 163 | /// let mut e_iter = VM::eval("[1,2,3].map").unwrap(). 164 | /// try_convert_to::().unwrap(); 165 | /// 166 | /// assert_eq!(Ok(Fixnum::new(1).to_any_object()), e_iter.next()); 167 | /// e_iter.feed(Fixnum::new(999).to_any_object()); 168 | /// assert_eq!(Ok(Fixnum::new(2).to_any_object()), e_iter.next()); 169 | /// e_iter.feed(Fixnum::new(888).to_any_object()); 170 | /// assert_eq!(Ok(Fixnum::new(3).to_any_object()), e_iter.next()); 171 | /// e_iter.feed(Fixnum::new(777).to_any_object()); 172 | /// 173 | /// match e_iter.next() { 174 | /// Ok(_) => unreachable!(), 175 | /// Err(e) => { 176 | /// let mut expected = Array::with_capacity(3); 177 | /// expected.push(Fixnum::new(999).to_any_object()); 178 | /// expected.push(Fixnum::new(888).to_any_object()); 179 | /// expected.push(Fixnum::new(777).to_any_object()); 180 | /// 181 | /// assert!(Class::from_existing("StopIteration").case_equals(&e)); 182 | /// assert_eq!(expected.to_any_object(), unsafe { e.send("result", &[]) }); 183 | /// }, 184 | /// } 185 | /// ``` 186 | /// 187 | /// Ruby: 188 | /// 189 | /// ```ruby 190 | /// e = [1,2,3].map 191 | /// p e.next #=> 1 192 | /// e.feed 999 193 | /// p e.next #=> 2 194 | /// e.feed 888 195 | /// p e.next #=> 3 196 | /// e.feed 777 197 | /// begin 198 | /// e.next 199 | /// rescue StopIteration 200 | /// p $!.result #=> [999, 888, 777] 201 | /// end 202 | /// ``` 203 | pub fn feed(&mut self, object: AnyObject) -> Result<(), AnyException> { 204 | self.protect_send("feed", &[object]).map(|_| ()) 205 | } 206 | } 207 | 208 | impl From for Enumerator { 209 | fn from(value: Value) -> Self { 210 | Enumerator { value } 211 | } 212 | } 213 | 214 | impl From for Value { 215 | fn from(val: Enumerator) -> Self { 216 | val.value 217 | } 218 | } 219 | 220 | impl From for AnyObject { 221 | fn from(val: Enumerator) -> Self { 222 | AnyObject::from(val.value) 223 | } 224 | } 225 | 226 | impl Object for Enumerator { 227 | #[inline] 228 | fn value(&self) -> Value { 229 | self.value 230 | } 231 | } 232 | 233 | impl VerifiedObject for Enumerator { 234 | fn is_correct_type(object: &T) -> bool { 235 | Class::from_existing("Enumerator").case_equals(object) 236 | } 237 | 238 | fn error_message() -> &'static str { 239 | "Error converting to Enumerator" 240 | } 241 | } 242 | 243 | impl PartialEq for Enumerator { 244 | fn eq(&self, other: &Self) -> bool { 245 | self.equals(other) 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/class/hash.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::From, default::Default}; 2 | 3 | use crate::{ 4 | binding::hash, 5 | types::{Value, ValueType}, 6 | AnyObject, Object, VerifiedObject, 7 | }; 8 | 9 | /// `Hash` 10 | #[derive(Debug)] 11 | #[repr(C)] 12 | pub struct Hash { 13 | value: Value, 14 | } 15 | 16 | impl Hash { 17 | /// Creates a new instance of empty `Hash`. 18 | /// 19 | /// # Examples 20 | /// 21 | /// ``` 22 | /// use rutie::{Hash, VM}; 23 | /// # VM::init(); 24 | /// 25 | /// Hash::new(); 26 | /// ``` 27 | /// 28 | /// Ruby: 29 | /// 30 | /// ```ruby 31 | /// {} 32 | /// ``` 33 | pub fn new() -> Self { 34 | Self::from(hash::new()) 35 | } 36 | 37 | /// Retrieves an `AnyObject` from element stored at `key` key. 38 | /// 39 | /// # Examples 40 | /// 41 | /// ``` 42 | /// use rutie::{Fixnum, Hash, Object, Symbol, VM}; 43 | /// # VM::init(); 44 | /// 45 | /// let mut hash = Hash::new(); 46 | /// 47 | /// hash.store(Symbol::new("key"), Fixnum::new(1)); 48 | /// 49 | /// assert_eq!(hash.at(&Symbol::new("key")).try_convert_to::(), Ok(Fixnum::new(1))); 50 | /// ``` 51 | /// 52 | /// Ruby: 53 | /// 54 | /// ```ruby 55 | /// hash = {} 56 | /// hash[:key] = 1 57 | /// 58 | /// hash[:key] == 1 59 | /// ``` 60 | pub fn at(&self, key: &T) -> AnyObject { 61 | let result = hash::aref(self.value(), key.value()); 62 | 63 | AnyObject::from(result) 64 | } 65 | 66 | /// Associates the `value` with the `key`. 67 | /// 68 | /// Both `key` and `value` must be types which implement `Object` trait. 69 | /// 70 | /// # Examples 71 | /// 72 | /// ``` 73 | /// use rutie::{Fixnum, Hash, Object, Symbol, VM}; 74 | /// # VM::init(); 75 | /// 76 | /// let mut hash = Hash::new(); 77 | /// 78 | /// hash.store(Symbol::new("key"), Fixnum::new(1)); 79 | /// 80 | /// assert_eq!(hash.at(&Symbol::new("key")).try_convert_to::(), Ok(Fixnum::new(1))); 81 | /// ``` 82 | /// 83 | /// Ruby: 84 | /// 85 | /// ```ruby 86 | /// hash = {} 87 | /// hash[:key] = 1 88 | /// 89 | /// hash[:key] == 1 90 | /// ``` 91 | pub fn store(&mut self, key: K, value: V) -> AnyObject { 92 | let result = hash::aset(self.value(), key.value(), value.value()); 93 | 94 | AnyObject::from(result) 95 | } 96 | 97 | /// Retrieves the length of the hash. 98 | /// 99 | /// # Examples 100 | /// 101 | /// ``` 102 | /// use rutie::{Hash, Fixnum, Symbol, VM}; 103 | /// # VM::init(); 104 | /// 105 | /// let mut hash = Hash::new(); 106 | /// 107 | /// hash.store(Symbol::new("key1"), Fixnum::new(1)); 108 | /// assert_eq!(hash.length(), 1); 109 | /// 110 | /// hash.store(Symbol::new("key2"), Fixnum::new(2)); 111 | /// assert_eq!(hash.length(), 2); 112 | /// ``` 113 | /// 114 | /// Ruby: 115 | /// 116 | /// ```ruby 117 | /// hash = {} 118 | /// 119 | /// hash[:key1] = 1 120 | /// hash.length == 1 121 | /// 122 | /// hash[:key2] = 2 123 | /// hash.length == 2 124 | /// ``` 125 | pub fn length(&self) -> usize { 126 | hash::length(self.value()) as usize 127 | } 128 | 129 | /// Removes all key-value pairs. 130 | /// 131 | /// # Examples 132 | /// 133 | /// ``` 134 | /// use rutie::{Hash, Fixnum, Symbol, VM}; 135 | /// # VM::init(); 136 | /// 137 | /// let mut hash = Hash::new(); 138 | /// 139 | /// hash.store(Symbol::new("key1"), Fixnum::new(1)); 140 | /// hash.store(Symbol::new("key2"), Fixnum::new(2)); 141 | /// assert_eq!(hash.length(), 2); 142 | /// 143 | /// hash.clear(); 144 | /// assert_eq!(hash.length(), 0); 145 | /// ``` 146 | /// 147 | /// Ruby: 148 | /// 149 | /// ```ruby 150 | /// hash = {} 151 | /// 152 | /// hash[:key1] = 1 153 | /// hash[:key2] = 2 154 | /// hash.length == 2 155 | /// 156 | /// hash.clear 157 | /// 158 | /// hash.length == 0 159 | /// ``` 160 | pub fn clear(&self) { 161 | hash::clear(self.value()) 162 | } 163 | 164 | /// Deletes the key-value pair and returns the value from hash whose key is equal to key. If 165 | /// the key is not found, it returns nil. 166 | /// 167 | /// `key` must be a type which implements the `Object` trait. 168 | /// 169 | /// # Examples 170 | /// 171 | /// ``` 172 | /// use rutie::{Fixnum, Hash, Object, Symbol, VM}; 173 | /// # VM::init(); 174 | /// 175 | /// let mut hash = Hash::new(); 176 | /// 177 | /// hash.store(Symbol::new("key1"), Fixnum::new(1)); 178 | /// hash.store(Symbol::new("key2"), Fixnum::new(2)); 179 | /// assert_eq!(hash.length(), 2); 180 | /// 181 | /// let deleted = hash.delete(Symbol::new("key2")); 182 | /// assert_eq!(hash.length(), 1); 183 | /// assert_eq!(deleted.try_convert_to::(), Ok(Fixnum::new(2))); 184 | /// ``` 185 | /// 186 | /// Ruby: 187 | /// 188 | /// ```ruby 189 | /// hash = {} 190 | /// 191 | /// hash[:key1] = 1 192 | /// hash[:key2] = 2 193 | /// hash.length == 2 194 | /// 195 | /// deleted = hash.delete(:key2) 196 | /// 197 | /// hash.length == 1 198 | /// deleted == 2 199 | /// ``` 200 | pub fn delete(&mut self, key: K) -> AnyObject { 201 | let result = hash::delete(self.value(), key.value()); 202 | 203 | AnyObject::from(result) 204 | } 205 | 206 | /// Runs a closure for each `key` and `value` pair. 207 | /// 208 | /// Key and value have `AnyObject` type. 209 | /// 210 | /// # Examples 211 | /// 212 | /// ``` 213 | /// use rutie::{Fixnum, Hash, Object, Symbol, VM}; 214 | /// # VM::init(); 215 | /// 216 | /// let mut hash = Hash::new(); 217 | /// 218 | /// hash.store(Symbol::new("first_key"), Fixnum::new(1)); 219 | /// hash.store(Symbol::new("second_key"), Fixnum::new(2)); 220 | /// 221 | /// let mut doubled_values: Vec = Vec::new(); 222 | /// 223 | /// hash.each(|_key, value| { 224 | /// if let Ok(value) = value.try_convert_to::() { 225 | /// doubled_values.push(value.to_i64() * 2); 226 | /// } 227 | /// }); 228 | /// 229 | /// assert_eq!(doubled_values, vec![2, 4]); 230 | /// ``` 231 | /// 232 | /// Ruby: 233 | /// 234 | /// ```ruby 235 | /// hash = { 236 | /// first_key: 1, 237 | /// second_key: 2 238 | /// } 239 | /// 240 | /// doubled_values = [] 241 | /// 242 | /// hash.each do |_key, value| 243 | /// doubled_values << [value * 2] 244 | /// end 245 | /// 246 | /// doubled_values == [2, 4] 247 | /// ``` 248 | pub fn each(&self, closure: F) 249 | where 250 | F: FnMut(AnyObject, AnyObject), 251 | { 252 | hash::each(self.value(), closure); 253 | } 254 | } 255 | 256 | impl Clone for Hash { 257 | fn clone(&self) -> Hash { 258 | Hash { 259 | value: hash::dup(self.value()), 260 | } 261 | } 262 | } 263 | 264 | impl Default for Hash { 265 | fn default() -> Self { 266 | Hash::new() 267 | } 268 | } 269 | 270 | impl From for Hash { 271 | fn from(value: Value) -> Self { 272 | Hash { value } 273 | } 274 | } 275 | 276 | impl From for Value { 277 | fn from(val: Hash) -> Self { 278 | val.value 279 | } 280 | } 281 | 282 | impl From for AnyObject { 283 | fn from(val: Hash) -> Self { 284 | AnyObject::from(val.value) 285 | } 286 | } 287 | 288 | impl Object for Hash { 289 | #[inline] 290 | fn value(&self) -> Value { 291 | self.value 292 | } 293 | } 294 | 295 | impl VerifiedObject for Hash { 296 | fn is_correct_type(object: &T) -> bool { 297 | object.value().ty() == ValueType::Hash 298 | } 299 | 300 | fn error_message() -> &'static str { 301 | "Error converting to Hash" 302 | } 303 | } 304 | 305 | impl PartialEq for Hash { 306 | fn eq(&self, other: &Self) -> bool { 307 | self.equals(other) 308 | } 309 | } 310 | 311 | #[cfg(test)] 312 | mod tests { 313 | use super::super::super::{Fixnum, Hash, Object, Symbol}; 314 | use rb_sys_test_helpers::ruby_test; 315 | 316 | #[ruby_test] 317 | fn test_hash_each() { 318 | let mut hash = Hash::new(); 319 | 320 | let len: i64 = 200; 321 | 322 | for i in 0..len { 323 | hash.store(Symbol::new(&format!("key_{}", i)), Fixnum::new(i)); 324 | } 325 | 326 | assert_eq!(hash.length(), len as usize); 327 | 328 | let mut counter: i64 = 0; 329 | 330 | hash.each(|k, v| { 331 | assert_eq!( 332 | k.try_convert_to::().map(|s| s.to_string()), 333 | Ok(format!("key_{}", counter)) 334 | ); 335 | assert_eq!( 336 | v.try_convert_to::().map(|f| f.to_i64()), 337 | Ok(counter) 338 | ); 339 | 340 | counter += 1; 341 | }); 342 | 343 | assert_eq!(counter, len); 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /src/class/integer.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | 3 | use crate::{ 4 | binding::fixnum, 5 | types::{Value, ValueType}, 6 | AnyObject, Fixnum, Object, VerifiedObject, 7 | }; 8 | 9 | /// `Integer` 10 | #[derive(Debug)] 11 | #[repr(C)] 12 | pub struct Integer { 13 | value: Value, 14 | } 15 | 16 | impl Integer { 17 | /// Creates a new `Integer`. 18 | /// 19 | /// # Examples 20 | /// 21 | /// ``` 22 | /// use rutie::{Integer, VM}; 23 | /// # VM::init(); 24 | /// 25 | /// let integer = Integer::new(1); 26 | /// 27 | /// assert_eq!(integer.to_i64(), 1); 28 | /// ``` 29 | /// 30 | /// Ruby: 31 | /// 32 | /// ```ruby 33 | /// 1 == 1 34 | /// ``` 35 | pub fn new(num: i64) -> Self { 36 | Self::from(num) 37 | } 38 | 39 | /// Retrieves an `i64` value from `Integer`. 40 | /// 41 | /// # Examples 42 | /// 43 | /// ``` 44 | /// use rutie::{Integer, VM}; 45 | /// # VM::init(); 46 | /// 47 | /// let integer = Integer::new(1); 48 | /// 49 | /// assert_eq!(integer.to_i64(), 1); 50 | /// ``` 51 | /// 52 | /// Ruby: 53 | /// 54 | /// ```ruby 55 | /// 1 == 1 56 | /// ``` 57 | pub fn to_i64(&self) -> i64 { 58 | fixnum::num_to_i64(self.value()) 59 | } 60 | 61 | /// Retrieves an `u64` value from `Integer`. 62 | /// 63 | /// # Examples 64 | /// 65 | /// ``` 66 | /// use rutie::{Integer, VM}; 67 | /// # VM::init(); 68 | /// 69 | /// let integer = Integer::new(1); 70 | /// 71 | /// assert_eq!(integer.to_u64(), 1); 72 | /// ``` 73 | /// 74 | /// Ruby: 75 | /// 76 | /// ```ruby 77 | /// 1 == 1 78 | /// ``` 79 | pub fn to_u64(&self) -> u64 { 80 | fixnum::num_to_u64(self.value()) 81 | } 82 | 83 | /// Retrieves an `i32` value from `Integer`. 84 | /// 85 | /// # Examples 86 | /// 87 | /// ``` 88 | /// use rutie::{Integer, VM}; 89 | /// # VM::init(); 90 | /// 91 | /// let integer = Integer::new(1); 92 | /// 93 | /// assert_eq!(integer.to_i32(), 1); 94 | /// ``` 95 | /// 96 | /// Ruby: 97 | /// 98 | /// ```ruby 99 | /// 1 == 1 100 | /// ``` 101 | pub fn to_i32(&self) -> i32 { 102 | fixnum::num_to_i32(self.value()) 103 | } 104 | 105 | /// Retrieves a `u32` value from `Integer`. 106 | /// 107 | /// # Examples 108 | /// 109 | /// ``` 110 | /// use rutie::{Integer, VM}; 111 | /// # VM::init(); 112 | /// 113 | /// let integer = Integer::new(1); 114 | /// 115 | /// assert_eq!(integer.to_u32(), 1); 116 | /// ``` 117 | /// 118 | /// Ruby: 119 | /// 120 | /// ```ruby 121 | /// 1 == 1 122 | /// ``` 123 | pub fn to_u32(&self) -> u32 { 124 | fixnum::num_to_u32(self.value()) 125 | } 126 | } 127 | 128 | impl From for Integer { 129 | fn from(value: Value) -> Self { 130 | Integer { value } 131 | } 132 | } 133 | 134 | impl From for Integer { 135 | fn from(num: i64) -> Self { 136 | Integer { 137 | value: fixnum::i64_to_num(num), 138 | } 139 | } 140 | } 141 | 142 | impl From for i64 { 143 | fn from(val: Integer) -> Self { 144 | fixnum::num_to_i64(val.value()) 145 | } 146 | } 147 | 148 | impl From for Integer { 149 | fn from(num: u64) -> Self { 150 | Integer { 151 | value: fixnum::u64_to_num(num), 152 | } 153 | } 154 | } 155 | 156 | impl From for u64 { 157 | fn from(val: Integer) -> Self { 158 | fixnum::num_to_u64(val.value()) 159 | } 160 | } 161 | 162 | impl From for Integer { 163 | fn from(num: i32) -> Self { 164 | Integer { 165 | value: fixnum::i32_to_num(num), 166 | } 167 | } 168 | } 169 | 170 | impl From for i32 { 171 | fn from(val: Integer) -> Self { 172 | fixnum::num_to_i32(val.value()) 173 | } 174 | } 175 | 176 | impl From for Integer { 177 | fn from(num: u32) -> Self { 178 | Integer { 179 | value: fixnum::u32_to_num(num), 180 | } 181 | } 182 | } 183 | 184 | impl From for u32 { 185 | fn from(val: Integer) -> Self { 186 | fixnum::num_to_u32(val.value()) 187 | } 188 | } 189 | 190 | impl From for Integer { 191 | fn from(num: Fixnum) -> Self { 192 | Integer { value: num.value() } 193 | } 194 | } 195 | 196 | impl From for Value { 197 | fn from(val: Integer) -> Self { 198 | val.value 199 | } 200 | } 201 | 202 | impl From for AnyObject { 203 | fn from(val: Integer) -> Self { 204 | AnyObject::from(val.value) 205 | } 206 | } 207 | 208 | impl Object for Integer { 209 | #[inline] 210 | fn value(&self) -> Value { 211 | self.value 212 | } 213 | } 214 | 215 | impl VerifiedObject for Integer { 216 | fn is_correct_type(object: &T) -> bool { 217 | let ty = object.value().ty(); 218 | ty == ValueType::Fixnum || ty == ValueType::Bignum 219 | } 220 | 221 | fn error_message() -> &'static str { 222 | "Error converting to Integer" 223 | } 224 | } 225 | 226 | impl PartialEq for Integer { 227 | fn eq(&self, other: &Self) -> bool { 228 | self.equals(other) 229 | } 230 | } 231 | 232 | #[cfg(test)] 233 | mod tests { 234 | use super::super::super::{AnyException, Integer, NilClass, Object, VM}; 235 | use rb_sys_test_helpers::ruby_test; 236 | 237 | #[cfg(target_os = "macos")] 238 | #[ruby_test] 239 | fn test_github_issue_113_darwin_os() { 240 | VM::init(); 241 | 242 | let num: Integer = Integer::new(std::i64::MIN); 243 | assert_eq!(num.to_i64(), ::std::i64::MIN); 244 | 245 | let num: Integer = Integer::new(std::i64::MAX); 246 | assert_eq!(num.to_i64(), ::std::i64::MAX); 247 | 248 | let num: i64 = std::i64::MIN + std::u32::MAX as i64; 249 | assert_eq!(Integer::new(num).to_i64(), -9223372032559808513); 250 | 251 | let num: Integer = Integer::new((std::i32::MIN as i64).pow(2)); 252 | assert_eq!(num.to_i64(), 4611686018427387904); 253 | 254 | let num: Integer = Integer::new((std::i32::MIN as i64).pow(2) * -1 - 1); 255 | assert_eq!(num.to_i64(), -4611686018427387905) 256 | } 257 | 258 | #[ruby_test] 259 | fn test_i32() { 260 | let nil = NilClass::new(); 261 | 262 | let num = str_to_num("1").unwrap(); 263 | assert_eq!(1, num.to_i32()); 264 | 265 | let num = str_to_num("-1").unwrap(); 266 | assert_eq!(-1, num.to_i32()); 267 | 268 | let num = str_to_num("2 ** 31 - 1").unwrap(); 269 | assert_eq!(::std::i32::MAX, num.to_i32()); 270 | 271 | let num = str_to_num("2 ** 31").unwrap(); 272 | let result = VM::protect(|| { 273 | num.to_i32(); 274 | nil.into() 275 | }); 276 | assert!(result.is_err()); 277 | 278 | let num = str_to_num("-1 * 2 ** 31").unwrap(); 279 | assert_eq!(::std::i32::MIN, num.to_i32()); 280 | 281 | let num = str_to_num("-1 * 2 ** 31 - 1").unwrap(); 282 | let result = VM::protect(|| { 283 | num.to_i32(); 284 | nil.to_any_object() 285 | }); 286 | assert!(result.is_err()); 287 | } 288 | 289 | // #[ruby_test] 290 | // fn test_u32() { 291 | // let nil = NilClass::new(); 292 | // 293 | // let num = str_to_num("1").unwrap(); 294 | // assert_eq!(1, num.to_u32()); 295 | // 296 | // let num = str_to_num("-1").unwrap(); 297 | // assert_eq!(::std::u32::MAX, num.to_u32()); 298 | // 299 | // let num = str_to_num("2 ** 32 - 1").unwrap(); 300 | // assert_eq!(::std::u32::MAX, num.to_u32()); 301 | // 302 | // // TODO: Verify if this test 303 | // let num = str_to_num("2 ** 63").unwrap(); 304 | // let result = VM::protect(|| { 305 | // num.to_u32(); 306 | // nil.into() 307 | // }); 308 | // assert!(result.is_err()); 309 | // 310 | // let num = str_to_num("0").unwrap(); 311 | // assert_eq!(::std::u32::MIN, num.to_u32()); 312 | // } 313 | 314 | #[ruby_test] 315 | fn test_i64() { 316 | let nil = NilClass::new(); 317 | 318 | let num = str_to_num("2 ** 63 - 1").unwrap(); 319 | assert_eq!(::std::i64::MAX, num.to_i64()); 320 | 321 | let num = str_to_num("2 ** 63").unwrap(); 322 | let result = VM::protect(|| { 323 | num.to_i64(); 324 | nil.to_any_object() 325 | }); 326 | assert!(result.is_err()); 327 | 328 | let num = str_to_num("-1 * 2 ** 63").unwrap(); 329 | assert_eq!(::std::i64::MIN, num.to_i64()); 330 | 331 | let num = str_to_num("-1 * 2 ** 63 - 1").unwrap(); 332 | let result = VM::protect(|| { 333 | num.to_i64(); 334 | nil.to_any_object() 335 | }); 336 | assert!(result.is_err()); 337 | } 338 | 339 | #[ruby_test] 340 | fn test_u64() { 341 | let nil = NilClass::new(); 342 | 343 | let num = str_to_num("2 ** 64 - 1").unwrap(); 344 | assert_eq!(::std::u64::MAX, num.to_u64()); 345 | 346 | let num = str_to_num("2 ** 64").unwrap(); 347 | let result = VM::protect(|| { 348 | num.to_u64(); 349 | nil.into() 350 | }); 351 | assert!(result.is_err()); 352 | 353 | let num = str_to_num("0").unwrap(); 354 | assert_eq!(::std::u64::MIN, num.to_u64()); 355 | 356 | // // Current Ruby implementation does not raise an exception 357 | // let num = str_to_num("-1").unwrap(); 358 | // let result = VM::protect(|| { num.to_u64(); nil.into() }); 359 | // assert!(result.is_err()); 360 | } 361 | 362 | fn str_to_num(code: &str) -> Result { 363 | VM::eval(code).and_then(|x| x.try_convert_to::()) 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html) 6 | for the public APIs. `rubysys`, even though shared publicly, is considered a private 7 | API and may have breaking changes during a teeny version change. 8 | 9 | 10 | ## [Unreleased] 11 | ### Changed 12 | - Upgraded code base to use rb-sys build system for Ruby 3, thanks to @goyox86 13 | 14 | ## [0.9.0] - 2023-12-17 15 | ### Added 16 | - Support for trailing comma in method macros, thanks to @andrewtbiehl 17 | - Github actions testing support, thanks to @danielpclark & @striezel 18 | - Rust 2021 Edition, thanks to @goyox86 19 | - Support for compiling on OpenBSD, thanks to @marvinthepa 20 | 21 | ### Fixed 22 | - removed warnings for allow(unused_mut) and allow(unused_variables) on methods! rtself arg, thanks to @danlarkin 23 | - internal ruby string length check, thanks to @mpalmer 24 | 25 | ## [0.8.4] - 2022-03-29 26 | ### Added 27 | - Implement `Eq` and `Hash` for `Symbol`, thanks to @ahogappa0613 28 | 29 | ### Fixed 30 | - not FFI-safe warnings when Rutie structs are used as return types, thanks to @ankane 31 | 32 | ## [0.8.3] - 2021-09-06 33 | ### Added 34 | - Implement `Integer::to_u32` and `Fixnum::to_u32`, thanks to @Hywan 35 | 36 | ### Fixed 37 | - Docstring description for `to_enum` on `Array`, thanks to @jhwiig 38 | - `methods!` macro now uses `:ty` for return type instead of `:ident`, thanks to @n8ta 39 | 40 | ## [0.8.2] - 2021-02-09 41 | ### Added 42 | - Implement `VM::call_super` -> `rb_call_super`, thanks to @askreet 43 | 44 | ### Changed 45 | - Allow commas after methods macros, thanks to @gemmaro 46 | 47 | ### Fixed 48 | - Build for FreeBSD, thanks to @Stazer 49 | - Issue when Rutie dependency included with relative path 50 | 51 | ## [0.8.1] - 2020-09-28 52 | ### Added 53 | - cargo feature `no-link` disables linking to `libruby`, thanks to @danlarkin 54 | - initial changes for Android support, thanks to @Riey 55 | 56 | ### Changed 57 | - Optimized equality methods, thanks to @asppsa 58 | 59 | ### Fixed 60 | - `Hash::each` (via `binding::hash::each`) now calls `rubysys::rb_hash_foreach` with a 61 | callback that properly returns a `st_retval` instead of `()`, thanks to @danlarkin 62 | - fixed build warnings due to trait objects, thanks to @danlarkin 63 | - With dropping support for Ruby 2.4 we can now perfectly model the `force_encoding` method 64 | which checks if the object is frozen and raises the appropriate error. 65 | 66 | ## [0.7.0] - 2019-08-19 67 | ### Added 68 | - `VM::error_pop` to get the Ruby Exception and remove it from interfering 69 | with the current thread 70 | - `VM::exit` to exit the Ruby VM with status code given 71 | - `VM::exit_bang` to exit skipping exit handlers 72 | - `NilClass` has had `Copy` and `Clone` derived on it 73 | - Readme section for Ruby's Future and SemVer 74 | - `VM::abort` exit the Ruby VM via abort 75 | - `VM::trap` for signal handling 76 | - `VM::at_exit` for executing Rust code after the Ruby VM stops 77 | - `Float::implicit_to_f` 78 | 79 | ### Changed 80 | - `VM::protect` takes a function that now returns an `AnyObject` instead of a `Value`. 81 | `VM::protect` will become more frequently used and encouraged which is why this change 82 | is necessary as `Value` is meant to be internal. 83 | - Avoid showing `Value` or `.value()` in any documentation. Prefer `.into()` when necessary. 84 | `Value` should always be treated as a private API. 85 | 86 | ## [0.6.1] - 2019-06-18 87 | ### Added 88 | - `Encoding::is_compatible` which is the same as Ruby's `Encoding.compatible?` 89 | 90 | ## [0.6.0] - 2019-06-16 91 | ### Changed 92 | - Updated `libc` and `lazy_static` dependency versions. 93 | 94 | ## [0.6.0-rc.2] - 2019-05-16 95 | ### Fixed 96 | - Restored use of `Path` for Windows `build.rs` which had been removed in 0.5.5 97 | 98 | ## [0.5.6] - 2019-05-16 99 | ### Fixed 100 | - Restored use of `Path` for Windows `build.rs` which had been removed in 0.5.5 101 | 102 | ## [0.6.0-rc.1] - 2019-05-16 103 | ### Changed 104 | - Methods that took type `Option<&[]>` now take only type `&[]`, thanks to @dsander 105 | 106 | ## [0.5.5] - 2019-05-13 107 | ### Added 108 | - Safety policy in README 109 | - `Fixnum.to_u64`, thanks to @irxground 110 | - `Integer.to_u64`, thanks to @irxground 111 | - `impl From for Integer`, thanks to @irxground 112 | - `impl Into for Integer`, thanks to @irxground 113 | - `impl From for Integer`, thanks to @irxground 114 | - `impl From for Integer`, thanks to @irxground 115 | - `impl Into for Integer`, thanks to @irxground 116 | - `rubysys::fixnum::rb_uint2inum`, thanks to @irxground 117 | - `rubysys::fixnum::rb_ll2inum`, thanks to @irxground 118 | - `rubysys::fixnum::rb_ull2inum`, thanks to @irxground 119 | - `rubysys::fixnum::rb_num2short`, thanks to @irxground 120 | - `rubysys::fixnum::rb_num2ushort`, thanks to @irxground 121 | - `rubysys::fixnum::rb_num2uint`, thanks to @irxground 122 | - `rubysys::fixnum::rb_num2ulong`, thanks to @irxground 123 | - `rubysys::fixnum::rb_num2ll`, thanks to @irxground 124 | - `rubysys::fixnum::rb_num2ull`, thanks to @irxground 125 | 126 | ### Changed 127 | - Integer `is_correct_type` to permit Bignum, thanks to @irxground 128 | - `rubysys::fixnum::rb_num2int` returns `libc::c_long` rather than `c_int`, thanks to @irxground 129 | 130 | ### Fixed 131 | - symlink check in `build.rs` which had rare systems in which `exists` didn't work on symlink, thanks to @ekump 132 | 133 | ## [0.5.4] - 2019-04-15 134 | ### Added 135 | - `GC::adjust_memory_usage`, thanks to @Antti 136 | - `examples/rutie_ruby_gvl_example`, thanks to @dsander 137 | - `GC::count` 138 | - `GC::disable` 139 | - `GC::enable` 140 | - `GC::force_recycle` 141 | - `GC::mark_locations` 142 | - `GC::mark_maybe` 143 | - `GC::register` 144 | - `GC::start` 145 | - `GC::stat` 146 | - `GC::unregister` 147 | - `util::inmost_rb_object` which is a string recurse tool to get nested ruby objects 148 | 149 | ### Fixed 150 | - `GC::mark` documentation notes. 151 | - `util::closure_to_ptr` from `'static + FnOnce` to `FnMut`, thanks to @dsander 152 | - `Thread::new` from `'static + FnOnce` to `FnMut`, thanks to @dsander 153 | - `Thread::call_without_gvl` from `'static + FnOnce` to `FnMut`, thanks to @dsander 154 | - `Thread::call_without_gvl2` from `'static + FnOnce` to `FnMut`, thanks to @dsander 155 | - `Thread::call_with_gvl` from `'static + FnOnce` to `FnMut`, thanks to @dsander 156 | - `AnyException::new` to work with nested exception classes 157 | 158 | ## [0.5.3] - 2019-01-10 159 | ### Added 160 | - `util::is_proc` & `util::is_method` 161 | - `rb_enc_compatible` useful for internal string encoding compatibility checks from 162 | which we now have `binding::is_compatible_encoding` and `binding::compatible_encoding` 163 | - `RString.compatible_with` as the public API for `rb_enc_compatible` with trait `EncodingSupport` 164 | - `RString::compatible_encoding` as the public API for `rb_enc_compatible` with trait `EncodingSupport` 165 | - `impl Deref for AnyException` 166 | - `impl Deref for AnyObject` 167 | - `impl Borrow for AnyObject` 168 | - `impl Borrow for AnyException` 169 | - `impl AsRef for AnyObject` 170 | - `impl AsRef for AnyException` 171 | - `impl AsRef for AnyObject` 172 | - `impl AsRef for AnyException` 173 | - `impl From<&T> for AnyObject` 174 | 175 | ### Changed 176 | - Removed Ruby 2.3 support & added 2.6 177 | - `VM::raise_ex` now accepts `Into` rather than just `AnyException` 178 | - Refactor internal encoding types 179 | - Refactor `build.rs` script to use Ruby provided cflags 180 | 181 | ### Removed 182 | - pkg-config-rs removed from Rutie and from the build process 183 | 184 | ## [0.5.2] - 2018-12-18 185 | ### Added 186 | - `impl Into for Integer` thanks to @Antti 187 | - `Integer.to_i32`, thanks to @Antti 188 | - `Fixname.to_i32`, thanks to @Antti 189 | 190 | ### Fixed 191 | - `Integer.to_i64` to use `rb_num2long` for genuine `i64` result, thanks to @Antti 192 | - `impl Into for Integer` to use `rb_num2long` for genuine `i64` result, thanks to @Antti 193 | - `Fixname.to_i64` to use `rb_num2long` for genuine `i64` result, thanks to @Antti 194 | 195 | ## [0.5.1] - 2018-12-11 196 | ### Added 197 | - Windows build support (partially working) 198 | - Mac static build support, thanks to @felix-d 199 | - Rutie pronunciation guide 200 | 201 | ## [0.5.0] - 2018-10-23 202 | ### Changed 203 | - `CodepointIterator` now borrows RString parameter instead of consuming ownership 204 | 205 | ## [0.4.3] - 2018-10-23 206 | ### Fixed 207 | - `RString.codepoints` uses a new internal implementation as `rb_str_codepoints` isn't exported/available on some OSes 208 | 209 | ## [0.4.2] - 2018-10-16 210 | ### Fixed 211 | - Wrapping struct changed from Ruru to Rutie & some of the same changes in documentation, thanks to @turboladen 212 | 213 | ## [0.4.1] - 2018-10-04 214 | ### Added 215 | - Static build support 216 | 217 | ## [0.4.0] - 2018-08-20 218 | ### Added 219 | - Methods `VM::yield_object` and `VM::yield_splat` 220 | - `Enumerator` object 221 | - `Array.to_enum` 222 | - `TryConvert` for `AnyException` 223 | - `VM::error_info` and `VM::clear_error_info` 224 | - Documentation for `VM::protect` 225 | - `Binding` 226 | - `Into` for all types which `impl Object` 227 | - `Into` for all types which `impl Object` 228 | - `From` and `Into` for `Integer` 229 | - `From<&'static str>` for `RString` 230 | - `eval!()` macro with `binding, filename, linenum` for *optional* arguments 231 | - `rubysys::rproc::check_arity` for simple numeric bounds checking 232 | - `Symbol.to_proc` 233 | - `Proc.is_lambda` 234 | 235 | ### Changed 236 | - `Object.protect_send` and `Object.protect_public_send` have changed the 237 | first parameter from requiring `String` to `&str` 238 | - `VM::protect` returns `Result` rather than `Result` 239 | - `PartialEq` is now implemented for Ruby objects via the `==` method 240 | 241 | ## [0.3.4] - 2018-08-08 242 | ### Added 243 | - This `CHANGELOG.md` file 244 | - Method `RString.codepoints` 245 | - `CodepointIterator` which uses direct ruby calls to get character value 246 | from bytes as determeined by the strings own `Encoding` and produces 247 | them one at a time 248 | - `binding::new_frozen` for internal use with `CodepointIterator` 249 | - `rubysys::string::{rstring_embed_len, rstring_ptr, rstring_end}` to 250 | match equivalent Ruby C macros for use in `CodepointIterator` 251 | 252 | ### Changed 253 | - `rubysys::rb_str_len` renamed to `rubysys::rstring_len` to match the name 254 | of the Ruby C macro which it is a copy of 255 | 256 | ### Fixed 257 | - `rubysys::string::{RStringAs, RStringHeap, RStringAux}` to match Ruby's 258 | C code implementation perfectly 259 | 260 | ### Removed 261 | - CI testing for Rust 1.25 as pointer addition wasn't stable until 1.26 262 | 263 | ## [0.3.3] - 2018-08-07 264 | ### Added 265 | - Full encoding support with `VM::init_loadpath`, `RString.encode`, 266 | `RString.is_valid_encoding` 267 | - `RString::from_bytes` which takes both a byte sequence for characters 268 | and an `Encoding` object to interpret how to get those characters from bytes 269 | - Documentation about what to try if binary installs of Ruby panic on CI 270 | servers 271 | - `rubysys::encoding::{coderange_set, coderange_clear}` and encoding flags 272 | - `EncodingIndex` type for internal use in the `binding` layer 273 | 274 | ### Changed 275 | - Updated code examples to remove deprecated `RString::new` from them 276 | - TravisCI Linux builds now compile all Rubies 277 | 278 | ## [0.3.2] - 2018-08-03 279 | ### Added 280 | - CI server logging for the Rust build process 281 | - Ruby gem `rutie` version 0.0.3 282 | - Documentation for Ruby gem `rutie` 283 | - Build documentation with `build.md` 284 | - Customization options for using `pkg-config` 285 | - Example CLI eval program in examples directory, thanks to @irxground 286 | - `RString::count_chars`, thanks to @irxground 287 | 288 | ### Changed 289 | - Refactor of `VM::protect`, thanks to @irxground 290 | - Internally use `RString::new_utf8` 291 | - `TryConvert` moved to `src/class/traits/try_convert.rs` but still shared in root of crate 292 | - Refactor internal method names for `Value` in `src/rubysys/value.rs` to match Ruby source code 293 | 294 | ### Deprecated 295 | - `RString::new` — use either `RString::new_utf8` or `RString::new_usascii_unchecked` 296 | 297 | ### Removed 298 | - Use of `fiddle` from examples and documentation 299 | 300 | ## [0.3.1] - 2018-07-17 301 | ### Added 302 | - CI testing for Rust 1.25 for purpose of older match ref syntax 303 | 304 | ### Changed 305 | - `cargo test` and `cargo build` require the `-vv` flag afterwards in older Rust versions 306 | - refactor `option_to_slice` for Rust 1.25 compatible syntax 307 | 308 | ## [0.3.0] - 2018-07-17 309 | ### Added 310 | - `TryConvert` implicit conversion or `NilClass` result 311 | - `Encoding` and `EncodingSupport` 312 | - `TryConvert` for `RString` 313 | - Majority of Ruby main constants in `src/rubysys/constant.rs` 314 | - `rubysys::class::{rb_define_singleton_method, rb_scan_args}` 315 | - `rubysys::string::{rb_check_string_type, rb_str_locktmp, rb_str_unlocktmp, is_lockedtmp}` 316 | - `is_frozen` check for `Value` and several Ruby macros for `Value` 317 | - `util::option_to_slice` 318 | 319 | ### Changed 320 | - Refactor Pathname example in README 321 | - Refactor away `util.rs` files from `binding` and `rubysys` 322 | - Refactor away from using heap to stack memory, thanks to @irxground 323 | 324 | ### Fixed 325 | - A few Ruby `ValueType` flags were incorrect in `rubysys` 326 | 327 | ## [0.2.2] - 2018-07-07 328 | ### Added 329 | - `String#concat`, thanks to @irxground 330 | - Method signatures for all of `rubysys` direct method mappings documented 331 | 332 | ### Fixed 333 | - `Array.store` does not return anything 334 | - Misnamed `rubysys::string` method `rb_str_ascii_only_p` to `rb_enc_str_asciionly_p` 335 | 336 | ## [0.2.1] - 2018-06-30 337 | ### Added 338 | - OSX testing on Travis CI 339 | - `Cargo.toml` badges for Travis CI and maintenance status 340 | - Full README details 341 | - Ruby & Rust examples 342 | 343 | ## [0.2.0] - 2018-06-26 344 | ### Changed 345 | - Migrated `parse_arguments` from `VM` to `util` 346 | 347 | ## [0.1.4] - 2018-05-25 348 | ### Changed 349 | - Refactor build script 350 | 351 | ## [0.1.3] - 2018-05-25 352 | ### Added 353 | - Verbose CI output for Rust 354 | - Set default `pkg-config` path for Ruby 355 | 356 | ## [0.1.2] - 2018-05-25 357 | ### Added 358 | - `pkg-config` support 359 | - Basic migrating from Ruru to Rutie notes 360 | 361 | ### Changed 362 | - TravisCI testing to not use feature flag 363 | 364 | ## [0.1.0] - 2018-05-20 365 | ### Added 366 | - `Display` and `Debug` traits for `AnyException` 367 | 368 | ### Changed 369 | - Migrated from `Error` to `AnyException` 370 | - `Object.protect_send`, `Object.protect_public_send`, `Object.try_convert_to`, 371 | `VM::eval` method signatures changed to return `AnyException` on `Err` 372 | - Macro DSL to have the error types as `AnyException` 373 | 374 | ### Removed 375 | - Duplicate thread methods from `VM` 376 | - `result::Error` 377 | 378 | ## [0.0.3] - 2018-05-20 379 | ### Added 380 | - Officially forked [Ruru](https://github.com/d-unseductable/ruru) and renamed 381 | to `Rutie` with the following Pull Requests merged 382 | * 79 [eval functions with panic safe implementation](https://github.com/d-unseductable/ruru/pull/79) 383 | * 80 [Module support](https://github.com/d-unseductable/ruru/pull/80) 384 | * 82 [Private method and module function](https://github.com/d-unseductable/ruru/pull/82) 385 | * 87 [Object equality methods](https://github.com/d-unseductable/ruru/pull/87) 386 | * 88 [Protect send: a panic safe feature for Ruby interaction](https://github.com/d-unseductable/ruru/pull/88) 387 | * 89 [allocate -> Class](https://github.com/d-unseductable/ruru/pull/89) 388 | * 93 [Working `Exception` and `AnyException`](https://github.com/d-unseductable/ruru/pull/93) 389 | * 98 [String methods to convert to `&[u8]` and `Vec`](https://github.com/d-unseductable/ruru/pull/98) 390 | - Merged [ruby-sys](https://github.com/steveklabnik/ruby-sys) into `src/rubysys` with 391 | the following Pull Requests merged 392 | * 26 [private method and module method added](https://github.com/steveklabnik/ruby-sys/pull/26) 393 | * 27 [Two additional type data check methods](https://github.com/steveklabnik/ruby-sys/pull/27) 394 | * 28 [Thread specific error setter and getter](https://github.com/steveklabnik/ruby-sys/pull/28) 395 | * 29 [public send](https://github.com/steveklabnik/ruby-sys/pull/29) 396 | * 30 [variadic function support](https://github.com/steveklabnik/ruby-sys/pull/30) 397 | * 33 [Encoding support](https://github.com/steveklabnik/ruby-sys/pull/33) 398 | --------------------------------------------------------------------------------