rudy4 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +114 -0
- data/LICENSE +21 -0
- data/Makefile +137 -0
- data/README.md +400 -0
- data/bin/rudy4 +6 -0
- data/examples/hello_gem/README.md +91 -0
- data/examples/hello_gem/ext/hello_gem/extconf.rb +75 -0
- data/examples/hello_gem/ext/hello_gem/hello_gem.d +122 -0
- data/examples/hello_gem/hello_gem.gemspec +35 -0
- data/examples/hello_gem/lib/hello_gem/version.rb +5 -0
- data/examples/hello_gem/lib/hello_gem.rb +32 -0
- data/examples/hello_gem/test/smoke.sh +81 -0
- data/experiments/exp_compact.d +178 -0
- data/experiments/exp_convert.d +93 -0
- data/experiments/exp_cross.d +144 -0
- data/experiments/exp_defargs.d +301 -0
- data/experiments/exp_exc.d +265 -0
- data/experiments/exp_gcstorm.d +169 -0
- data/experiments/exp_http.d +254 -0
- data/experiments/exp_nonregression.d +209 -0
- data/experiments/exp_objproto.d +123 -0
- data/experiments/exp_perf.d +81 -0
- data/experiments/exp_wrap.d +186 -0
- data/lib/rudy4/cli.rb +204 -0
- data/lib/rudy4/doctor.rb +295 -0
- data/lib/rudy4/error.rb +10 -0
- data/lib/rudy4/generator.rb +172 -0
- data/lib/rudy4/templates.rb +484 -0
- data/lib/rudy4/vendor.rb +36 -0
- data/lib/rudy4/version.rb +5 -0
- data/lib/rudy4.rb +16 -0
- data/scripts/abi_audit.sh +64 -0
- data/scripts/memcheck.sh +77 -0
- data/scripts/vendor_check.sh +33 -0
- data/scripts/vendor_sync.sh +25 -0
- data/src/dexitest.d +278 -0
- data/src/dextest.d +119 -0
- data/src/dtypetest.d +159 -0
- data/src/rudy/args.d +253 -0
- data/src/rudy/bind/value.d +306 -0
- data/src/rudy/convert.d +221 -0
- data/src/rudy/defs.d +440 -0
- data/src/rudy/objproto.d +282 -0
- data/src/rudy/typeddata.d +143 -0
- data/test/dextest_loader.rb +6 -0
- data/test/exitest.rb +113 -0
- data/test/rudy4_test.rb +291 -0
- data/test/run.rb +74 -0
- data/test/typedtest.rb +69 -0
- data/vendor/rudy/args.d +253 -0
- data/vendor/rudy/bind/value.d +306 -0
- data/vendor/rudy/convert.d +221 -0
- data/vendor/rudy/defs.d +440 -0
- data/vendor/rudy/objproto.d +282 -0
- data/vendor/rudy/typeddata.d +143 -0
- metadata +106 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/// Hand-written binding to the Ruby C API (Ruby 3.x / 4.x).
|
|
2
|
+
///
|
|
3
|
+
/// Design notes:
|
|
4
|
+
/// - `VALUE` is `uintptr_t` on modern 64-bit CRuby. We never touch RBasic
|
|
5
|
+
/// or any R* struct layout: all access goes through exported functions
|
|
6
|
+
/// (rb_float_value, rb_ary_entry, rb_string_value_ptr, ...), the way the
|
|
7
|
+
/// Ruby project intends binary extensions to work post-1.9.
|
|
8
|
+
/// - Global variables (rb_cObject, rb_eArgError, ...) are BSS data symbols;
|
|
9
|
+
/// bound with `extern(C) extern __gshared`.
|
|
10
|
+
/// - `C_long` aliases mirror the old RuDy/PyD trick so the same templates
|
|
11
|
+
/// work on 32/64-bit.
|
|
12
|
+
|
|
13
|
+
module rudy.bind.value;
|
|
14
|
+
|
|
15
|
+
extern (C):
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------- core types
|
|
18
|
+
|
|
19
|
+
alias ulong VALUE; /// Ruby object reference (uintptr_t, tagged)
|
|
20
|
+
alias long SIGNED_VALUE; /// intptr_t
|
|
21
|
+
|
|
22
|
+
// special constants (values are tag bits, identical on all modern rubies)
|
|
23
|
+
enum VALUE Qnil_ = cast(VALUE)0x04; // RUBY_Qnil
|
|
24
|
+
enum VALUE Qtrue_ = cast(VALUE)0x14; // RUBY_Qtrue
|
|
25
|
+
enum VALUE Qfalse_ = cast(VALUE)0x00; // RUBY_Qfalse
|
|
26
|
+
enum VALUE Qundef_ = cast(VALUE)0x24; // RUBY_Qundef (internal)
|
|
27
|
+
|
|
28
|
+
// Fixed-arity method callback shapes. For a fixed arity N, Ruby calls the
|
|
29
|
+
// C function as `VALUE f(VALUE self, VALUE a1, .., VALUE aN)`; the
|
|
30
|
+
// variadic (argc/argv) style is only used when the registered arity is < 0.
|
|
31
|
+
// rudy's def! always registers fixed arity, so trampolines below use these.
|
|
32
|
+
alias VALUE function(VALUE self) AnyMethod_0;
|
|
33
|
+
alias VALUE function(VALUE self, VALUE a1) AnyMethod_1;
|
|
34
|
+
alias VALUE function(VALUE self, VALUE a1, VALUE a2) AnyMethod_2;
|
|
35
|
+
alias VALUE function(VALUE self, VALUE a1, VALUE a2, VALUE a3) AnyMethod_3;
|
|
36
|
+
alias void* AnyMethod; // what rb_define_method actually takes
|
|
37
|
+
|
|
38
|
+
/// Callback shape for rb_protect: `VALUE (*protected)(VALUE data)`
|
|
39
|
+
alias VALUE function(VALUE) ProtectFn;
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------- API fns
|
|
42
|
+
|
|
43
|
+
// ---- definitions ---------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
VALUE rb_define_class(const(char)* name, VALUE superclass);
|
|
46
|
+
VALUE rb_define_module(const(char)* name);
|
|
47
|
+
VALUE rb_define_class_under(VALUE outer, const(char)* name, VALUE superclass);
|
|
48
|
+
VALUE rb_define_module_under(VALUE outer, const(char)* name);
|
|
49
|
+
void rb_extend_object(VALUE mod);
|
|
50
|
+
void rb_define_method(VALUE klass, const(char)* name, AnyMethod func, int argc);
|
|
51
|
+
void rb_define_module_function(VALUE mod, const(char)* name, AnyMethod func, int argc);
|
|
52
|
+
void rb_define_global_function(const(char)* name, AnyMethod func, int argc);
|
|
53
|
+
void rb_define_alloc_func(VALUE klass, VALUE function(VALUE) alloc);
|
|
54
|
+
void rb_define_alias(VALUE klass, const(char)* alt, const(char)* old);
|
|
55
|
+
void rb_include_module(VALUE klass, VALUE mod);
|
|
56
|
+
void rb_define_attr(VALUE klass, const(char)* name, int read, int write);
|
|
57
|
+
void rb_define_const(VALUE klass, const(char)* name, VALUE value);
|
|
58
|
+
void rb_define_class_variable(VALUE klass, const(char)* name, VALUE value);
|
|
59
|
+
|
|
60
|
+
ID rb_intern(const(char)* name);
|
|
61
|
+
alias long ID;
|
|
62
|
+
|
|
63
|
+
// ---- calling / conversions ------------------------------------------------
|
|
64
|
+
|
|
65
|
+
VALUE rb_funcall(VALUE recv, ID mid, int argc, ...);
|
|
66
|
+
VALUE rb_funcallv(VALUE recv, ID mid, int argc, const(VALUE)* argv);
|
|
67
|
+
VALUE rb_funcallv_public(VALUE recv, ID mid, int argc, const(VALUE)* argv);
|
|
68
|
+
// call mid with argv and a Proc/block (use for methods taking a block:
|
|
69
|
+
// sort, each, map...) — NOT passable as a positional arg via rb_funcallv
|
|
70
|
+
VALUE rb_funcall_with_block(VALUE recv, ID mid, int argc, const(VALUE)* argv, VALUE proc);
|
|
71
|
+
VALUE rb_obj_call_init(VALUE obj, int argc, const(VALUE)* argv);
|
|
72
|
+
VALUE rb_eval_string(const(char)* str);
|
|
73
|
+
VALUE rb_obj_alloc(VALUE klass);
|
|
74
|
+
|
|
75
|
+
// numbers
|
|
76
|
+
long rb_num2long(VALUE);
|
|
77
|
+
long rb_num2ll(VALUE);
|
|
78
|
+
double rb_num2dbl(VALUE);
|
|
79
|
+
VALUE rb_ull2inum(C_ulonglong);
|
|
80
|
+
VALUE rb_uint2inum(C_ulong);
|
|
81
|
+
ulong rb_num2ulong(VALUE);
|
|
82
|
+
VALUE rb_ll2inum(long);
|
|
83
|
+
int rb_num2int(VALUE); // raises RangeError outside int range
|
|
84
|
+
uint rb_num2uint(VALUE); // raises RangeError outside uint range
|
|
85
|
+
VALUE rb_int2inum(C_long); // C ABI: intptr_t — was misbound as int (torture
|
|
86
|
+
// finding #4: wrong width silently truncates on
|
|
87
|
+
// SysV x86-64 because of register reuse)
|
|
88
|
+
VALUE rb_float_new(double);
|
|
89
|
+
double rb_float_value(VALUE);
|
|
90
|
+
VALUE rb_obj_is_instance_of(VALUE, VALUE); // exported; rb_type is macro-only
|
|
91
|
+
VALUE rb_Integer(VALUE);
|
|
92
|
+
|
|
93
|
+
// strings
|
|
94
|
+
VALUE rb_str_new(const(char)* ptr, long len);
|
|
95
|
+
VALUE rb_str_new_cstr(const(char)* ptr);
|
|
96
|
+
VALUE rb_str_dup(VALUE);
|
|
97
|
+
void rb_str_modify(VALUE);
|
|
98
|
+
VALUE rb_str_cat(VALUE str, const(char)* ptr, long len);
|
|
99
|
+
VALUE rb_str_buf_cat(VALUE str, const(char)* ptr, long len);
|
|
100
|
+
VALUE rb_string_value(VALUE* value);
|
|
101
|
+
/// Correct signature: takes a pointer to the slot (may replace its
|
|
102
|
+
/// contents, hence volatile). See rstring.h:289.
|
|
103
|
+
char* rb_string_value_ptr(VALUE* ptr);
|
|
104
|
+
const(char)* rb_string_value_cstr(VALUE str);
|
|
105
|
+
long rb_str_strlen(VALUE);
|
|
106
|
+
VALUE rb_str_to_str(VALUE str);
|
|
107
|
+
VALUE rb_obj_as_string(VALUE obj);
|
|
108
|
+
VALUE rb_any_to_s(VALUE obj);
|
|
109
|
+
|
|
110
|
+
// arrays
|
|
111
|
+
VALUE rb_ary_new();
|
|
112
|
+
VALUE rb_ary_new_capa(C_long capa);
|
|
113
|
+
VALUE rb_ary_new_from_values(C_long n, const(VALUE)* vals);
|
|
114
|
+
VALUE rb_ary_push(VALUE ary, VALUE item);
|
|
115
|
+
VALUE rb_ary_entry(VALUE ary, long offset);
|
|
116
|
+
VALUE rb_ary_store(VALUE ary, long idx, VALUE item);
|
|
117
|
+
VALUE rb_ary_join(VALUE ary, VALUE sep);
|
|
118
|
+
VALUE rb_ary_to_s(VALUE ary);
|
|
119
|
+
VALUE rb_obj_class(VALUE obj);
|
|
120
|
+
const(char)* rb_class2name(VALUE klass); // exported (class.c); name of class/module
|
|
121
|
+
|
|
122
|
+
// hashes
|
|
123
|
+
VALUE rb_hash_new();
|
|
124
|
+
VALUE rb_hash_lookup2(VALUE hash, VALUE key, VALUE def); // no Hash#default raise
|
|
125
|
+
VALUE rb_hash_aset(VALUE hash, VALUE key, VALUE val);
|
|
126
|
+
VALUE rb_hash_aref(VALUE hash, VALUE key);
|
|
127
|
+
size_t rb_hash_size(VALUE hash);
|
|
128
|
+
|
|
129
|
+
// ivars / constants
|
|
130
|
+
VALUE rb_iv_get(VALUE obj, const(char)* name);
|
|
131
|
+
VALUE rb_iv_set(VALUE obj, const(char)* name, VALUE value);
|
|
132
|
+
VALUE rb_const_get(VALUE klass, ID name);
|
|
133
|
+
void rb_const_set(VALUE klass, ID name, VALUE value);
|
|
134
|
+
|
|
135
|
+
// equality / inspection
|
|
136
|
+
VALUE rb_equal(VALUE a, VALUE b);
|
|
137
|
+
VALUE rb_obj_id(VALUE obj);
|
|
138
|
+
VALUE rb_inspect(VALUE obj);
|
|
139
|
+
VALUE rb_id2sym(ID);
|
|
140
|
+
VALUE rb_sym2str(VALUE sym);
|
|
141
|
+
const(char)* rb_id2name(ID);
|
|
142
|
+
VALUE rb_obj_frozen_p(VALUE obj);
|
|
143
|
+
void rb_check_frozen(VALUE obj); // raises FrozenError when frozen
|
|
144
|
+
VALUE rb_obj_freeze(VALUE obj); // returns self, sets FL_FREEZE
|
|
145
|
+
int rb_typeddata_is_kind_of(VALUE obj, in void* data_type); // non-raising
|
|
146
|
+
// (check_typeddata raises; this doesn't)
|
|
147
|
+
|
|
148
|
+
// exceptions / control flow
|
|
149
|
+
// (rb_raise/rb_fatal never return; D has no `noreturn` attribute for extern(C))
|
|
150
|
+
void rb_raise(VALUE exc, const(char)* fmt, ...);
|
|
151
|
+
void rb_fatal(const(char)* fmt, ...);
|
|
152
|
+
void rb_throw(const(char)* tag, VALUE val);
|
|
153
|
+
void rb_throw_obj(VALUE tag, VALUE val);
|
|
154
|
+
void rb_exit(int status);
|
|
155
|
+
VALUE rb_protect(ProtectFn fn, VALUE data, int* state);
|
|
156
|
+
// Re-raise an existing exception object (the only way to re-raise the SAME
|
|
157
|
+
// exception VALUE from D — rb_raise formats a NEW exception). noreturn.
|
|
158
|
+
void rb_exc_raise(VALUE exc);
|
|
159
|
+
// After a rb_protect state != 0 (or rb_rescue/rb_ensure rescue path), the
|
|
160
|
+
// pending exception is fetched via rb_errinfo. Without binding it D cannot
|
|
161
|
+
// inspect what Ruby raised while crossing the boundary.
|
|
162
|
+
VALUE rb_errinfo();
|
|
163
|
+
void rb_set_errinfo(VALUE err);
|
|
164
|
+
// int rb_check_arity(int argc, int min, int max); -- inline-only in ruby/internal/intern/error.h, NOT exported; keep out of bindings
|
|
165
|
+
|
|
166
|
+
// GC
|
|
167
|
+
void rb_gc_register_address(VALUE* address);
|
|
168
|
+
void rb_gc_unregister_address(VALUE* address);
|
|
169
|
+
VALUE rb_gc_location(VALUE); // remap a VALUE after GC.compact moved it
|
|
170
|
+
void rb_gc_mark(VALUE); // conservative mark (no compaction update)
|
|
171
|
+
void rb_gc_mark_movable(VALUE); // mark + this slot is updated via rb_gc_location in dcompact
|
|
172
|
+
VALUE rb_gc_enable();
|
|
173
|
+
VALUE rb_gc_disable();
|
|
174
|
+
|
|
175
|
+
// introspection
|
|
176
|
+
int rb_check_funcall(VALUE recv, ID mid, int argc, const(VALUE)* argv);
|
|
177
|
+
int rb_respond_to(VALUE obj, ID mid);
|
|
178
|
+
|
|
179
|
+
// blocks / iteration (bound for PR#4 — issue #4)
|
|
180
|
+
// rb_block_call_func signature (ruby/internal/iterator.h):
|
|
181
|
+
// VALUE f(VALUE yielded_arg, VALUE callback_arg, int argc,
|
|
182
|
+
// const VALUE* argv, VALUE blockarg)
|
|
183
|
+
alias VALUE function(VALUE yielded_arg, VALUE callback_arg, int argc,
|
|
184
|
+
const(VALUE)* argv, VALUE blockarg) BlockCallFunc;
|
|
185
|
+
alias VALUE function(VALUE data) BodyFunc; // rb_rescue/rb_ensure b_proc
|
|
186
|
+
alias VALUE function(VALUE data2, VALUE exc) RescueFunc; // rb_rescue r_proc
|
|
187
|
+
VALUE rb_yield(VALUE val);
|
|
188
|
+
VALUE rb_yield_values(int n, ...);
|
|
189
|
+
VALUE rb_yield_values2(int n, const(VALUE)* argv);
|
|
190
|
+
VALUE rb_yield_splat(VALUE ary);
|
|
191
|
+
VALUE rb_yield_block(VALUE yielded_arg, VALUE callback_arg, int argc,
|
|
192
|
+
const(VALUE)* argv, VALUE blockarg); // as rb_block_call_func
|
|
193
|
+
VALUE rb_block_call(VALUE obj, ID mid, int argc, const(VALUE)* argv,
|
|
194
|
+
BlockCallFunc proc, VALUE data2);
|
|
195
|
+
int rb_block_given_p();
|
|
196
|
+
VALUE rb_block_proc(); // materialize the passed block as a Proc VALUE
|
|
197
|
+
VALUE rb_each(VALUE obj);
|
|
198
|
+
|
|
199
|
+
// structured exception handling from D (only rb_protect was bound before)
|
|
200
|
+
VALUE rb_rescue(BodyFunc b_proc, VALUE data1, RescueFunc r_proc, VALUE data2);
|
|
201
|
+
// rb_rescue2: variadic list of exception classes terminated by (VALUE)0.
|
|
202
|
+
// D has no C varargs forwarding — bind rb_vrescue2 (takes va_list) is not
|
|
203
|
+
// portable either; the common single-class case can use rb_rescue. For
|
|
204
|
+
// multiple classes call with the variadic ABI is NOT provided; use
|
|
205
|
+
// rb_protect + rb_respond_to rescue semantics at Ruby level instead.
|
|
206
|
+
// (Kept out deliberately: calling variadic C from D is not possible.)
|
|
207
|
+
VALUE rb_ensure(BodyFunc b_proc, VALUE data1, BodyFunc e_proc, VALUE data2);
|
|
208
|
+
|
|
209
|
+
// per-object methods
|
|
210
|
+
void rb_define_singleton_method(VALUE obj, const(char)* mid, AnyMethod func, int arity);
|
|
211
|
+
void rb_define_private_method(VALUE klass, const(char)* mid, AnyMethod func, int arity);
|
|
212
|
+
void rb_define_protected_method(VALUE klass, const(char)* mid, AnyMethod func, int arity);
|
|
213
|
+
|
|
214
|
+
// GVL (Ruby threading interop)
|
|
215
|
+
alias void function(void*) UnblockFunc; // rb_unblock_function_t
|
|
216
|
+
alias void* function(void*) NoGvlFunc; // func passed to *_without_gvl
|
|
217
|
+
void* rb_thread_call_without_gvl(NoGvlFunc func, void* data1,
|
|
218
|
+
UnblockFunc ubf, void* data2);
|
|
219
|
+
void* rb_thread_call_without_gvl2(NoGvlFunc func, void* data1,
|
|
220
|
+
UnblockFunc ubf, void* data2);
|
|
221
|
+
void* rb_thread_call_with_gvl(NoGvlFunc func, void* data1);
|
|
222
|
+
enum void* RUBY_UBF_IO = cast(void*) -1; // special UBF for blocking IO
|
|
223
|
+
|
|
224
|
+
// argument scanning (the C variadic stores into VALUE* slots)
|
|
225
|
+
int rb_scan_args(int argc, const(VALUE)* argv, const(char)* fmt, ...);
|
|
226
|
+
|
|
227
|
+
// struct wrapping (minimal; not used by core rudy)
|
|
228
|
+
alias void function(void* ptr) RUBY_DATA_FUNC_MARK;
|
|
229
|
+
alias void function(void* ptr) RUBY_DATA_FUNC_FREE;
|
|
230
|
+
VALUE rb_data_object_wrap(VALUE klass, void* ptr,
|
|
231
|
+
RUBY_DATA_FUNC_MARK mark, RUBY_DATA_FUNC_FREE free);
|
|
232
|
+
// void* rb_data_object_get(VALUE obj); -- inline-only in ruby/internal/core/rdata.h, NOT exported (use typeddata helper)
|
|
233
|
+
|
|
234
|
+
// typed-data (rb_data_object_get is an inline macro; these are real
|
|
235
|
+
// exported functions — see rudy/typeddata.d for a D-friendly helper)
|
|
236
|
+
// NOTE: RbDataType (rb_data_type_t) is defined in rudy/typeddata.d to keep
|
|
237
|
+
// this raw binding free of struct definitions; cast accordingly.
|
|
238
|
+
alias size_t function(const void*) RUBY_DATA_FUNC_DSIZE;
|
|
239
|
+
VALUE rb_data_typed_object_wrap(VALUE klass, void* ptr, in void* data_type);
|
|
240
|
+
void* rb_check_typeddata(VALUE obj, in void* data_type);
|
|
241
|
+
// NB: the C prototype is (VALUE klass, size_t size, const rb_data_type_t*)
|
|
242
|
+
// — the pre-audit binding dropped `size` and silently worked through
|
|
243
|
+
// register reuse (finding #21). Signature now matches the header.
|
|
244
|
+
void* rb_data_typed_object_zalloc(VALUE klass, size_t size, in void* data_type);
|
|
245
|
+
|
|
246
|
+
// ---------------------------------------------------------------- globals
|
|
247
|
+
|
|
248
|
+
extern __gshared VALUE rb_cObject;
|
|
249
|
+
extern __gshared VALUE rb_cInteger;
|
|
250
|
+
extern __gshared VALUE rb_cFloat;
|
|
251
|
+
extern __gshared VALUE rb_cString;
|
|
252
|
+
extern __gshared VALUE rb_cArray;
|
|
253
|
+
extern __gshared VALUE rb_cHash;
|
|
254
|
+
extern __gshared VALUE rb_mKernel;
|
|
255
|
+
|
|
256
|
+
extern __gshared VALUE rb_eException;
|
|
257
|
+
extern __gshared VALUE rb_eStandardError;
|
|
258
|
+
extern __gshared VALUE rb_eRuntimeError;
|
|
259
|
+
extern __gshared VALUE rb_eTypeError;
|
|
260
|
+
extern __gshared VALUE rb_eArgError;
|
|
261
|
+
extern __gshared VALUE rb_eRangeError;
|
|
262
|
+
extern __gshared VALUE rb_eNameError;
|
|
263
|
+
extern __gshared VALUE rb_eNoMethodError;
|
|
264
|
+
extern __gshared VALUE rb_eNotImpError;
|
|
265
|
+
extern __gshared VALUE rb_eStopIteration;
|
|
266
|
+
extern __gshared VALUE rb_eKeyError;
|
|
267
|
+
extern __gshared VALUE rb_eIndexError;
|
|
268
|
+
extern __gshared VALUE rb_eLoadError;
|
|
269
|
+
extern __gshared VALUE rb_eSystemExit;
|
|
270
|
+
extern __gshared VALUE rb_eSysStackError; // Ruby 4 exports rb_eSysStackError (rb_eSystemStackError never existed)
|
|
271
|
+
extern __gshared VALUE rb_eZeroDivError;
|
|
272
|
+
extern __gshared VALUE rb_eLocalJumpError; // yield with no block / blk.call when none given
|
|
273
|
+
extern __gshared VALUE rb_eFrozenError; // rb_check_frozen on a frozen object
|
|
274
|
+
|
|
275
|
+
// type tags (ruby_value_type) — subset rudy needs
|
|
276
|
+
enum int RUBY_T_NONE = 0x00;
|
|
277
|
+
enum int RUBY_T_OBJECT = 0x01;
|
|
278
|
+
enum int RUBY_T_CLASS = 0x02;
|
|
279
|
+
enum int RUBY_T_MODULE = 0x03;
|
|
280
|
+
enum int RUBY_T_FLOAT = 0x0b;
|
|
281
|
+
enum int RUBY_T_STRING = 0x0d;
|
|
282
|
+
enum int RUBY_T_SYMBOL = 0x0f;
|
|
283
|
+
enum int RUBY_T_ARRAY = 0x11;
|
|
284
|
+
enum int RUBY_T_HASH = 0x13;
|
|
285
|
+
enum int RUBY_T_TRUE = 0x20;
|
|
286
|
+
enum int RUBY_T_FALSE = 0x21;
|
|
287
|
+
enum int RUBY_T_NIL = 0x22;
|
|
288
|
+
enum int RUBY_T_DATA = 0x26;
|
|
289
|
+
enum int RUBY_T_COMPLEX = 0x2a;
|
|
290
|
+
|
|
291
|
+
// C `long` on this platform (LP64 = 64 bits). Keeps to_ruby_value/d_type
|
|
292
|
+
// templates portable to 32-bit builds.
|
|
293
|
+
version (X86_64) {
|
|
294
|
+
alias long C_long;
|
|
295
|
+
alias ulong C_ulong;
|
|
296
|
+
} else version (X86) {
|
|
297
|
+
alias int C_long;
|
|
298
|
+
alias uint C_ulong;
|
|
299
|
+
} else {
|
|
300
|
+
alias long C_long;
|
|
301
|
+
alias ulong C_ulong;
|
|
302
|
+
}
|
|
303
|
+
alias long C_longlong;
|
|
304
|
+
alias ulong C_ulonglong;
|
|
305
|
+
|
|
306
|
+
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/// Two-way conversions between native D values and Ruby VALUEs.
|
|
2
|
+
/// D2 rewrite of the original RuDy make_object.d (2010), now calling only
|
|
3
|
+
/// exported Ruby functions — no struct peeking.
|
|
4
|
+
|
|
5
|
+
module rudy.convert;
|
|
6
|
+
|
|
7
|
+
import rudy.bind.value;
|
|
8
|
+
import std.traits;
|
|
9
|
+
import std.string : toStringz;
|
|
10
|
+
import std.exception : enforce;
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------- helpers
|
|
14
|
+
|
|
15
|
+
/// Opt-in raw-VALUE passthrough for def!'d function parameters.
|
|
16
|
+
///
|
|
17
|
+
/// VALUE identity(VALUE v) ... -> DECODED as ulong (VALUE is aliased
|
|
18
|
+
/// to ulong, and d_type now decodes it instead of passing the raw
|
|
19
|
+
/// tagged pointer through).
|
|
20
|
+
///
|
|
21
|
+
/// Raw!VALUE identity(Raw!VALUE v) ... -> raw, untouched VALUE.
|
|
22
|
+
///
|
|
23
|
+
/// Rationale: VALUE is `alias ulong VALUE`, so "parameter is VALUE" can't
|
|
24
|
+
/// be detected as a distinct type — and a plain `ulong` param used to hit
|
|
25
|
+
/// the old raw-passthrough branch, receiving the TAGGED value (Ruby 5
|
|
26
|
+
/// arrived as 11). Passthrough is now explicit: wrap in Raw!(...) to say
|
|
27
|
+
/// "give me the bits untouched".
|
|
28
|
+
struct Raw(T)
|
|
29
|
+
{
|
|
30
|
+
T value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Compile-time check: is T the Raw!(...) passthrough wrapper?
|
|
34
|
+
template isRaw(T)
|
|
35
|
+
{
|
|
36
|
+
static if (is(T == Raw!R, R))
|
|
37
|
+
enum isRaw = true;
|
|
38
|
+
else
|
|
39
|
+
enum isRaw = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Type traits the old rudy/ replaced its meta/ directory for.
|
|
43
|
+
template isDynamicArrayType(T) { enum bool isDynamicArrayType = isDynamicArray!T; }
|
|
44
|
+
template isStaticArrayType(T) { enum bool isStaticArrayType = isStaticArray!T; }
|
|
45
|
+
template isAssocArrayType(T) { enum bool isAssocArrayType = is(T == V[K], V, K); }
|
|
46
|
+
|
|
47
|
+
/// Byte length of a Ruby String, the exported-API way. History: the old
|
|
48
|
+
/// code sliced rb_str_strlen (a CHARACTER count) as a byte count —
|
|
49
|
+
/// truncating every multibyte string (finding #12) — and the natural fix
|
|
50
|
+
/// target rb_str_bytes_len turned out to be inline-macro-only, NOT
|
|
51
|
+
/// exported (caught locally by a symbol-lookup crash; the ABI audit would
|
|
52
|
+
/// have caught it in CI). Ruby's own String#bytesize is the exact answer;
|
|
53
|
+
/// rb_intern lazily, idempotently (VM is alive inside any method call).
|
|
54
|
+
private __gshared ID rudy_id_bytesize;
|
|
55
|
+
|
|
56
|
+
private long rudy_bytesize(VALUE str)
|
|
57
|
+
{
|
|
58
|
+
if (!rudy_id_bytesize)
|
|
59
|
+
rudy_id_bytesize = rb_intern("bytesize");
|
|
60
|
+
return cast(long) rb_num2ll(rb_funcallv(str, rudy_id_bytesize, 0, null));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Reject negative values where the D target type is unsigned: the
|
|
64
|
+
/// numeric family (rb_num2ulong / rb_num2uint) would otherwise wrap
|
|
65
|
+
/// them 2's-complement style (-1 arrived as 2^64-1, finding #13 family).
|
|
66
|
+
private void rudy_check_unsigned(VALUE val)
|
|
67
|
+
{
|
|
68
|
+
// Sign via double: rb_num2long would itself raise on values above
|
|
69
|
+
// LONG_MAX (2^64-1 is a legitimate ulong). Sign survives the cast;
|
|
70
|
+
// NaN/inf fail the comparison and get caught by num2ulong below.
|
|
71
|
+
if (rb_num2dbl(val) < 0)
|
|
72
|
+
rb_raise(rb_eRangeError, "negative value not convertible to unsigned D type");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Require a Ruby feature (e.g. "complex") before using it from D.
|
|
76
|
+
private void rb_require_c(string name)
|
|
77
|
+
{
|
|
78
|
+
VALUE req = rb_str_new(name.ptr, cast(C_long) name.length);
|
|
79
|
+
VALUE[1] av = [req];
|
|
80
|
+
rb_funcallv(rb_cObject, rb_intern("require"), 1, av.ptr);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------- to_ruby_value
|
|
84
|
+
|
|
85
|
+
/// Convert a native D value into a Ruby VALUE.
|
|
86
|
+
VALUE to_ruby_value(T)(T t)
|
|
87
|
+
{
|
|
88
|
+
static if (isRaw!T) // Raw!VALUE -> pass bits through
|
|
89
|
+
return t.value;
|
|
90
|
+
else static if (is(T == bool))
|
|
91
|
+
return t ? Qtrue_ : Qfalse_;
|
|
92
|
+
|
|
93
|
+
else static if (is(T == char))
|
|
94
|
+
return rb_int2inum(t);
|
|
95
|
+
|
|
96
|
+
else static if (is(T == ulong) || is(T == C_ulonglong))
|
|
97
|
+
return rb_ull2inum(cast(C_ulonglong) t); // unsigned encode path —
|
|
98
|
+
// the old signed
|
|
99
|
+
// rb_ll2inum(cast(long))
|
|
100
|
+
// turned 2^64-1 into -1
|
|
101
|
+
// (finding #15)
|
|
102
|
+
|
|
103
|
+
else static if (is(T : C_longlong))
|
|
104
|
+
return rb_ll2inum(cast(long) t);
|
|
105
|
+
|
|
106
|
+
else static if (is(T : C_long))
|
|
107
|
+
return rb_int2inum(cast(int) t);
|
|
108
|
+
|
|
109
|
+
else static if (is(T : double)) // float, real promote
|
|
110
|
+
return rb_float_new(cast(double) t);
|
|
111
|
+
|
|
112
|
+
else static if (is(T == creal) || is(T == cdouble))
|
|
113
|
+
{
|
|
114
|
+
rb_require_c("complex");
|
|
115
|
+
VALUE c = rb_const_get(rb_cObject, rb_intern("Complex"));
|
|
116
|
+
VALUE[2] av = [to_ruby_value(t.re), to_ruby_value(t.im)];
|
|
117
|
+
return rb_funcallv(c, rb_intern("new"), 2, av.ptr);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
else static if (is(T == string))
|
|
121
|
+
return rb_str_new(t.ptr, cast(C_long) t.length);
|
|
122
|
+
|
|
123
|
+
else static if (isDynamicArrayType!T)
|
|
124
|
+
{
|
|
125
|
+
VALUE ary = rb_ary_new_capa(cast(C_long) t.length);
|
|
126
|
+
foreach (ref e; t)
|
|
127
|
+
rb_ary_push(ary, to_ruby_value(e));
|
|
128
|
+
return ary;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
else static if (isStaticArrayType!T)
|
|
132
|
+
{
|
|
133
|
+
VALUE ary = rb_ary_new_capa(cast(C_long) t.length);
|
|
134
|
+
foreach (ref e; t)
|
|
135
|
+
rb_ary_push(ary, to_ruby_value(e));
|
|
136
|
+
return ary;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
else static if (isAssocArrayType!T)
|
|
140
|
+
{
|
|
141
|
+
VALUE hash = rb_hash_new();
|
|
142
|
+
foreach (k, ref v; t)
|
|
143
|
+
rb_hash_aset(hash, to_ruby_value(k), to_ruby_value(v));
|
|
144
|
+
return hash;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
else static assert(0, "to_ruby_value: no conversion for " ~ T.stringof);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
// ---------------------------------------------------------------- d_type
|
|
152
|
+
|
|
153
|
+
/// Convert a Ruby VALUE into a native D type T.
|
|
154
|
+
///
|
|
155
|
+
/// Numeric decode order matters: EXACT types must be tested before the
|
|
156
|
+
/// subtype branches they would otherwise fall into — is(T : C_longlong)
|
|
157
|
+
/// matches int too, and the old chain let `int` decode through the
|
|
158
|
+
/// unchecked-wide path. Each exact type now gets Ruby's own
|
|
159
|
+
/// range-checked converter: rb_num2int/rb_num2uint raise RangeError on
|
|
160
|
+
/// overflow (finding #13: silent wraparound before), fractional floats
|
|
161
|
+
/// truncate toward zero and NaN/inf raise FloatDomainError — exactly
|
|
162
|
+
/// NUM2INT/NUM2UINT/Integer() semantics (finding #14).
|
|
163
|
+
T d_type(T)(VALUE val)
|
|
164
|
+
{
|
|
165
|
+
static if (is(T == bool))
|
|
166
|
+
{
|
|
167
|
+
// RUBY_TEST (RTEST) semantics: nil/false are false, everything
|
|
168
|
+
// else — 0, 0.0, "" — is true, matching Ruby's own truthiness.
|
|
169
|
+
// (The old `val == Qtrue_` made Ruby truthy 0 decode as D false.)
|
|
170
|
+
return val != Qnil_ && val != Qfalse_;
|
|
171
|
+
}
|
|
172
|
+
else static if (is(T == string))
|
|
173
|
+
{
|
|
174
|
+
VALUE str = rb_str_to_str(val);
|
|
175
|
+
auto ptr = rb_string_value_ptr(&str); // may swap str in-place
|
|
176
|
+
return ptr[0 .. rudy_bytesize(str)].idup; // BYTE length, not chars
|
|
177
|
+
// (finding #12)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// exact unsigned machine words first (VALUE itself is a ulong alias)
|
|
181
|
+
else static if (is(T == ulong) || is(T == C_ulonglong))
|
|
182
|
+
{
|
|
183
|
+
rudy_check_unsigned(val); // -1 must raise, not wrap
|
|
184
|
+
return cast(T) rb_num2ulong(val);
|
|
185
|
+
} // a ulong param still means
|
|
186
|
+
// "Ruby Integer -> machine word",
|
|
187
|
+
// NOT raw passthrough
|
|
188
|
+
else static if (is(T == int))
|
|
189
|
+
return cast(T) rb_num2int(val); // range-checked (finding #13)
|
|
190
|
+
else static if (is(T == uint))
|
|
191
|
+
{
|
|
192
|
+
rudy_check_unsigned(val);
|
|
193
|
+
return cast(T) rb_num2uint(val); // range-checked (finding #13)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// remaining signed subtypes (long, char, short...): wide is fine
|
|
197
|
+
else static if (is(T : C_longlong))
|
|
198
|
+
return cast(T) rb_num2ll(val);
|
|
199
|
+
|
|
200
|
+
// remaining unsigned subtypes (ubyte, ushort...): range-checked
|
|
201
|
+
else static if (is(T : uint))
|
|
202
|
+
{
|
|
203
|
+
rudy_check_unsigned(val);
|
|
204
|
+
return cast(T) rb_num2uint(val);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
else static if (is(T : double))
|
|
208
|
+
return cast(T) rb_num2dbl(val);
|
|
209
|
+
|
|
210
|
+
else static assert(0, "d_type: no conversion to " ~ T.stringof);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/// NB: D's uninitialized `double x;` is NaN (float.init), NOT 0.0 as in C.
|
|
214
|
+
/// Always explicitly initialize double accumulators: `double x = 0.0;` —
|
|
215
|
+
/// otherwise every sum silently becomes NaN (issue #3).
|
|
216
|
+
|
|
217
|
+
/// Raise a Ruby exception from D.
|
|
218
|
+
void rudy_raise(VALUE exc, string msg)
|
|
219
|
+
{
|
|
220
|
+
rb_raise(exc, "%s", toStringz(msg));
|
|
221
|
+
}
|