diff --git a/Cargo.toml b/Cargo.toml index 446d6e07..97d071e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,5 +12,5 @@ keywords = ["cruby", "mri", "ruby", "ruru"] license = "MIT" [dependencies] -ruby-sys = "0.3.0" +ruby-sys = { git = "https://github.com/danielpclark/ruby-sys", branch = "playground" } lazy_static = "0.2.1" diff --git a/src/binding/class.rs b/src/binding/class.rs index 2918119d..b1cbc30f 100644 --- a/src/binding/class.rs +++ b/src/binding/class.rs @@ -81,6 +81,14 @@ pub fn define_method(klass: Value, name: &str, callback: C } } +pub fn define_private_method(klass: Value, name: &str, callback: Callback) { + let name = util::str_to_cstring(name); + + unsafe { + class::rb_define_private_method(klass, name.as_ptr(), callback as CallbackPtr, -1); + } +} + pub fn define_singleton_method( klass: Value, name: &str, diff --git a/src/binding/mod.rs b/src/binding/mod.rs index 7cce7f5b..a142a5e1 100644 --- a/src/binding/mod.rs +++ b/src/binding/mod.rs @@ -5,6 +5,7 @@ pub mod float; pub mod gc; pub mod global; pub mod hash; +pub mod module; pub mod rproc; pub mod string; pub mod symbol; diff --git a/src/binding/module.rs b/src/binding/module.rs new file mode 100644 index 00000000..0c36b1aa --- /dev/null +++ b/src/binding/module.rs @@ -0,0 +1,44 @@ +use ruby_sys::class; + +use binding::global::rb_cObject; +use binding::util as binding_util; +use types::{Value, Callback, CallbackPtr}; +use util; + +use Object; + +pub fn define_module(name: &str) -> Value { + let name = util::str_to_cstring(name); + + unsafe { class::rb_define_module(name.as_ptr()) } +} + +pub fn define_nested_module(outer: Value, name: &str) -> Value { + let name = util::str_to_cstring(name); + + unsafe { class::rb_define_module_under(outer, name.as_ptr()) } +} + +pub fn define_module_function(klass: Value, name: &str, callback: Callback) { + let name = util::str_to_cstring(name); + + unsafe { + class::rb_define_module_function(klass, name.as_ptr(), callback as CallbackPtr, -1); + } +} + +pub fn include_module(klass: Value, module: &str) { + let object_module = unsafe { rb_cObject }; + + let module_value = binding_util::get_constant(module, object_module); + + unsafe { class::rb_include_module(klass, module_value) }; +} + +pub fn prepend_module(klass: Value, module: &str) { + let object_module = unsafe { rb_cObject }; + + let module_value = binding_util::get_constant(module, object_module); + + unsafe { class::rb_prepend_module(klass, module_value) }; +} diff --git a/src/class/class.rs b/src/class/class.rs index 7ae047ef..c99590da 100644 --- a/src/class/class.rs +++ b/src/class/class.rs @@ -1,13 +1,13 @@ use std::convert::From; -use binding::class; +use binding::{class, module}; use binding::global::rb_cObject; use binding::util as binding_util; use typed_data::DataTypeWrapper; use types::{Value, ValueType}; use util; -use {AnyObject, Array, Object, VerifiedObject}; +use {AnyObject, Array, Object, Module, VerifiedObject}; /// `Class` /// @@ -277,6 +277,39 @@ impl Class { Self::from(binding_util::get_constant(name, self.value())) } + /// Retrieves a `Module` nested to current `Class`. + /// + /// # Examples + /// + /// ``` + /// use ruru::{Class, Module, Object, VM}; + /// # VM::init(); + /// + /// Class::new("Outer", None).define(|itself| { + /// itself.define_nested_module("Inner"); + /// }); + /// + /// Class::from_existing("Outer").get_nested_module("Inner"); + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// class Outer + /// module Inner + /// end + /// end + /// + /// Outer::Inner + /// + /// # or + /// + /// Outer.const_get('Inner') + /// ``` + pub fn get_nested_module(&self, name: &str) -> Module { + Module::from(binding_util::get_constant(name, self.value())) + } + /// Creates a new `Class` nested into current class. /// /// `superclass` can receive the following values: @@ -318,6 +351,39 @@ impl Class { Self::from(class::define_nested_class(self.value(), name, superclass)) } + /// Creates a new `Module` nested into current `Class`. + /// + /// # Examples + /// + /// ``` + /// use ruru::{Class, Module, Object, VM}; + /// # VM::init(); + /// + /// Class::new("Outer", None).define(|itself| { + /// itself.define_nested_module("Inner"); + /// }); + /// + /// Module::from_existing("Outer").get_nested_module("Inner"); + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// class Outer + /// module Inner + /// end + /// end + /// + /// Outer::Inner + /// + /// # or + /// + /// Outer.const_get('Inner') + /// ``` + pub fn define_nested_module(&mut self, name: &str) -> Module { + Module::from(module::define_nested_module(self.value(), name)) + } + /// Retrieves a constant from class. /// /// # Examples @@ -408,6 +474,46 @@ impl Class { class::const_set(self.value(), name, value.value()); } + /// Includes module into current class + /// + /// # Examples + /// + /// ``` + /// use ruru::{Class, Module, VM}; + /// # VM::init(); + /// + /// let a_module = Module::new("A"); + /// Class::new("B", None).include("A"); + /// + /// let b_class_ancestors = Class::from_existing("B").ancestors(); + /// let expected_ancestors = vec![Module::from_existing("A")]; + /// + /// assert!(expected_ancestors.iter().any(|anc| *anc == a_module)); + /// ``` + pub fn include(&self, md: &str) { + module::include_module(self.value(), md); + } + + /// Prepends module into current class + /// + /// # Examples + /// + /// ``` + /// use ruru::{Class, Module, VM}; + /// # VM::init(); + /// + /// let a_module = Module::new("A"); + /// Class::new("B", None).prepend("A"); + /// + /// let b_class_ancestors = Class::from_existing("B").ancestors(); + /// let expected_ancestors = vec![Module::from_existing("A")]; + /// + /// assert!(expected_ancestors.iter().any(|anc| *anc == a_module)); + /// ``` + pub fn prepend(&self, md: &str) { + module::prepend_module(self.value(), md); + } + /// Defines an `attr_reader` for class /// /// # Examples diff --git a/src/class/mod.rs b/src/class/mod.rs index 3ca3a448..a4dbe189 100644 --- a/src/class/mod.rs +++ b/src/class/mod.rs @@ -7,6 +7,7 @@ pub mod float; pub mod gc; pub mod hash; pub mod integer; +pub mod module; pub mod nil_class; pub mod rproc; pub mod string; diff --git a/src/class/module.rs b/src/class/module.rs new file mode 100644 index 00000000..28c77f62 --- /dev/null +++ b/src/class/module.rs @@ -0,0 +1,760 @@ +use std::convert::From; + +use binding::{module, class}; +use binding::global::rb_cObject; +use binding::util as binding_util; +use typed_data::DataTypeWrapper; +use types::{Value, ValueType, Callback}; + +use {AnyObject, Array, Object, Class, VerifiedObject}; + +/// `Module` +/// +/// Also see `def`, `def_self`, `define` and some more functions from `Object` trait. +/// +/// ```rust +/// #[macro_use] extern crate ruru; +/// +/// use std::error::Error; +/// +/// use ruru::{Module, Fixnum, Object, VM}; +/// +/// module!(Example); +/// +/// methods!( +/// Example, +/// itself, +/// +/// fn square(exp: Fixnum) -> Fixnum { +/// // `exp` is not a valid `Fixnum`, raise an exception +/// if let Err(ref error) = exp { +/// VM::raise(error.to_exception(), error.description()); +/// } +/// +/// // We can safely unwrap here, because an exception was raised if `exp` is `Err` +/// let exp = exp.unwrap().to_i64(); +/// +/// Fixnum::new(exp * exp) +/// } +/// ); +/// +/// fn main() { +/// # VM::init(); +/// Module::new("Example").define(|itself| { +/// itself.def("square", square); +/// }); +/// } +/// ``` +/// +/// Ruby: +/// +/// ```ruby +/// module Example +/// def square(exp) +/// raise TypeError unless exp.is_a?(Fixnum) +/// +/// exp * exp +/// end +/// end +/// ``` +#[derive(Debug, PartialEq)] +pub struct Module { + value: Value, +} + +impl Module { + /// Creates a new `Module`. + /// + /// # Examples + /// + /// ``` + /// use ruru::{Module, VM}; + /// # VM::init(); + /// + /// let basic_record_module = Module::new("BasicRecord"); + /// + /// assert_eq!(basic_record_module, Module::from_existing("BasicRecord")); + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// module BasicRecord + /// end + /// ``` + pub fn new(name: &str) -> Self { + Self::from(module::define_module(name)) + } + + /// Retrieves an existing `Module` object. + /// + /// # Examples + /// + /// ``` + /// use ruru::{Module, VM}; + /// # VM::init(); + /// + /// let module = Module::new("Record"); + /// + /// assert_eq!(module, Module::from_existing("Record")); + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// module Record + /// end + /// + /// # get module + /// + /// Record + /// + /// # or + /// + /// Object.const_get('Record') + /// ``` + pub fn from_existing(name: &str) -> Self { + let object_module = unsafe { rb_cObject }; + + Self::from(binding_util::get_constant(name, object_module)) + } + + /// Returns a Vector of ancestors of current module + /// + /// # Examples + /// + /// ### Getting all the ancestors + /// + /// ``` + /// use ruru::{Module, VM}; + /// # VM::init(); + /// + /// let process_module_ancestors = Module::from_existing("Process").ancestors(); + /// + /// let expected_ancestors = vec![ + /// Module::from_existing("Process") + /// ]; + /// + /// assert_eq!(process_module_ancestors, expected_ancestors); + /// ``` + /// + /// ### Searching for an ancestor + /// + /// ``` + /// use ruru::{Module, VM}; + /// # VM::init(); + /// + /// let record_module = Module::new("Record"); + /// + /// let ancestors = record_module.ancestors(); + /// + /// assert!(ancestors.iter().any(|module| *module == record_module)); + /// ``` + // Using unsafe conversions is ok, because MRI guarantees to return an `Array` of `Module`es + pub fn ancestors(&self) -> Vec { + let ancestors = Array::from(class::ancestors(self.value())); + + ancestors + .into_iter() + .map(|module| unsafe { module.to::() }) + .collect() + } + + /// Retrieves a `Module` nested to current `Module`. + /// + /// # Examples + /// + /// ``` + /// use ruru::{Module, Object, VM}; + /// # VM::init(); + /// + /// Module::new("Outer").define(|itself| { + /// itself.define_nested_module("Inner"); + /// }); + /// + /// Module::from_existing("Outer").get_nested_module("Inner"); + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// module Outer + /// module Inner + /// end + /// end + /// + /// Outer::Inner + /// + /// # or + /// + /// Outer.const_get('Inner') + /// ``` + pub fn get_nested_module(&self, name: &str) -> Self { + Self::from(binding_util::get_constant(name, self.value())) + } + + /// Retrieves a `Class` nested to current `Module`. + /// + /// # Examples + /// + /// ``` + /// use ruru::{Class, Module, Object, VM}; + /// # VM::init(); + /// + /// Module::new("Outer").define(|itself| { + /// itself.define_nested_class("Inner", None); + /// }); + /// + /// Module::from_existing("Outer").get_nested_class("Inner"); + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// module Outer + /// class Inner + /// end + /// end + /// + /// Outer::Inner + /// + /// # or + /// + /// Outer.const_get('Inner') + /// ``` + pub fn get_nested_class(&self, name: &str) -> Class { + Class::from(binding_util::get_constant(name, self.value())) + } + + /// Creates a new `Module` nested into current `Module`. + /// + /// # Examples + /// + /// ``` + /// use ruru::{Module, Object, VM}; + /// # VM::init(); + /// + /// Module::new("Outer").define(|itself| { + /// itself.define_nested_module("Inner"); + /// }); + /// + /// Module::from_existing("Outer").get_nested_module("Inner"); + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// module Outer + /// module Inner + /// end + /// end + /// + /// Outer::Inner + /// + /// # or + /// + /// Outer.const_get('Inner') + /// ``` + pub fn define_nested_module(&mut self, name: &str) -> Self { + Self::from(module::define_nested_module(self.value(), name)) + } + + /// Creates a new `Class` nested into current module. + /// + /// # Examples + /// + /// ``` + /// use ruru::{Class, Module, Object, VM}; + /// # VM::init(); + /// + /// Module::new("Outer").define(|itself| { + /// itself.define_nested_class("Inner", None); + /// }); + /// + /// Module::from_existing("Outer").get_nested_class("Inner"); + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// module Outer + /// class Inner + /// end + /// end + /// + /// Outer::Inner + /// + /// # or + /// + /// Outer.const_get('Inner') + /// ``` + pub fn define_nested_class(&mut self, name: &str, superclass: Option<&Class>) -> Class { + let superclass = Self::superclass_to_value(superclass); + + Class::from(class::define_nested_class(self.value(), name, superclass)) + } + + /// Defines an instance method for the given module. + /// + /// Use `methods!` macro to define a `callback`. + /// + /// You can also use `def()` alias for this function combined with `Module::define()` for a + /// nicer DSL. + /// + /// # Panics + /// + /// Ruby can raise an exception if you try to define instance method directly on an instance + /// of some class (like `Fixnum`, `String`, `Array` etc). + /// + /// Use this method only on classes (or singleton classes of objects). + /// + /// # Examples + /// + /// ### The famous String#blank? method + /// + /// ```rust + /// #[macro_use] extern crate ruru; + /// + /// use ruru::{Boolean, Module, Class, Object, RString, VM}; + /// + /// methods!( + /// RString, + /// itself, + /// + /// fn is_blank() -> Boolean { + /// Boolean::new(itself.to_str().chars().all(|c| c.is_whitespace())) + /// } + /// ); + /// + /// fn main() { + /// # VM::init(); + /// Module::new("Blank").define(|itself| { + /// itself.mod_func("blank?", is_blank); + /// }); + /// + /// Class::from_existing("String").include("Blank"); + /// } + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// module Blank + /// def blank? + /// # simplified + /// self.chars.all? { |c| c == ' ' } + /// end + /// module_function :blank? + /// end + /// + /// String.include Blank + /// ``` + /// + /// ### Receiving arguments + /// + /// Raise `Fixnum` to the power of `exp`. + /// + /// ```rust + /// #[macro_use] extern crate ruru; + /// + /// use std::error::Error; + /// + /// use ruru::{Module, Fixnum, Object, VM}; + /// + /// methods!( + /// Fixnum, + /// itself, + /// + /// fn pow(exp: Fixnum) -> Fixnum { + /// // `exp` is not a valid `Fixnum`, raise an exception + /// if let Err(ref error) = exp { + /// VM::raise(error.to_exception(), error.description()); + /// } + /// + /// // We can safely unwrap here, because an exception was raised if `exp` is `Err` + /// let exp = exp.unwrap().to_i64() as u32; + /// + /// Fixnum::new(itself.to_i64().pow(exp)) + /// } + /// + /// fn pow_with_default_argument(exp: Fixnum) -> Fixnum { + /// let default_exp = 0; + /// let exp = exp.map(|exp| exp.to_i64()).unwrap_or(default_exp); + /// + /// let result = itself.to_i64().pow(exp as u32); + /// + /// Fixnum::new(result) + /// } + /// ); + /// + /// fn main() { + /// # VM::init(); + /// Module::from_existing("Fixnum").define(|itself| { + /// itself.mod_func("pow", pow); + /// itself.mod_func("pow_with_default_argument", pow_with_default_argument); + /// }); + /// } + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// module Fixnum + /// def pow(exp) + /// raise ArgumentError unless exp.is_a?(Fixnum) + /// + /// self ** exp + /// end + /// module_function :pow + /// + /// def pow_with_default_argument(exp) + /// default_exp = 0 + /// exp = default_exp unless exp.is_a?(Fixnum) + /// + /// self ** exp + /// end + /// module_function :pow_with_default_argument + /// end + /// ``` + pub fn define_module_function(&mut self, name: &str, callback: Callback) { + module::define_module_function(self.value(), name, callback); + } + + /// An alias for `define_module_function` (similar to Ruby `module_function :some_method`). + pub fn mod_func(&mut self, name: &str, callback: Callback) { + self.define_module_function(name, callback); + } + + /// Retrieves a constant from module. + /// + /// # Examples + /// + /// ``` + /// use ruru::{Module, Object, RString, VM}; + /// # VM::init(); + /// + /// Module::new("Greeter").define(|itself| { + /// itself.const_set("GREETING", &RString::new("Hello, World!")); + /// }); + /// + /// let greeting = Module::from_existing("Greeter") + /// .const_get("GREETING") + /// .try_convert_to::() + /// .unwrap(); + /// + /// assert_eq!(greeting.to_str(), "Hello, World!"); + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// module Greeter + /// GREETING = 'Hello, World!' + /// end + /// + /// # or + /// + /// Greeter = Module.new + /// Greeter.const_set('GREETING', 'Hello, World!') + /// + /// # ... + /// + /// Greeter::GREETING == 'Hello, World!' + /// + /// # or + /// + /// Greeter.const_get('GREETING') == 'Hello, World' + /// ``` + pub fn const_get(&self, name: &str) -> AnyObject { + let value = class::const_get(self.value(), name); + + AnyObject::from(value) + } + + /// Defines a constant for module. + /// + /// # Examples + /// + /// ``` + /// use ruru::{Module, Object, RString, VM}; + /// # VM::init(); + /// + /// Module::new("Greeter").define(|itself| { + /// itself.const_set("GREETING", &RString::new("Hello, World!")); + /// }); + /// + /// let greeting = Module::from_existing("Greeter") + /// .const_get("GREETING") + /// .try_convert_to::() + /// .unwrap(); + /// + /// assert_eq!(greeting.to_str(), "Hello, World!"); + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// module Greeter + /// GREETING = 'Hello, World!' + /// end + /// + /// # or + /// + /// Greeter = Module.new + /// Greeter.const_set('GREETING', 'Hello, World!') + /// + /// # ... + /// + /// Greeter::GREETING == 'Hello, World!' + /// + /// # or + /// + /// Greeter.const_get('GREETING') == 'Hello, World' + /// ``` + pub fn const_set(&mut self, name: &str, value: &T) { + class::const_set(self.value(), name, value.value()); + } + + /// Includes module into current module + /// + /// # Examples + /// + /// ``` + /// use ruru::{Module, VM}; + /// # VM::init(); + /// + /// Module::new("A"); + /// Module::new("B").include("A"); + /// + /// let b_module_ancestors = Module::from_existing("B").ancestors(); + /// + /// let expected_ancestors = vec![ + /// Module::from_existing("B"), + /// Module::from_existing("A") + /// ]; + /// + /// assert_eq!(b_module_ancestors, expected_ancestors); + /// ``` + pub fn include(&self, md: &str) { + module::include_module(self.value(), md); + } + + /// Prepends module into current module + /// + /// # Examples + /// + /// ``` + /// use ruru::{Module, VM}; + /// # VM::init(); + /// + /// Module::new("A"); + /// Module::new("B").prepend("A"); + /// + /// let b_module_ancestors = Module::from_existing("B").ancestors(); + /// + /// let expected_ancestors = vec![ + /// Module::from_existing("A"), + /// Module::from_existing("B") + /// ]; + /// + /// assert_eq!(b_module_ancestors, expected_ancestors); + /// ``` + pub fn prepend(&self, md: &str) { + module::prepend_module(self.value(), md); + } + + /// Defines an `attr_reader` for module + /// + /// # Examples + /// + /// ``` + /// use ruru::{Module, Object, VM}; + /// # VM::init(); + /// + /// Module::new("Test").define(|itself| { + /// itself.attr_reader("reader"); + /// }); + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// module Test + /// attr_reader :reader + /// end + /// ``` + pub fn attr_reader(&mut self, name: &str) { + class::define_attribute(self.value(), name, true, false); + } + + /// Defines an `attr_writer` for module + /// + /// # Examples + /// + /// ``` + /// use ruru::{Module, Object, VM}; + /// # VM::init(); + /// + /// Module::new("Test").define(|itself| { + /// itself.attr_writer("writer"); + /// }); + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// module Test + /// attr_writer :writer + /// end + /// ``` + pub fn attr_writer(&mut self, name: &str) { + class::define_attribute(self.value(), name, false, true); + } + + /// Defines an `attr_accessor` for module + /// + /// # Examples + /// + /// ``` + /// use ruru::{Module, Object, VM}; + /// # VM::init(); + /// + /// Module::new("Test").define(|itself| { + /// itself.attr_accessor("accessor"); + /// }); + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// module Test + /// attr_accessor :accessor + /// end + /// ``` + pub fn attr_accessor(&mut self, name: &str) { + class::define_attribute(self.value(), name, true, true); + } + + /// Wraps Rust structure into a new Ruby object of the current module. + /// + /// See the documentation for `wrappable_struct!` macro for more information. + /// + /// # Examples + /// + /// Wrap `Server` structs to `RubyServer` objects. Note: Example shows use + /// with class but the method still applies to module. + /// + /// ``` + /// #[macro_use] extern crate ruru; + /// #[macro_use] extern crate lazy_static; + /// + /// use ruru::{AnyObject, Class, Fixnum, Object, RString, VM}; + /// + /// // The structure which we want to wrap + /// pub struct Server { + /// host: String, + /// port: u16, + /// } + /// + /// impl Server { + /// fn new(host: String, port: u16) -> Self { + /// Server { + /// host: host, + /// port: port, + /// } + /// } + /// + /// fn host(&self) -> &str { + /// &self.host + /// } + /// + /// fn port(&self) -> u16 { + /// self.port + /// } + /// } + /// + /// wrappable_struct!(Server, ServerWrapper, SERVER_WRAPPER); + /// + /// class!(RubyServer); + /// + /// methods!( + /// RubyServer, + /// itself, + /// + /// fn ruby_server_new(host: RString, port: Fixnum) -> AnyObject { + /// let server = Server::new(host.unwrap().to_string(), + /// port.unwrap().to_i64() as u16); + /// + /// Class::from_existing("RubyServer").wrap_data(server, &*SERVER_WRAPPER) + /// } + /// + /// fn ruby_server_host() -> RString { + /// let host = itself.get_data(&*SERVER_WRAPPER).host(); + /// + /// RString::new(host) + /// } + /// + /// fn ruby_server_port() -> Fixnum { + /// let port = itself.get_data(&*SERVER_WRAPPER).port(); + /// + /// Fixnum::new(port as i64) + /// } + /// ); + /// + /// fn main() { + /// # VM::init(); + /// let data_class = Class::from_existing("Data"); + /// + /// Class::new("RubyServer", None).define(|itself| { + /// itself.def_self("new", ruby_server_new); + /// + /// itself.def("host", ruby_server_host); + /// itself.def("port", ruby_server_port); + /// }); + /// } + /// ``` + /// + /// To use the `RubyServer` class in Ruby: + /// + /// ```ruby + /// server = RubyServer.new("127.0.0.1", 3000) + /// + /// server.host == "127.0.0.1" + /// server.port == 3000 + /// ``` + pub fn wrap_data(&self, data: T, wrapper: &DataTypeWrapper) -> O { + let value = class::wrap_data(self.value(), data, wrapper); + + O::from(value) + } + + fn superclass_to_value(superclass: Option<&Class>) -> Value { + match superclass { + Some(class) => class.value(), + None => unsafe { rb_cObject }, + } + } +} + +impl From for Module { + fn from(value: Value) -> Self { + Module { value: value } + } +} + +impl Object for Module { + #[inline] + fn value(&self) -> Value { + self.value + } +} + +impl VerifiedObject for Module { + fn is_correct_type(object: &T) -> bool { + object.value().ty() == ValueType::Module + } + + fn error_message() -> &'static str { + "Error converting to Module" + } +} diff --git a/src/class/traits/object.rs b/src/class/traits/object.rs index de9fd8b9..eccf0b17 100644 --- a/src/class/traits/object.rs +++ b/src/class/traits/object.rs @@ -457,6 +457,126 @@ pub trait Object: From { class::define_method(self.value(), name, callback); } + /// Defines a private instance method for the given class or object. + /// + /// Use `methods!` macro to define a `callback`. + /// + /// You can also use `def_private()` alias for this function combined with `Class::define()` for a + /// nicer DSL. + /// + /// # Panics + /// + /// Ruby can raise an exception if you try to define instance method directly on an instance + /// of some class (like `Fixnum`, `String`, `Array` etc). + /// + /// Use this method only on classes (or singleton classes of objects). + /// + /// # Examples + /// + /// ### The famous String#blank? method + /// + /// ```rust + /// #[macro_use] extern crate ruru; + /// + /// use ruru::{Boolean, Class, Object, RString, VM}; + /// + /// methods!( + /// RString, + /// itself, + /// + /// fn is_blank() -> Boolean { + /// Boolean::new(itself.to_str().chars().all(|c| c.is_whitespace())) + /// } + /// ); + /// + /// fn main() { + /// # VM::init(); + /// Class::from_existing("String").define(|itself| { + /// itself.def_private("blank?", is_blank); + /// }); + /// } + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// class String + /// private def blank? + /// # simplified + /// self.chars.all? { |c| c == ' ' } + /// end + /// end + /// ``` + /// + /// ### Receiving arguments + /// + /// Raise `Fixnum` to the power of `exp`. + /// + /// ```rust + /// #[macro_use] extern crate ruru; + /// + /// use std::error::Error; + /// + /// use ruru::{Class, Fixnum, Object, VM}; + /// + /// methods!( + /// Fixnum, + /// itself, + /// + /// fn pow(exp: Fixnum) -> Fixnum { + /// // `exp` is not a valid `Fixnum`, raise an exception + /// if let Err(ref error) = exp { + /// VM::raise(error.to_exception(), error.description()); + /// } + /// + /// // We can safely unwrap here, because an exception was raised if `exp` is `Err` + /// let exp = exp.unwrap().to_i64() as u32; + /// + /// Fixnum::new(itself.to_i64().pow(exp)) + /// } + /// + /// fn pow_with_default_argument(exp: Fixnum) -> Fixnum { + /// let default_exp = 0; + /// let exp = exp.map(|exp| exp.to_i64()).unwrap_or(default_exp); + /// + /// let result = itself.to_i64().pow(exp as u32); + /// + /// Fixnum::new(result) + /// } + /// ); + /// + /// fn main() { + /// # VM::init(); + /// Class::from_existing("Fixnum").define(|itself| { + /// itself.def_private("pow", pow); + /// itself.def_private("pow_with_default_argument", pow_with_default_argument); + /// }); + /// } + /// ``` + /// + /// Ruby: + /// + /// ```ruby + /// class Fixnum + /// private + /// def pow(exp) + /// raise ArgumentError unless exp.is_a?(Fixnum) + /// + /// self ** exp + /// end + /// + /// def pow_with_default_argument(exp) + /// default_exp = 0 + /// exp = default_exp unless exp.is_a?(Fixnum) + /// + /// self ** exp + /// end + /// end + /// ``` + fn define_private_method(&mut self, name: &str, callback: Callback) { + class::define_private_method(self.value(), name, callback); + } + /// Defines a class method for given class or singleton method for object. /// /// Use `methods!` macro to define a `callback`. @@ -567,6 +687,11 @@ pub trait Object: From { self.define_method(name, callback); } + /// An alias for `define_private_method` (similar to Ruby syntax `private def some_method`). + fn def_private(&mut self, name: &str, callback: Callback) { + self.define_private_method(name, callback); + } + /// An alias for `define_singleton_method` (similar to Ruby `def self.some_method`). fn def_self(&mut self, name: &str, callback: Callback) { self.define_singleton_method(name, callback); diff --git a/src/dsl.rs b/src/dsl.rs index cf9f9116..7c03dc03 100644 --- a/src/dsl.rs +++ b/src/dsl.rs @@ -81,6 +81,89 @@ macro_rules! class { } } +/// Creates Rust structure for new Ruby module +/// +/// This macro does not define an actual Ruby module. It only creates structs for using +/// the module in Rust. To define the module in Ruby, use `Module` structure. +/// +/// # Examples +/// +/// ``` +/// #[macro_use] +/// extern crate ruru; +/// +/// use ruru::{Module, RString, Object, VM}; +/// +/// module!(Greeter); +/// +/// methods!( +/// Greeter, +/// itself, +/// +/// fn anonymous_greeting() -> RString { +/// RString::new("Hello stranger!") +/// } +/// +/// fn friendly_greeting(name: RString) -> RString { +/// let name = name +/// .map(|name| name.to_string()) +/// .unwrap_or("Anonymous".to_string()); +/// +/// let greeting = format!("Hello dear {}!", name); +/// +/// RString::new(&greeting) +/// } +/// ); +/// +/// fn main() { +/// # VM::init(); +/// Module::new("Greeter").define(|itself| { +/// itself.def("anonymous_greeting", anonymous_greeting); +/// itself.def("friendly_greeting", friendly_greeting); +/// }); +/// } +/// ``` +/// +/// Ruby: +/// +/// ```ruby +/// module Greeter +/// def anonymous_greeting +/// 'Hello stranger!' +/// end +/// +/// def friendly_greeting(name) +/// default_name = 'Anonymous' +/// +/// name = defaut_name unless name.is_a?(String) +/// +/// "Hello dear #{name}" +/// end +/// end +/// ``` +#[macro_export] +macro_rules! module { + ($module: ident) => { + #[derive(Debug, PartialEq)] + pub struct $module { + value: $crate::types::Value, + } + + impl From<$crate::types::Value> for $module { + fn from(value: $crate::types::Value) -> Self { + $module { value: value } + } + } + + impl $crate::Object for $module { + #[inline] + fn value(&self) -> $crate::types::Value { + self.value + } + } + } +} + /// Creates unsafe callbacks for Ruby methods /// /// This macro is unsafe, because: diff --git a/src/lib.rs b/src/lib.rs index 5f4af696..a0a2092e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ pub use class::gc::GC; pub use class::hash::Hash; pub use class::integer::Integer; pub use class::nil_class::NilClass; +pub use class::module::Module; pub use class::rproc::Proc; pub use class::string::RString; pub use class::symbol::Symbol;