mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
doc: add getting-started guide, C API reference, and architecture overview
Three new documents: - doc/guides/getting-started.md: building, running, and embedding mruby - doc/guides/capi.md: C API reference for values, classes, methods, etc. - doc/internal/architecture.md: internal architecture for developers Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -128,15 +128,19 @@ extensions in C and/or Ruby. For a guide on how to use mrbgems, consult the
|
||||
|
||||
- [About the Limitations of mruby](doc/limitations.md)
|
||||
- [About Amalgamation (Single-File Build)](doc/guides/amalgamation.md)
|
||||
- [C API Reference](doc/guides/capi.md)
|
||||
- [About the Compile](doc/guides/compile.md)
|
||||
- [About the Debugger with the `mrdb` Command](doc/guides/debugger.md)
|
||||
- [About GC Arena](doc/guides/gc-arena-howto.md)
|
||||
- [Getting Started with mruby](doc/guides/getting-started.md)
|
||||
- [About the mruby directory structure](doc/guides/hier.md)
|
||||
- [About Linking with `libmruby`](doc/guides/link.md)
|
||||
- [About Memory Allocator Customization](doc/guides/memory.md)
|
||||
- [About Memory Allocator Customization and Heap Regions](doc/guides/memory.md)
|
||||
- [About Build-time Configurations](doc/guides/mrbconf.md)
|
||||
- [About the Build-time Library Manager](doc/guides/mrbgems.md)
|
||||
- [ROM Method Tables for Memory-Efficient Method Registration](doc/guides/rom-method-table.md)
|
||||
- [About the Symbols](doc/guides/symbol.md)
|
||||
- [Internal Implementation / About mruby Architecture](doc/internal/architecture.md)
|
||||
- [Internal Implementation / About Value Boxing](doc/internal/boxing.md)
|
||||
- [Internal Implementation / About mruby Virtual Machine Instructions](doc/internal/opcode.md)
|
||||
|
||||
|
||||
@@ -0,0 +1,529 @@
|
||||
<!-- summary: C API Reference -->
|
||||
|
||||
# C API Reference
|
||||
|
||||
This document covers the mruby C API for embedding and extending mruby.
|
||||
|
||||
## Headers
|
||||
|
||||
```c
|
||||
#include <mruby.h> /* core types, state, class/method definition */
|
||||
#include <mruby/compile.h> /* mrb_load_string, mrb_load_file */
|
||||
#include <mruby/string.h> /* string operations */
|
||||
#include <mruby/array.h> /* array operations */
|
||||
#include <mruby/hash.h> /* hash operations */
|
||||
#include <mruby/data.h> /* wrapping C structs */
|
||||
#include <mruby/class.h> /* class inspection */
|
||||
#include <mruby/value.h> /* value type macros */
|
||||
#include <mruby/irep.h> /* loading precompiled bytecode */
|
||||
#include <mruby/error.h> /* error handling (mrb_protect etc.) */
|
||||
#include <mruby/variable.h> /* instance/class/global variables */
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
```c
|
||||
mrb_state *mrb = mrb_open(); /* create state with all gems */
|
||||
mrb_state *mrb = mrb_open_core(); /* create state without gems */
|
||||
mrb_close(mrb); /* close and free state */
|
||||
```
|
||||
|
||||
`mrb_open()` returns `NULL` on allocation failure. Always check the
|
||||
return value.
|
||||
|
||||
## Values
|
||||
|
||||
All Ruby values are represented as `mrb_value` in C.
|
||||
|
||||
### Creating Values
|
||||
|
||||
```c
|
||||
mrb_nil_value() /* nil */
|
||||
mrb_true_value() /* true */
|
||||
mrb_false_value() /* false */
|
||||
mrb_bool_value(mrb_bool b) /* true or false */
|
||||
mrb_fixnum_value(mrb_int i) /* Integer */
|
||||
mrb_float_value(mrb_state *mrb, mrb_float f) /* Float */
|
||||
mrb_symbol_value(mrb_sym sym) /* Symbol */
|
||||
mrb_obj_value(void *p) /* object pointer to value */
|
||||
mrb_cptr_value(mrb_state *mrb, void *p) /* C pointer */
|
||||
```
|
||||
|
||||
### Type Checking
|
||||
|
||||
```c
|
||||
mrb_type(v) /* returns enum mrb_vtype */
|
||||
mrb_nil_p(v) /* true if nil */
|
||||
mrb_integer_p(v) /* true if Integer */
|
||||
mrb_float_p(v) /* true if Float */
|
||||
mrb_symbol_p(v) /* true if Symbol */
|
||||
mrb_string_p(v) /* true if String */
|
||||
mrb_array_p(v) /* true if Array */
|
||||
mrb_hash_p(v) /* true if Hash */
|
||||
mrb_true_p(v) /* true if true */
|
||||
mrb_false_p(v) /* true if false */
|
||||
mrb_undef_p(v) /* true if undefined */
|
||||
mrb_immediate_p(v) /* true if not a heap object */
|
||||
```
|
||||
|
||||
### Extracting C Values
|
||||
|
||||
```c
|
||||
mrb_integer(v) /* mrb_int from Integer value */
|
||||
mrb_float(v) /* mrb_float from Float value */
|
||||
mrb_symbol(v) /* mrb_sym from Symbol value */
|
||||
mrb_ptr(v) /* void* from object value */
|
||||
mrb_str_to_cstr(mrb, v) /* const char* from String value */
|
||||
```
|
||||
|
||||
### Value Types
|
||||
|
||||
| `mrb_vtype` | Ruby Class | Notes |
|
||||
| ----------- | ---------- | ----- |
|
||||
| `MRB_TT_FALSE` | FalseClass/NilClass | `nil` has `MRB_TT_FALSE` |
|
||||
| `MRB_TT_TRUE` | TrueClass | |
|
||||
| `MRB_TT_INTEGER` | Integer | Immediate value |
|
||||
| `MRB_TT_FLOAT` | Float | May be immediate |
|
||||
| `MRB_TT_SYMBOL` | Symbol | Immediate value |
|
||||
| `MRB_TT_STRING` | String | Heap object |
|
||||
| `MRB_TT_ARRAY` | Array | Heap object |
|
||||
| `MRB_TT_HASH` | Hash | Heap object |
|
||||
| `MRB_TT_OBJECT` | Object | User-defined classes |
|
||||
| `MRB_TT_CLASS` | Class | |
|
||||
| `MRB_TT_MODULE` | Module | |
|
||||
| `MRB_TT_PROC` | Proc | |
|
||||
| `MRB_TT_CDATA` | (C data) | Wrapped C structs |
|
||||
| `MRB_TT_EXCEPTION` | Exception | |
|
||||
| `MRB_TT_FIBER` | Fiber | |
|
||||
|
||||
## Defining Classes and Modules
|
||||
|
||||
```c
|
||||
/* Define a class under Object */
|
||||
struct RClass *my_class = mrb_define_class(mrb, "MyClass", mrb->object_class);
|
||||
|
||||
/* Define a class under another class/module */
|
||||
struct RClass *inner = mrb_define_class_under(mrb, outer, "Inner", mrb->object_class);
|
||||
|
||||
/* Define a module */
|
||||
struct RClass *my_mod = mrb_define_module(mrb, "MyModule");
|
||||
struct RClass *inner_mod = mrb_define_module_under(mrb, outer, "InnerMod");
|
||||
|
||||
/* Include/prepend a module */
|
||||
mrb_include_module(mrb, my_class, my_mod);
|
||||
mrb_prepend_module(mrb, my_class, my_mod);
|
||||
|
||||
/* Look up existing class/module */
|
||||
struct RClass *c = mrb_class_get(mrb, "String");
|
||||
struct RClass *m = mrb_module_get(mrb, "Kernel");
|
||||
|
||||
/* Define a constant */
|
||||
mrb_define_const(mrb, my_class, "VERSION", mrb_str_new_lit(mrb, "1.0"));
|
||||
```
|
||||
|
||||
## Defining Methods
|
||||
|
||||
All C methods have the same signature:
|
||||
|
||||
```c
|
||||
static mrb_value
|
||||
my_method(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
/* self is the receiver */
|
||||
return mrb_nil_value();
|
||||
}
|
||||
```
|
||||
|
||||
Register with:
|
||||
|
||||
```c
|
||||
mrb_define_method(mrb, klass, "name", my_method, MRB_ARGS_NONE());
|
||||
mrb_define_class_method(mrb, klass, "name", my_method, MRB_ARGS_REQ(1));
|
||||
mrb_define_module_function(mrb, mod, "name", my_method, MRB_ARGS_ANY());
|
||||
```
|
||||
|
||||
### Argument Specifiers
|
||||
|
||||
| Macro | Meaning |
|
||||
| ----- | ------- |
|
||||
| `MRB_ARGS_NONE()` | No arguments |
|
||||
| `MRB_ARGS_REQ(n)` | `n` required arguments |
|
||||
| `MRB_ARGS_OPT(n)` | `n` optional arguments |
|
||||
| `MRB_ARGS_ARG(r,o)` | `r` required + `o` optional |
|
||||
| `MRB_ARGS_REST()` | Splat (`*args`) |
|
||||
| `MRB_ARGS_BLOCK()` | Block (`&block`) |
|
||||
| `MRB_ARGS_ANY()` | Any number (same as REST) |
|
||||
| `MRB_ARGS_KEY(n,rest)` | `n` keyword args, `rest`=1 for `**kw` |
|
||||
|
||||
These can be combined with `|`:
|
||||
|
||||
```c
|
||||
MRB_ARGS_REQ(1) | MRB_ARGS_OPT(2) | MRB_ARGS_BLOCK()
|
||||
```
|
||||
|
||||
## Parsing Arguments
|
||||
|
||||
`mrb_get_args()` extracts arguments from the Ruby call stack:
|
||||
|
||||
```c
|
||||
mrb_int mrb_get_args(mrb_state *mrb, const char *format, ...);
|
||||
```
|
||||
|
||||
### Format Specifiers
|
||||
|
||||
| Spec | Ruby Type | C Type(s) | Notes |
|
||||
| ---- | --------- | --------- | ----- |
|
||||
| `o` | any | `mrb_value` | No type check |
|
||||
| `i` | Numeric | `mrb_int` | Coerces to integer |
|
||||
| `f` | Numeric | `mrb_float` | Coerces to float |
|
||||
| `b` | any | `mrb_bool` | Truthiness |
|
||||
| `n` | String/Symbol | `mrb_sym` | Converts to symbol |
|
||||
| `s` | String | `const char*, mrb_int` | Pointer + length |
|
||||
| `z` | String | `const char*` | Null-terminated |
|
||||
| `S` | String | `mrb_value` | String value |
|
||||
| `A` | Array | `mrb_value` | Array value |
|
||||
| `H` | Hash | `mrb_value` | Hash value |
|
||||
| `C` | Class | `mrb_value` | Class/Module value |
|
||||
| `c` | Class | `struct RClass*` | Class pointer |
|
||||
| `a` | Array | `const mrb_value*, mrb_int` | Array pointer + length |
|
||||
| `d` | C Data | `void*` | Requires `mrb_data_type*` |
|
||||
| `&` | Block | `mrb_value` | Block argument |
|
||||
| `*` | rest | `const mrb_value*, mrb_int` | Rest arguments |
|
||||
| `\|` | — | — | Following args are optional |
|
||||
| `?` | — | `mrb_bool` | Was previous optional arg given? |
|
||||
| `:` | keywords | `mrb_kwargs` | Keyword arguments |
|
||||
|
||||
Adding `!` to `S`, `A`, `H`, `C`, `c`, `s`, `z`, `a`, `d` allows `nil`
|
||||
(returns NULL/zero for nil).
|
||||
|
||||
### Examples
|
||||
|
||||
```c
|
||||
/* def method(name, count) */
|
||||
const char *name; mrb_int len, count;
|
||||
mrb_get_args(mrb, "si", &name, &len, &count);
|
||||
|
||||
/* def method(required, optional=nil) */
|
||||
mrb_value req, opt = mrb_nil_value();
|
||||
mrb_get_args(mrb, "o|o", &req, &opt);
|
||||
|
||||
/* def method(*args) */
|
||||
const mrb_value *args; mrb_int argc;
|
||||
mrb_get_args(mrb, "*", &args, &argc);
|
||||
|
||||
/* def method(&block) */
|
||||
mrb_value block;
|
||||
mrb_get_args(mrb, "&", &block);
|
||||
|
||||
/* def method(name:, age: 0) */
|
||||
mrb_sym kw_names[] = { mrb_intern_lit(mrb, "name"), mrb_intern_lit(mrb, "age") };
|
||||
mrb_value kw_values[2];
|
||||
mrb_kwargs kw = { 2, 1, kw_names, kw_values, NULL };
|
||||
mrb_get_args(mrb, ":", &kw);
|
||||
/* kw_values[0] = name (required), kw_values[1] = age (optional, undef if not given) */
|
||||
```
|
||||
|
||||
## Calling Ruby Methods from C
|
||||
|
||||
```c
|
||||
/* Call obj.method(arg1, arg2) */
|
||||
mrb_funcall(mrb, obj, "method", 2, arg1, arg2);
|
||||
|
||||
/* Call with symbol (faster, no string lookup) */
|
||||
mrb_funcall_id(mrb, obj, mrb_intern_lit(mrb, "method"), 2, arg1, arg2);
|
||||
|
||||
/* Call with argv array */
|
||||
mrb_value argv[] = { arg1, arg2 };
|
||||
mrb_funcall_argv(mrb, obj, mrb_intern_lit(mrb, "method"), 2, argv);
|
||||
|
||||
/* Call with block */
|
||||
mrb_funcall_with_block(mrb, obj, mid, argc, argv, block);
|
||||
|
||||
/* Yield to block */
|
||||
mrb_yield(mrb, block, arg);
|
||||
mrb_yield_argv(mrb, block, argc, argv);
|
||||
```
|
||||
|
||||
## String Operations
|
||||
|
||||
```c
|
||||
/* Creation */
|
||||
mrb_str_new_lit(mrb, "hello") /* from string literal */
|
||||
mrb_str_new(mrb, ptr, len) /* from pointer + length */
|
||||
mrb_str_new_cstr(mrb, cstr) /* from null-terminated C string */
|
||||
mrb_str_new_static(mrb, ptr, len) /* from static data (no copy) */
|
||||
|
||||
/* Access */
|
||||
RSTRING_PTR(str) /* char* pointer */
|
||||
RSTRING_LEN(str) /* length */
|
||||
mrb_str_to_cstr(mrb, str) /* null-terminated (may copy) */
|
||||
|
||||
/* Modification */
|
||||
mrb_str_cat(mrb, str, ptr, len) /* append bytes */
|
||||
mrb_str_cat_cstr(mrb, str, cstr) /* append C string */
|
||||
mrb_str_cat_str(mrb, str, str2) /* append String */
|
||||
|
||||
/* Comparison */
|
||||
mrb_str_equal(mrb, str1, str2) /* equality */
|
||||
mrb_str_cmp(mrb, str1, str2) /* comparison (-1, 0, 1) */
|
||||
```
|
||||
|
||||
## Array Operations
|
||||
|
||||
```c
|
||||
/* Creation */
|
||||
mrb_ary_new(mrb) /* empty array */
|
||||
mrb_ary_new_capa(mrb, capa) /* preallocated */
|
||||
mrb_ary_new_from_values(mrb, n, vals) /* from C array */
|
||||
|
||||
/* Access */
|
||||
RARRAY_PTR(ary) /* mrb_value* pointer */
|
||||
RARRAY_LEN(ary) /* length */
|
||||
mrb_ary_entry(ary, idx) /* get element (no mrb needed) */
|
||||
|
||||
/* Modification */
|
||||
mrb_ary_push(mrb, ary, val) /* append */
|
||||
mrb_ary_pop(mrb, ary) /* remove last */
|
||||
mrb_ary_shift(mrb, ary) /* remove first */
|
||||
mrb_ary_unshift(mrb, ary, val) /* prepend */
|
||||
mrb_ary_set(mrb, ary, idx, val) /* set element */
|
||||
mrb_ary_splice(mrb, ary, pos, len, rpl) /* splice */
|
||||
mrb_ary_concat(mrb, ary, other) /* extend */
|
||||
```
|
||||
|
||||
## Hash Operations
|
||||
|
||||
```c
|
||||
/* Creation */
|
||||
mrb_hash_new(mrb) /* empty hash */
|
||||
|
||||
/* Access */
|
||||
mrb_hash_get(mrb, hash, key) /* get value */
|
||||
mrb_hash_fetch(mrb, hash, key, def) /* get with default */
|
||||
mrb_hash_key_p(mrb, hash, key) /* key exists? */
|
||||
mrb_hash_empty_p(mrb, hash) /* empty? */
|
||||
mrb_hash_size(mrb, hash) /* number of entries */
|
||||
|
||||
/* Modification */
|
||||
mrb_hash_set(mrb, hash, key, val) /* set key-value */
|
||||
mrb_hash_delete_key(mrb, hash, key) /* delete key */
|
||||
mrb_hash_merge(mrb, hash1, hash2) /* merge hash2 into hash1 */
|
||||
|
||||
/* Iteration */
|
||||
mrb_hash_keys(mrb, hash) /* Array of keys */
|
||||
mrb_hash_values(mrb, hash) /* Array of values */
|
||||
```
|
||||
|
||||
## Wrapping C Structures
|
||||
|
||||
To expose a C struct to Ruby, use `mrb_data_type` and `Data_Wrap_Struct`:
|
||||
|
||||
```c
|
||||
/* 1. Define the data type with a name and destructor */
|
||||
static void point_free(mrb_state *mrb, void *p) {
|
||||
mrb_free(mrb, p);
|
||||
}
|
||||
|
||||
static const mrb_data_type point_type = {
|
||||
"Point", point_free
|
||||
};
|
||||
|
||||
/* 2. Allocate and initialize */
|
||||
static mrb_value
|
||||
point_init(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_float x, y;
|
||||
mrb_get_args(mrb, "ff", &x, &y);
|
||||
|
||||
double *data = (double*)mrb_malloc(mrb, sizeof(double) * 2);
|
||||
data[0] = x;
|
||||
data[1] = y;
|
||||
|
||||
DATA_PTR(self) = data;
|
||||
DATA_TYPE(self) = &point_type;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* 3. Access the wrapped data */
|
||||
static mrb_value
|
||||
point_x(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
double *data = (double*)mrb_data_get_ptr(mrb, self, &point_type);
|
||||
return mrb_float_value(mrb, data[0]);
|
||||
}
|
||||
|
||||
/* 4. Register the class */
|
||||
struct RClass *point = mrb_define_class(mrb, "Point", mrb->object_class);
|
||||
MRB_SET_INSTANCE_TT(point, MRB_TT_CDATA);
|
||||
mrb_define_method(mrb, point, "initialize", point_init, MRB_ARGS_REQ(2));
|
||||
mrb_define_method(mrb, point, "x", point_x, MRB_ARGS_NONE());
|
||||
```
|
||||
|
||||
## Exception Handling
|
||||
|
||||
### Raising Exceptions
|
||||
|
||||
```c
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "something went wrong");
|
||||
mrb_raisef(mrb, E_ARGUMENT_ERROR, "expected %d, got %d", expected, actual);
|
||||
mrb_raise(mrb, E_TYPE_ERROR, "wrong type");
|
||||
```
|
||||
|
||||
Common exception classes: `E_RUNTIME_ERROR`, `E_TYPE_ERROR`,
|
||||
`E_ARGUMENT_ERROR`, `E_RANGE_ERROR`, `E_NAME_ERROR`,
|
||||
`E_NOMETHOD_ERROR`, `E_NOTIMP_ERROR`, `E_KEY_ERROR`.
|
||||
|
||||
### Catching Exceptions
|
||||
|
||||
```c
|
||||
/* Check after mrb_load_string or mrb_funcall */
|
||||
mrb_value result = mrb_load_string(mrb, code);
|
||||
if (mrb->exc) {
|
||||
mrb_print_error(mrb);
|
||||
mrb->exc = NULL; /* clear exception */
|
||||
}
|
||||
|
||||
/* Protected call */
|
||||
mrb_bool error;
|
||||
mrb_value result = mrb_protect(mrb, my_func, data, &error);
|
||||
if (error) {
|
||||
/* result contains the exception */
|
||||
}
|
||||
```
|
||||
|
||||
## Symbols
|
||||
|
||||
```c
|
||||
/* Create symbol from string */
|
||||
mrb_sym sym = mrb_intern_lit(mrb, "name"); /* from literal */
|
||||
mrb_sym sym = mrb_intern_cstr(mrb, cstr); /* from C string */
|
||||
mrb_sym sym = mrb_intern(mrb, ptr, len); /* from pointer + length */
|
||||
|
||||
/* Symbol to string */
|
||||
const char *name = mrb_sym_name(mrb, sym);
|
||||
mrb_int len;
|
||||
const char *name = mrb_sym_name_len(mrb, sym, &len);
|
||||
```
|
||||
|
||||
## Instance Variables
|
||||
|
||||
```c
|
||||
/* Get/set instance variables on an object */
|
||||
mrb_iv_get(mrb, obj, mrb_intern_lit(mrb, "@x"));
|
||||
mrb_iv_set(mrb, obj, mrb_intern_lit(mrb, "@x"), val);
|
||||
mrb_iv_defined(mrb, obj, mrb_intern_lit(mrb, "@x"));
|
||||
mrb_iv_remove(mrb, obj, mrb_intern_lit(mrb, "@x"));
|
||||
```
|
||||
|
||||
## Global Variables
|
||||
|
||||
```c
|
||||
mrb_gv_get(mrb, mrb_intern_lit(mrb, "$verbose"));
|
||||
mrb_gv_set(mrb, mrb_intern_lit(mrb, "$verbose"), mrb_true_value());
|
||||
```
|
||||
|
||||
## Class Variables
|
||||
|
||||
```c
|
||||
mrb_cv_get(mrb, klass, mrb_intern_lit(mrb, "@@count"));
|
||||
mrb_cv_set(mrb, klass, mrb_intern_lit(mrb, "@@count"), mrb_fixnum_value(0));
|
||||
```
|
||||
|
||||
## Loading and Executing Code
|
||||
|
||||
```c
|
||||
/* Load and execute a string (requires mruby-compiler gem) */
|
||||
mrb_value result = mrb_load_string(mrb, "1 + 2");
|
||||
|
||||
/* Load and execute a file */
|
||||
FILE *f = fopen("script.rb", "r");
|
||||
mrb_value result = mrb_load_file(mrb, f);
|
||||
fclose(f);
|
||||
|
||||
/* Load precompiled bytecode (no compiler needed) */
|
||||
mrb_value result = mrb_load_irep(mrb, bytecode_array);
|
||||
```
|
||||
|
||||
## GC Arena
|
||||
|
||||
When creating many temporary Ruby objects in C, use the GC arena to
|
||||
prevent them from being collected prematurely:
|
||||
|
||||
```c
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
/* create temporary objects here */
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
```
|
||||
|
||||
See [gc-arena-howto.md](gc-arena-howto.md) for details.
|
||||
|
||||
## Memory Allocation
|
||||
|
||||
```c
|
||||
void *p = mrb_malloc(mrb, size); /* raises on failure */
|
||||
void *p = mrb_calloc(mrb, nmemb, size); /* zero-initialized */
|
||||
void *p = mrb_realloc(mrb, ptr, size); /* resize */
|
||||
mrb_free(mrb, p); /* free */
|
||||
|
||||
/* NULL-returning variants (for custom error handling) */
|
||||
void *p = mrb_malloc_simple(mrb, size);
|
||||
void *p = mrb_realloc_simple(mrb, ptr, size);
|
||||
```
|
||||
|
||||
## Type Conversion
|
||||
|
||||
```c
|
||||
mrb_obj_as_string(mrb, val) /* to_s */
|
||||
mrb_inspect(mrb, val) /* inspect */
|
||||
mrb_any_to_s(mrb, val) /* default to_s */
|
||||
mrb_str_to_integer(mrb, str, base, badcheck) /* String to Integer */
|
||||
mrb_str_to_dbl(mrb, str, badcheck) /* String to Float */
|
||||
mrb_ensure_float_type(mrb, val) /* ensure Float */
|
||||
```
|
||||
|
||||
## Object Comparison
|
||||
|
||||
```c
|
||||
mrb_equal(mrb, a, b) /* Ruby == */
|
||||
mrb_eql(mrb, a, b) /* Ruby eql? */
|
||||
mrb_obj_eq(mrb, a, b) /* Ruby equal? (identity) */
|
||||
mrb_cmp(mrb, a, b) /* Ruby <=> (returns mrb_int) */
|
||||
```
|
||||
|
||||
## Object Inspection
|
||||
|
||||
```c
|
||||
mrb_obj_classname(mrb, obj) /* class name as C string */
|
||||
mrb_obj_class(mrb, obj) /* class as RClass* */
|
||||
mrb_obj_is_kind_of(mrb, obj, klass) /* is_a? / kind_of? */
|
||||
mrb_obj_respond_to(mrb, klass, mid) /* respond_to? */
|
||||
mrb_obj_id(obj) /* object_id */
|
||||
mrb_obj_freeze(mrb, obj) /* freeze */
|
||||
mrb_obj_dup(mrb, obj) /* dup */
|
||||
```
|
||||
|
||||
## Compile-Time Flags
|
||||
|
||||
When compiling C code that uses mruby, you must use the same flags as
|
||||
the library was built with. Use `mruby-config` to get them:
|
||||
|
||||
```console
|
||||
$ build/host/bin/mruby-config --cflags # compiler flags
|
||||
$ build/host/bin/mruby-config --ldflags # linker flags
|
||||
$ build/host/bin/mruby-config --libs # libraries
|
||||
```
|
||||
|
||||
Key macros that affect ABI:
|
||||
|
||||
| Macro | Effect |
|
||||
| ----- | ------ |
|
||||
| `MRB_NO_BOXING` | Struct-based values (larger, debuggable) |
|
||||
| `MRB_WORD_BOXING` | Single-word values (fast, 32-bit safe) |
|
||||
| `MRB_NAN_BOXING` | NaN-tagged values (default on 32-bit) |
|
||||
| `MRB_NO_FLOAT` | Disable Float support |
|
||||
| `MRB_INT64` | 64-bit integers |
|
||||
| `MRB_USE_FLOAT32` | 32-bit floats |
|
||||
|
||||
Mismatching these between library and application causes silent
|
||||
data corruption.
|
||||
@@ -0,0 +1,281 @@
|
||||
<!-- summary: Getting Started with mruby -->
|
||||
|
||||
# Getting Started with mruby
|
||||
|
||||
This guide walks you through building mruby, running your first Ruby program,
|
||||
and embedding mruby in a C application.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You need:
|
||||
|
||||
- C compiler (`gcc` or `clang`)
|
||||
- Ruby 2.5 or later (for the build system)
|
||||
- `rake` (bundled with Ruby)
|
||||
- `git` (optional, for cloning the source)
|
||||
|
||||
## Building mruby
|
||||
|
||||
Clone the repository and build:
|
||||
|
||||
```console
|
||||
$ git clone https://github.com/mruby/mruby.git
|
||||
$ cd mruby
|
||||
$ rake
|
||||
```
|
||||
|
||||
This compiles the default configuration and produces:
|
||||
|
||||
- `bin/mruby` — Ruby script interpreter
|
||||
- `bin/mirb` — interactive Ruby shell
|
||||
- `bin/mrbc` — bytecode compiler
|
||||
- `build/host/lib/libmruby.a` — library for embedding
|
||||
|
||||
## Running Ruby Code
|
||||
|
||||
### Interactive shell
|
||||
|
||||
```console
|
||||
$ bin/mirb
|
||||
mirb - Pair interactive mruby
|
||||
> puts "Hello, mruby!"
|
||||
Hello, mruby!
|
||||
=> nil
|
||||
> 1 + 2
|
||||
=> 3
|
||||
```
|
||||
|
||||
### Running a script file
|
||||
|
||||
Create `hello.rb`:
|
||||
|
||||
```ruby
|
||||
puts "Hello from mruby!"
|
||||
```
|
||||
|
||||
Run it:
|
||||
|
||||
```console
|
||||
$ bin/mruby hello.rb
|
||||
Hello from mruby!
|
||||
```
|
||||
|
||||
### One-liner
|
||||
|
||||
```console
|
||||
$ bin/mruby -e 'puts "Hello!"'
|
||||
Hello!
|
||||
```
|
||||
|
||||
## Compiling to Bytecode
|
||||
|
||||
mruby can compile Ruby scripts to bytecode (`.mrb` files) for faster
|
||||
loading and deployment without source code:
|
||||
|
||||
```console
|
||||
$ bin/mrbc hello.rb # produces hello.mrb
|
||||
$ bin/mruby -b hello.mrb # run bytecode
|
||||
Hello from mruby!
|
||||
```
|
||||
|
||||
You can also generate C source from Ruby scripts:
|
||||
|
||||
```console
|
||||
$ bin/mrbc -Bhello_code hello.rb # produces hello.c with byte array
|
||||
```
|
||||
|
||||
This generates a C file with a `const uint8_t hello_code[]` array that
|
||||
can be loaded with `mrb_load_irep()` in your C application.
|
||||
|
||||
## Embedding mruby in C
|
||||
|
||||
The primary use case of mruby is embedding in C/C++ applications.
|
||||
|
||||
### Minimal example
|
||||
|
||||
Create `embed.c`:
|
||||
|
||||
```c
|
||||
#include <mruby.h>
|
||||
#include <mruby/compile.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
mrb_state *mrb = mrb_open();
|
||||
if (!mrb) return 1;
|
||||
|
||||
mrb_load_string(mrb, "puts 'Hello from embedded mruby!'");
|
||||
if (mrb->exc) {
|
||||
mrb_print_error(mrb);
|
||||
}
|
||||
|
||||
mrb_close(mrb);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Compile and link
|
||||
|
||||
Use `mruby-config` to get the correct compiler and linker flags:
|
||||
|
||||
```console
|
||||
$ gcc -I include `build/host/bin/mruby-config --cflags` embed.c \
|
||||
`build/host/bin/mruby-config --ldflags --libs` -o embed
|
||||
$ ./embed
|
||||
Hello from embedded mruby!
|
||||
```
|
||||
|
||||
**Important**: Always use `mruby-config --cflags` when compiling code
|
||||
that uses mruby. The build configuration may define macros (such as
|
||||
`MRB_NO_BOXING` or `MRB_USE_BIGINT`) that change the internal data
|
||||
layout. Compiling without these flags causes silent data corruption.
|
||||
|
||||
### Calling Ruby from C
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <mruby.h>
|
||||
#include <mruby/compile.h>
|
||||
#include <mruby/string.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
mrb_state *mrb = mrb_open();
|
||||
|
||||
/* Define a Ruby method */
|
||||
mrb_load_string(mrb, "def greet(name) \"Hello, #{name}!\" end");
|
||||
|
||||
/* Call it from C */
|
||||
mrb_value result = mrb_funcall(mrb, mrb_top_self(mrb),
|
||||
"greet", 1, mrb_str_new_lit(mrb, "World"));
|
||||
printf("%s\n", mrb_str_to_cstr(mrb, result));
|
||||
|
||||
mrb_close(mrb);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Defining C functions callable from Ruby
|
||||
|
||||
```c
|
||||
#include <mruby.h>
|
||||
#include <mruby/compile.h>
|
||||
|
||||
static mrb_value
|
||||
my_add(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_int a, b;
|
||||
mrb_get_args(mrb, "ii", &a, &b);
|
||||
return mrb_fixnum_value(a + b);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
mrb_state *mrb = mrb_open();
|
||||
|
||||
/* Define method on Kernel (available everywhere) */
|
||||
mrb_define_method(mrb, mrb->kernel_module, "my_add",
|
||||
my_add, MRB_ARGS_REQ(2));
|
||||
|
||||
mrb_load_string(mrb, "puts my_add(3, 4)"); /* prints 7 */
|
||||
|
||||
mrb_close(mrb);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Loading Precompiled Bytecode
|
||||
|
||||
For deployment without the compiler gem, precompile your Ruby code:
|
||||
|
||||
```console
|
||||
$ bin/mrbc -Bruby_code app.rb
|
||||
```
|
||||
|
||||
Then load in C:
|
||||
|
||||
```c
|
||||
#include <mruby.h>
|
||||
#include <mruby/irep.h>
|
||||
#include "app.c" /* contains ruby_code[] */
|
||||
|
||||
int main(void)
|
||||
{
|
||||
mrb_state *mrb = mrb_open();
|
||||
mrb_load_irep(mrb, ruby_code);
|
||||
if (mrb->exc) {
|
||||
mrb_print_error(mrb);
|
||||
}
|
||||
mrb_close(mrb);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
This approach does not require the `mruby-compiler` gem, resulting in
|
||||
a smaller binary.
|
||||
|
||||
## Customizing the Build
|
||||
|
||||
mruby's functionality is controlled by the build configuration file.
|
||||
The default is `build_config/default.rb`.
|
||||
|
||||
### Using a custom configuration
|
||||
|
||||
```console
|
||||
$ MRUBY_CONFIG=build_config/minimal.rb rake
|
||||
```
|
||||
|
||||
### Selecting gems
|
||||
|
||||
Gems add features to mruby. A minimal configuration:
|
||||
|
||||
```ruby
|
||||
MRuby::Build.new do |conf|
|
||||
conf.toolchain :gcc
|
||||
|
||||
# Core language extensions
|
||||
conf.gem core: 'mruby-array-ext'
|
||||
conf.gem core: 'mruby-string-ext'
|
||||
conf.gem core: 'mruby-hash-ext'
|
||||
|
||||
# Tools
|
||||
conf.gem core: 'mruby-bin-mruby' # mruby command
|
||||
conf.gem core: 'mruby-bin-mirb' # interactive shell
|
||||
conf.gem core: 'mruby-bin-mrbc' # bytecode compiler
|
||||
|
||||
# Compiler (needed for mrb_load_string)
|
||||
conf.gem core: 'mruby-compiler'
|
||||
end
|
||||
```
|
||||
|
||||
### Using a gembox
|
||||
|
||||
Gemboxes are predefined collections of gems:
|
||||
|
||||
```ruby
|
||||
MRuby::Build.new do |conf|
|
||||
conf.toolchain :gcc
|
||||
conf.gembox 'default' # standard set of gems
|
||||
end
|
||||
```
|
||||
|
||||
## Amalgamation (Single-File Build)
|
||||
|
||||
For the simplest integration, use amalgamation to combine all mruby
|
||||
source into a single `mruby.c` and `mruby.h`:
|
||||
|
||||
```console
|
||||
$ rake amalgam
|
||||
$ gcc -I build/host/amalgam your_app.c build/host/amalgam/mruby.c -o your_app -lm
|
||||
```
|
||||
|
||||
See [amalgamation.md](amalgamation.md) for details.
|
||||
|
||||
## What's Next
|
||||
|
||||
- [Compile](compile.md) — full build system reference
|
||||
- [mrbgems](mrbgems.md) — creating and using gems
|
||||
- [Linking](link.md) — linking `libmruby` to applications
|
||||
- [Build-time Configurations](mrbconf.md) — compile-time options
|
||||
- [GC Arena](gc-arena-howto.md) — managing GC arena in C extensions
|
||||
- [Limitations](../limitations.md) — differences from CRuby
|
||||
@@ -0,0 +1,293 @@
|
||||
<!-- summary: About mruby Architecture -->
|
||||
|
||||
# mruby Architecture
|
||||
|
||||
This document provides a map of mruby's internals for developers who
|
||||
want to understand, debug, or contribute to the codebase.
|
||||
|
||||
## Overview
|
||||
|
||||
mruby's execution pipeline:
|
||||
|
||||
```text
|
||||
Ruby source → Parser → AST → Code Generator → Bytecode (irep)
|
||||
↓
|
||||
VM → Result
|
||||
```
|
||||
|
||||
The design priority is **memory > performance > readability**.
|
||||
|
||||
## Object Model
|
||||
|
||||
All heap-allocated Ruby objects share a common header (`MRB_OBJECT_HEADER`):
|
||||
|
||||
```text
|
||||
struct RBasic (8 bytes on 64-bit)
|
||||
┌──────────────┬─────┬──────────┬────────┬───────┐
|
||||
│ RClass *c │ tt │ gc_color │ frozen │ flags │
|
||||
│ (class ptr) │ 8b │ 3b │ 1b │ 20b │
|
||||
└──────────────┴─────┴──────────┴────────┴───────┘
|
||||
```
|
||||
|
||||
All object structs embed this header via `MRB_OBJECT_HEADER`:
|
||||
|
||||
| Struct | Ruby Type | Extra Fields |
|
||||
| ------ | --------- | ------------ |
|
||||
| `RObject` | Object instances | `iv` (instance variables) |
|
||||
| `RClass` | Class/Module | `iv`, `mt` (method table), `super` |
|
||||
| `RString` | String | embedded or heap buffer, length |
|
||||
| `RArray` | Array | embedded or heap buffer, length |
|
||||
| `RHash` | Hash | hash table or k-v array |
|
||||
| `RProc` | Proc/Lambda | `irep` or C function, environment |
|
||||
| `RData` | C data wrapper | `void *data`, `mrb_data_type` |
|
||||
| `RFiber` | Fiber | `mrb_context` |
|
||||
| `RException` | Exception | `iv` |
|
||||
|
||||
Immediate values (Integer, Symbol, `true`, `false`, `nil`) are encoded
|
||||
directly in `mrb_value` without heap allocation. The encoding depends on
|
||||
the boxing mode (see [boxing.md](boxing.md)).
|
||||
|
||||
Objects must fit within 5 words (`mrb_static_assert_object_size`).
|
||||
|
||||
## Virtual Machine
|
||||
|
||||
### Execution Context
|
||||
|
||||
The VM uses two stacks stored in `mrb_context`:
|
||||
|
||||
```text
|
||||
mrb_context
|
||||
├── stbase..stend value stack (mrb_value[])
|
||||
├── cibase..ciend call info stack (mrb_callinfo[])
|
||||
├── ci current call frame (→ cibase[n])
|
||||
└── status fiber state
|
||||
```
|
||||
|
||||
Each method call pushes a `mrb_callinfo` frame:
|
||||
|
||||
```text
|
||||
mrb_callinfo
|
||||
├── mid method symbol
|
||||
├── proc current RProc
|
||||
├── stack pointer into value stack
|
||||
├── pc program counter (bytecode position)
|
||||
├── n, nk argument counts
|
||||
├── cci nonzero if called from C
|
||||
└── u.env / u.target_class
|
||||
```
|
||||
|
||||
The value stack is register-based: local variables and temporaries
|
||||
occupy fixed register slots (determined at compile time by `nregs`).
|
||||
|
||||
### Dispatch Loop
|
||||
|
||||
The main loop in `mrb_vm_run()` (`src/vm.c`) decodes and dispatches
|
||||
opcodes. Each opcode operates on registers:
|
||||
|
||||
```text
|
||||
OP_MOVE R(a) = R(b)
|
||||
OP_LOADI R(a) = integer
|
||||
OP_ADD R(a) = R(a) + R(a+1)
|
||||
OP_SEND R(a) = call R(a).method(R(a+1)..R(a+n))
|
||||
OP_RETURN return R(a)
|
||||
```
|
||||
|
||||
See [opcode.md](opcode.md) for the full instruction set.
|
||||
|
||||
### Method Dispatch
|
||||
|
||||
When `OP_SEND` executes:
|
||||
|
||||
1. Look up method in receiver's class method table (`mt`)
|
||||
2. Walk superclass chain if not found
|
||||
3. If method is a C function (`MRB_METHOD_CFUNC_P`), call directly
|
||||
4. If method is Ruby (irep-based), push new `mrb_callinfo` and jump
|
||||
|
||||
Method tables use a hash map (`mrb_mt_tbl`). A per-state method cache
|
||||
speeds up repeated lookups.
|
||||
|
||||
### Exception Handling
|
||||
|
||||
mruby uses `setjmp`/`longjmp` for exception unwinding (or C++ exceptions
|
||||
if `enable_cxx_exception` is configured). The `rescue`/`ensure` entries
|
||||
are tracked in `mrb_callinfo` entries and unwound when an exception
|
||||
propagates.
|
||||
|
||||
## Garbage Collector
|
||||
|
||||
### Tri-Color Mark-and-Sweep
|
||||
|
||||
The GC uses tri-color marking with incremental execution:
|
||||
|
||||
| Color | Meaning |
|
||||
| ----- | ------- |
|
||||
| White | Unmarked — candidate for collection |
|
||||
| Gray | Marked but children not yet scanned |
|
||||
| Black | Fully marked (reachable) |
|
||||
| Red | Static/ROM — never collected |
|
||||
|
||||
### GC Phases
|
||||
|
||||
```text
|
||||
GC_STATE_ROOT → GC_STATE_MARK → GC_STATE_SWEEP → GC_STATE_ROOT
|
||||
```
|
||||
|
||||
1. **Root marking**: marks objects directly reachable from the VM
|
||||
(stack, globals, arena)
|
||||
2. **Incremental marking**: processes gray objects from `gray_stack[]`,
|
||||
marking their children. Runs in small steps between VM instructions.
|
||||
3. **Sweep**: iterates heap pages, freeing white objects and flipping
|
||||
the white bit for the next cycle
|
||||
|
||||
### Write Barriers
|
||||
|
||||
When a black object stores a reference to a white object, a write
|
||||
barrier is required to prevent premature collection:
|
||||
|
||||
```c
|
||||
mrb_field_write_barrier(mrb, parent, child); /* specific field */
|
||||
mrb_write_barrier(mrb, obj); /* general */
|
||||
```
|
||||
|
||||
The barrier paints the parent gray, adding it back to the scan queue.
|
||||
|
||||
### GC Arena
|
||||
|
||||
The arena (`gc.arena[]`) protects newly created objects from collection
|
||||
before they are stored in a reachable location. C extensions must save
|
||||
and restore the arena index when creating many temporary objects:
|
||||
|
||||
```c
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
/* ... create temporary objects ... */
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
```
|
||||
|
||||
See [../guides/gc-arena-howto.md](../guides/gc-arena-howto.md) for details.
|
||||
|
||||
### Heap Structure
|
||||
|
||||
Objects are allocated from fixed-size heap pages (`HEAP_PAGE_SIZE`
|
||||
objects per page). Each page maintains a freelist of available slots.
|
||||
Dead objects are returned to their page's freelist during sweep.
|
||||
|
||||
The `mrb_gc_add_region()` API allows pre-allocating contiguous heap
|
||||
regions for reduced fragmentation and faster allocation.
|
||||
|
||||
### Generational Mode
|
||||
|
||||
Optional generational GC (`mrb_gc_generational_mode_set`) treats
|
||||
objects surviving a full GC as "old generation" and performs minor
|
||||
collections that only scan young objects.
|
||||
|
||||
## Compiler Pipeline
|
||||
|
||||
### Stage 1: Parser (`mrbgems/mruby-compiler/core/parse.y`)
|
||||
|
||||
The yacc/bison grammar (~16K lines) produces an AST of `mrb_ast_node`
|
||||
linked structures. The parser tracks lexer state (expression context,
|
||||
heredocs, string interpolation) and local variable scopes.
|
||||
|
||||
### Stage 2: Code Generator (`mrbgems/mruby-compiler/core/codegen.c`)
|
||||
|
||||
Walks the AST and emits bytecode into `mrb_irep` structures:
|
||||
|
||||
```text
|
||||
mrb_irep
|
||||
├── iseq[] instruction stream (bytecode)
|
||||
├── pool[] constant pool (strings, numbers)
|
||||
├── syms[] symbol table (method names, variable names)
|
||||
├── reps[] child ireps (nested methods, blocks)
|
||||
├── nlocals local variable count
|
||||
└── nregs register count (locals + temporaries)
|
||||
```
|
||||
|
||||
The code generator:
|
||||
|
||||
- Assigns register slots for local variables and temporaries
|
||||
- Emits instructions for each AST node type
|
||||
- Builds jump tables for control flow (if/unless/while/for)
|
||||
- Encodes exception handler ranges for rescue/ensure
|
||||
|
||||
### Stage 3: Execution
|
||||
|
||||
The irep is wrapped in an `RProc` and executed by the VM. Alternative
|
||||
loading paths:
|
||||
|
||||
- `mrb_load_string()` — compile from source and execute
|
||||
- `mrb_load_irep()` — load precompiled `.mrb` bytecode
|
||||
- `mrbc` tool — ahead-of-time compilation to `.mrb` or C array
|
||||
|
||||
## Source File Map
|
||||
|
||||
### Core (`src/`)
|
||||
|
||||
| File | Responsibility |
|
||||
| ---- | -------------- |
|
||||
| `vm.c` | Bytecode dispatch loop, method invocation |
|
||||
| `state.c` | `mrb_state` init/close, irep management |
|
||||
| `gc.c` | Garbage collector (mark-sweep, incremental) |
|
||||
| `class.c` | Class/module definition, method tables |
|
||||
| `object.c` | Core object operations |
|
||||
| `variable.c` | Instance/class/global variables, object shapes |
|
||||
| `proc.c` | Proc/Lambda/closure handling |
|
||||
| `array.c` | Array implementation |
|
||||
| `string.c` | String implementation (embedded, shared, heap) |
|
||||
| `hash.c` | Hash implementation (open addressing) |
|
||||
| `numeric.c` | Integer/Float arithmetic |
|
||||
| `symbol.c` | Symbol table and interning |
|
||||
| `range.c` | Range implementation |
|
||||
| `error.c` | Exception creation, raise, backtrace |
|
||||
| `kernel.c` | Kernel module methods |
|
||||
| `load.c` | `.mrb` bytecode loading |
|
||||
| `dump.c` | Bytecode serialization (write `.mrb`) |
|
||||
| `print.c` | Print/puts/p output |
|
||||
| `backtrace.c` | Stack trace generation |
|
||||
|
||||
### Compiler (`mrbgems/mruby-compiler/core/`)
|
||||
|
||||
| File | Responsibility |
|
||||
| ---- | -------------- |
|
||||
| `parse.y` | Yacc grammar → AST |
|
||||
| `y.tab.c` | Generated parser (from parse.y) |
|
||||
| `codegen.c` | AST → bytecode (irep) |
|
||||
| `node.h` | AST node type definitions |
|
||||
|
||||
### Key Headers (`include/mruby/`)
|
||||
|
||||
| Header | Contents |
|
||||
| ------ | -------- |
|
||||
| `mruby.h` | `mrb_state`, core API declarations |
|
||||
| `value.h` | `mrb_value`, type enums, value macros |
|
||||
| `object.h` | `RBasic`, `RObject`, object header |
|
||||
| `class.h` | `RClass`, method table types |
|
||||
| `string.h` | `RString`, string macros |
|
||||
| `array.h` | `RArray`, array macros |
|
||||
| `hash.h` | `RHash`, hash API |
|
||||
| `data.h` | `RData`, C data wrapping |
|
||||
| `irep.h` | `mrb_irep`, bytecode structures |
|
||||
| `compile.h` | Compiler context, `mrb_load_string` |
|
||||
| `boxing_*.h` | Value boxing implementations |
|
||||
|
||||
## mrbgems System
|
||||
|
||||
Gems are the module system for mruby. Each gem lives in
|
||||
`mrbgems/mruby-*/` and contains:
|
||||
|
||||
```text
|
||||
mruby-example/
|
||||
├── mrbgem.rake gem specification (name, deps, bins)
|
||||
├── src/ C source files
|
||||
├── mrblib/ Ruby source files (compiled to bytecode)
|
||||
├── include/ C headers
|
||||
├── test/ mrbtest test files
|
||||
└── bintest/ binary test files (CRuby)
|
||||
```
|
||||
|
||||
At build time, gem Ruby files are compiled with `mrbc` and linked into
|
||||
`libmruby.a`. Gem initialization runs in dependency order via
|
||||
`gem_init.c` (auto-generated).
|
||||
|
||||
GemBoxes (`mrbgems/*.gembox`) define named collections of gems
|
||||
(e.g., `default.gembox` includes `stdlib`, `stdlib-ext`, `stdlib-io`,
|
||||
`math`, `metaprog`, and binary tools).
|
||||
Reference in New Issue
Block a user