1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
//! Magnus is a library for writing Ruby extentions in Rust, or running Ruby
//! code from Rust.
//!
//! # Overview
//!
//! All Ruby objects are represented by [`Value`]. To make it easier to work
//! with values that are instances of specific classes a number of wrapper
//! types are available. These wrappers will [`Deref`](`std::ops::Deref`) to
//! `Value`, so you can still use `Value`'s methods on them.
//!
//! | Ruby Class | Magnus Type |
//! |------------|-------------|
//! | `String`   | [`RString`] |
//! | `Integer`  | [`Integer`] |
//! | `Float`    | [`Float`]   |
//! | `Array`    | [`RArray`]  |
//! | `Hash`     | [`RHash`]   |
//! | `Symbol`   | [`Symbol`]  |
//! | `Class`    | [`RClass`]  |
//! | `Module`   | [`RModule`] |
//!
//! When writing Rust code to be called from Ruby the [`init`] attribute can
//! be used to mark your init function that Ruby will call when your library
//! is `require`d.
//!
//! When embedding Ruby in a Rust program, see [`embed::init`] for initialising
//! the Ruby VM.
//!
//! The [`method`](`macro@method`) macro can be used to wrap a Rust function
//! with automatic type conversion and error handing so it can be exposed to
//! Ruby. The [`TryConvert`] trait handles conversions from Ruby to Rust, and
//! anything implementing `Into<Value>` can be returned to Ruby. See the
//! [`Module`] and [`Object`] traits for defining methods.
//!
//! [`Value::funcall`] can be used to call Ruby methods from Rust.
//!
//! See the [`wrap`] attribute macro for wrapping Rust types as Ruby objects.
//!
//! ## Safety
//!
//! When using Magnus, in Rust code, Ruby objects must be kept on the stack. If
//! objects are moved to the heap the Ruby GC can not reach them, and they may
//! be garbage collected. This could lead to memory safety issues.
//!
//! It is not possible to enforce this rule in Rust's type system or via the
//! borrow checker, users of Magnus must maintain this rule manually.
//!
//! While it would be possible to mark any functions that could expose this
//! unsafty as `unsafe`, that would mean that almost every interaction with
//! Ruby would be `unsafe`. This would leave no way to differentiate the
//! *really* unsafe functions that need much more care to use.
//!
//! # Examples
//!
//! ```
//! use magnus::{define_module, function, method, prelude::*, Error};
//!
//! #[magnus::wrap(class = "Euclid::Point", free_immediatly, size)]
//! struct Point {
//!     x: isize,
//!     y: isize,
//! }
//!
//! impl Point {
//!     fn new(x: isize, y: isize) -> Self {
//!         Self { x, y }
//!     }
//!
//!     fn x(&self) -> isize {
//!         self.x
//!     }
//!
//!     fn y(&self) -> isize {
//!         self.y
//!     }
//! }
//!
//! fn distance(a: &Point, b: &Point) -> f64 {
//!     (((b.x - a.x).pow(2) + (b.y - a.y).pow(2)) as f64).sqrt()
//! }
//!
//! #[magnus::init]
//! fn init() -> Result<(), Error> {
//!     let module = define_module("Euclid")?;
//!     let class = module.define_class("Point", Default::default())?;
//!     class.define_singleton_method("new", function!(Point::new, 2));
//!     class.define_method("x", method!(Point::x, 0));
//!     class.define_method("y", method!(Point::y, 0));
//!     module.define_module_function("distance", function!(distance, 2));
//!     Ok(())
//! }
//! ```

#![warn(missing_docs)]

mod binding;
pub mod block;
pub mod class;
#[cfg(feature = "embed")]
pub mod embed;
mod enumerator;
pub mod error;
pub mod exception;
mod float;
pub mod gc;
mod integer;
pub mod method;
pub mod module;
mod object;
mod r_array;
mod r_bignum;
mod r_complex;
mod r_file;
mod r_float;
pub mod r_hash;
mod r_match;
mod r_object;
mod r_rational;
mod r_regexp;
pub mod r_string;
pub mod r_struct;
pub mod r_typed_data;
mod range;
mod ruby_sys;
pub mod scan_args;
mod symbol;
mod try_convert;
pub mod value;

use std::{ffi::CString, mem::transmute};

pub use magnus_macros::{init, wrap, DataTypeFunctions, TypedData};

use error::protect;
use method::Method;
use ruby_sys::{
    rb_define_class, rb_define_global_function, rb_define_module, rb_define_variable, rb_errinfo,
    rb_eval_string_protect, rb_set_errinfo, VALUE,
};

pub use value::{Fixnum, Flonum, StaticSymbol, Value, QFALSE, QNIL, QTRUE};
pub use {
    binding::Binding,
    class::RClass,
    enumerator::Enumerator,
    error::Error,
    exception::{Exception, ExceptionClass},
    float::Float,
    integer::Integer,
    module::Module,
    module::RModule,
    object::Object,
    r_array::RArray,
    r_bignum::RBignum,
    r_complex::RComplex,
    r_file::RFile,
    r_float::RFloat,
    r_hash::RHash,
    r_match::RMatch,
    r_object::RObject,
    r_rational::RRational,
    r_regexp::RRegexp,
    r_string::RString,
    r_struct::RStruct,
    r_typed_data::{DataType, DataTypeFunctions, RTypedData, TypedData},
    range::Range,
    symbol::Symbol,
    try_convert::{ArgList, TryConvert},
};

/// Traits that commonly should be in scope.
pub mod prelude {
    pub use crate::{module::Module, object::Object};
}

/// Utility to simplify initialising a static with [`std::sync::Once`].
///
/// Similar (but less generally useful) to
/// [`lazy_static!`](https://crates.io/crates/lazy_static) without an external
/// dependency.
///
/// # Examples
///
/// ```
/// use magnus::{define_class, memoize, RClass};
///
/// fn foo_class() -> &'static RClass {
///     memoize!(RClass: define_class("Foo", Default::default()).unwrap())
/// }
/// ```
#[macro_export]
macro_rules! memoize {
    ($type:ty: $val:expr) => {{
        static INIT: std::sync::Once = std::sync::Once::new();
        static mut VALUE: Option<$type> = None;
        unsafe {
            INIT.call_once(|| {
                VALUE = Some($val);
            });
            VALUE.as_ref().unwrap()
        }
    }};
}

/// Define a class in the root scope.
pub fn define_class(name: &str, superclass: RClass) -> Result<RClass, Error> {
    debug_assert_value!(superclass);
    let name = CString::new(name).unwrap();
    let superclass = superclass.as_rb_value();
    unsafe {
        let res = protect(|| Value::new(rb_define_class(name.as_ptr(), superclass)));
        res.map(|v| RClass::from_value(v).unwrap())
    }
}

/// Define a module in the root scope.
pub fn define_module(name: &str) -> Result<RModule, Error> {
    let name = CString::new(name).unwrap();
    unsafe {
        let res = protect(|| Value::new(rb_define_module(name.as_ptr())));
        res.map(|v| RModule::from_value(v).unwrap())
    }
}

/// Define a global variable.
pub fn define_global_variable<T: Into<Value>>(name: &str, initial: T) -> Result<*mut Value, Error> {
    let initial = initial.into();
    debug_assert_value!(initial);
    let name = CString::new(name).unwrap();
    let ptr = Box::into_raw(Box::new(initial));
    unsafe {
        rb_define_variable(name.as_ptr(), ptr as *mut VALUE);
    }
    Ok(ptr)
}

/// Define a method in the root scope.
pub fn define_global_function<M>(name: &str, func: M)
where
    M: Method,
{
    let name = CString::new(name).unwrap();
    unsafe {
        rb_define_global_function(name.as_ptr(), transmute(func.as_ptr()), M::arity().into());
    }
}

/// Evaluate a string of Ruby code, converting the result to a `T`.
///
/// Ruby will use the 'ASCII-8BIT' (aka binary) encoding for any Ruby string
/// literals in the passed string of Ruby code. See the
/// [`eval`](macro@crate::eval) macro or [`Binding::eval`] for alternatives that
/// support utf-8.
///
/// Errors if `s` contains a null byte, the conversion fails, or on an uncaught
/// Ruby exception.
///
/// # Examples
///
/// ```
/// # let _cleanup = unsafe { magnus::embed::init() };
///
/// assert_eq!(magnus::eval::<i64>("1 + 2").unwrap(), 3);
/// ```
pub fn eval<T>(s: &str) -> Result<T, Error>
where
    T: TryConvert,
{
    let mut state = 0;
    // safe ffi to Ruby, captures raised errors (+ brake, throw, etc) as state
    let result = unsafe {
        let s =
            CString::new(s).map_err(|e| Error::new(exception::script_error(), e.to_string()))?;
        rb_eval_string_protect(s.as_c_str().as_ptr(), &mut state as *mut _)
    };

    match state {
        // Tag::None
        0 => Value::new(result).try_convert(),
        // Tag::Raise
        6 => unsafe {
            let ex = Exception::from_rb_value_unchecked(rb_errinfo());
            rb_set_errinfo(QNIL.as_rb_value());
            Err(Error::Exception(ex))
        },
        other => Err(Error::Jump(unsafe { transmute(other) })),
    }
}