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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +114 -0
  3. data/LICENSE +21 -0
  4. data/Makefile +137 -0
  5. data/README.md +400 -0
  6. data/bin/rudy4 +6 -0
  7. data/examples/hello_gem/README.md +91 -0
  8. data/examples/hello_gem/ext/hello_gem/extconf.rb +75 -0
  9. data/examples/hello_gem/ext/hello_gem/hello_gem.d +122 -0
  10. data/examples/hello_gem/hello_gem.gemspec +35 -0
  11. data/examples/hello_gem/lib/hello_gem/version.rb +5 -0
  12. data/examples/hello_gem/lib/hello_gem.rb +32 -0
  13. data/examples/hello_gem/test/smoke.sh +81 -0
  14. data/experiments/exp_compact.d +178 -0
  15. data/experiments/exp_convert.d +93 -0
  16. data/experiments/exp_cross.d +144 -0
  17. data/experiments/exp_defargs.d +301 -0
  18. data/experiments/exp_exc.d +265 -0
  19. data/experiments/exp_gcstorm.d +169 -0
  20. data/experiments/exp_http.d +254 -0
  21. data/experiments/exp_nonregression.d +209 -0
  22. data/experiments/exp_objproto.d +123 -0
  23. data/experiments/exp_perf.d +81 -0
  24. data/experiments/exp_wrap.d +186 -0
  25. data/lib/rudy4/cli.rb +204 -0
  26. data/lib/rudy4/doctor.rb +295 -0
  27. data/lib/rudy4/error.rb +10 -0
  28. data/lib/rudy4/generator.rb +172 -0
  29. data/lib/rudy4/templates.rb +484 -0
  30. data/lib/rudy4/vendor.rb +36 -0
  31. data/lib/rudy4/version.rb +5 -0
  32. data/lib/rudy4.rb +16 -0
  33. data/scripts/abi_audit.sh +64 -0
  34. data/scripts/memcheck.sh +77 -0
  35. data/scripts/vendor_check.sh +33 -0
  36. data/scripts/vendor_sync.sh +25 -0
  37. data/src/dexitest.d +278 -0
  38. data/src/dextest.d +119 -0
  39. data/src/dtypetest.d +159 -0
  40. data/src/rudy/args.d +253 -0
  41. data/src/rudy/bind/value.d +306 -0
  42. data/src/rudy/convert.d +221 -0
  43. data/src/rudy/defs.d +440 -0
  44. data/src/rudy/objproto.d +282 -0
  45. data/src/rudy/typeddata.d +143 -0
  46. data/test/dextest_loader.rb +6 -0
  47. data/test/exitest.rb +113 -0
  48. data/test/rudy4_test.rb +291 -0
  49. data/test/run.rb +74 -0
  50. data/test/typedtest.rb +69 -0
  51. data/vendor/rudy/args.d +253 -0
  52. data/vendor/rudy/bind/value.d +306 -0
  53. data/vendor/rudy/convert.d +221 -0
  54. data/vendor/rudy/defs.d +440 -0
  55. data/vendor/rudy/objproto.d +282 -0
  56. data/vendor/rudy/typeddata.d +143 -0
  57. metadata +106 -0
@@ -0,0 +1,440 @@
1
+ /// `def!` — register a D function as a Ruby method, with automatic
2
+ /// argument unwrapping (VALUE -> native D) and result rewrapping
3
+ /// (native D -> VALUE). The 2010 original passed raw VALUEs through;
4
+ /// this one finally does the wrapping PyD-style.
5
+ ///
6
+ /// def!(myFunc); // global (Kernel) function
7
+ /// def!("MyClass", myFunc); // instance method of MyClass
8
+ /// def!("MyModule", myFunc); // module function
9
+ ///
10
+ /// The D function may take native D types (int, double, string, ...) as
11
+ /// parameters; each argument is converted from the incoming VALUE with
12
+ /// d_type!T, and the return value goes back through to_ruby_value.
13
+ ///
14
+ /// ---
15
+ ///
16
+ /// Special parameter types (rudy/args.d) extend the model beyond fixed
17
+ /// positional arguments:
18
+ ///
19
+ /// RubyBlock (last param) — receives the caller's block as a Proc;
20
+ /// blk.call(...) invokes it; blk.given says
21
+ /// whether a block was passed at all.
22
+ /// Rest — captures *rest into a Ruby Array.
23
+ /// KwArgs — captures **kwargs into a Ruby Hash
24
+ /// (given()==false when none passed).
25
+ /// Self (first param) — the receiver as raw VALUE.
26
+ /// Raw!T — opt-in raw VALUE passthrough.
27
+ ///
28
+ /// Any def!'d function using a special parameter (or D default args) is
29
+ /// registered with arity -1 (argc/argv style); rb_scan_args splits the
30
+ /// arguments per a compile-time-generated format string and raises the
31
+ /// standard ArgumentError on wrong arity. Plain fixed-arity functions
32
+ /// go through the same uniform trampoline (also registered -1): one
33
+ /// code path for every def!'d method.
34
+ module rudy.defs;
35
+
36
+ import rudy.bind.value;
37
+ import rudy.convert;
38
+ import rudy.args;
39
+ import std.traits;
40
+ import std.string : toStringz;
41
+ import std.conv : to;
42
+ import std.meta : AliasSeq, anySatisfy;
43
+ import std.algorithm.iteration : splitter;
44
+
45
+ private:
46
+
47
+ // ------------------------------------------------------------------
48
+ // Parameter classification
49
+ // ------------------------------------------------------------------
50
+
51
+ /// True for parameter types the trampoline fills in ITSELF (never
52
+ /// decoded from a positional VALUE): the rudy/args.d wrappers, the
53
+ /// Self receiver, and the opt-in Raw!T passthrough.
54
+ template isValueParam(T)
55
+ {
56
+ static if (is(T == RubyBlock) || is(T == Rest) || is(T == KwArgs)
57
+ || is(T == Self) || isRaw!T)
58
+ enum bool isValueParam = true;
59
+ else
60
+ enum bool isValueParam = false;
61
+ }
62
+
63
+ /// Decode one positional argument VALUE into parameter type T.
64
+ /// Raw!T passthrough wraps the bits; everything else goes through
65
+ /// d_type!T. (There is deliberately no plain-VALUE branch: VALUE is
66
+ /// aliased to ulong, and a ulong parameter means "Ruby Integer ->
67
+ /// machine word", NOT raw passthrough — see issue #1.)
68
+ T argFrom(T)(VALUE v)
69
+ {
70
+ static if (isRaw!T)
71
+ return T(v);
72
+ else
73
+ {
74
+ static assert(!isValueParam!T, "def!: " ~ T.stringof
75
+ ~ " is filled by the trampoline, not decoded from argv");
76
+ return d_type!T(v);
77
+ }
78
+ }
79
+
80
+ private template isRestParam(T) { enum bool isRestParam = is(T == Rest); }
81
+ private template isKwParam(T) { enum bool isKwParam = is(T == KwArgs); }
82
+ private template isBlockParam(T) { enum bool isBlockParam = is(T == RubyBlock); }
83
+ private template isSelfParam(T) { enum bool isSelfParam = is(T == Self); }
84
+
85
+ /// Index of the first parameter satisfying pred, or -1.
86
+ template findIdx(alias pred, P...)
87
+ {
88
+ static if (P.length == 0)
89
+ enum int findIdx = -1;
90
+ else static if (pred!(P[0]))
91
+ enum int findIdx = 0;
92
+ else
93
+ enum int findIdx = findIdx!(pred, P[1 .. $]) == -1
94
+ ? -1 : 1 + findIdx!(pred, P[1 .. $]);
95
+ }
96
+
97
+ // ------------------------------------------------------------------
98
+ // ArgInfo: compile-time layout of a def!'d function's parameters.
99
+ //
100
+ // nLead / nOpt / nTrail / hasAsterisk — pieces of the rb_scan_args
101
+ // format string. At most one of { Rest, KwArgs, RubyBlock } per
102
+ // function (static assert'ed).
103
+ // posSlots — decodable positional slots in scan order (mandatory
104
+ // first, then optional): each element is the parameter index it
105
+ // maps to.
106
+ // restIdx / kwIdx / blkIdx / selfIdx — parameter indices, -1 when
107
+ // absent; filled by the trampoline (self from the self argument).
108
+ // ------------------------------------------------------------------
109
+
110
+ struct ArgInfo(alias fn)
111
+ {
112
+ alias P = Parameters!(typeof(&fn));
113
+ alias DEFAULTS = ParameterDefaults!(fn);
114
+
115
+ enum bool hasRest = anySatisfy!(isRestParam, P);
116
+ enum bool hasKw = anySatisfy!(isKwParam, P);
117
+ enum bool hasBlock = anySatisfy!(isBlockParam, P);
118
+ enum bool hasSelf = anySatisfy!(isSelfParam, P);
119
+
120
+ enum int restIdx = findIdx!(isRestParam, P);
121
+ enum int kwIdx = findIdx!(isKwParam, P);
122
+ enum int blkIdx = findIdx!(isBlockParam, P);
123
+ enum int selfIdx = findIdx!(isSelfParam, P);
124
+
125
+ // positional classification of one parameter: is it a decodable
126
+ // argv slot, and must the caller supply it? Raw!T params are
127
+ // ordinary positional slots (raw passthrough instead of d_type).
128
+ template slotKind(size_t i)
129
+ {
130
+ static if (is(P[i] == RubyBlock) || is(P[i] == Rest)
131
+ || is(P[i] == KwArgs) || is(P[i] == Self))
132
+ enum string slotKind = "none"; // filled by trampoline
133
+ else static if (!is(DEFAULTS[i] == void))
134
+ enum string slotKind = "opt"; // optional positional
135
+ else
136
+ enum string slotKind = "req"; // mandatory positional
137
+ }
138
+
139
+ enum nLead = countLead;
140
+ enum nOpt = countOpt;
141
+ enum nTrail = 0; // trailing mandatory after splat:
142
+ // not supported yet
143
+ enum bool hasAsterisk = hasRest;
144
+
145
+ // positional slots in scan order: mandatory first, then optional
146
+ template posSlotsOf(size_t i = 0)
147
+ {
148
+ static if (i >= P.length)
149
+ alias posSlotsOf = AliasSeq!();
150
+ else static if (slotKind!i == "none")
151
+ alias posSlotsOf = posSlotsOf!(i + 1);
152
+ else static if (slotKind!i == "req")
153
+ alias posSlotsOf = AliasSeq!(i, posSlotsOf!(i + 1));
154
+ else
155
+ alias posSlotsOf = AliasSeq!(i, posSlotsOf!(i + 1));
156
+ }
157
+ alias posSlots = posSlotsOf!();
158
+
159
+ // mandatory = leading run of "req" slots in scan order
160
+ private template countLeadImpl(size_t i, int acc)
161
+ {
162
+ static if (i >= P.length || slotKind!i != "req")
163
+ enum int countLeadImpl = acc;
164
+ else
165
+ enum int countLeadImpl = countLeadImpl!(i + 1, acc + 1);
166
+ }
167
+ enum int countLead = countLeadImpl!(0, 0);
168
+
169
+ enum int countOpt = posSlots.length - countLead;
170
+
171
+ // DEBUG
172
+
173
+ static assert(!(hasRest && hasKw), "def!: Rest + KwArgs in one function not supported yet");
174
+ static assert(blkIdx == -1 || blkIdx == P.length - 1,
175
+ "def!: RubyBlock must be the LAST parameter");
176
+ static assert(countLead + countOpt + (hasRest ? 1 : 0)
177
+ + (hasKw || hasBlock ? 1 : 0) + (hasSelf ? 1 : 0)
178
+ == P.length,
179
+ "def!: unsupported parameter layout — special params (Rest/KwArgs/RubyBlock/Self) must not carry D default values");
180
+ }
181
+
182
+ // ------------------------------------------------------------------
183
+ // rb_scan_args format string, generated at compile time:
184
+ // digits: nLead, then nOpt, then '*' when hasRest, then nTrail, then
185
+ // ':' when hasKw, then '&' when hasBlock.
186
+ // Examples: "0" (no args), "1", "12&" (1 req + up to 2 opt + block),
187
+ // "1*", "2*:", "0&", "1*&" (1 req + *rest + block).
188
+ // ------------------------------------------------------------------
189
+
190
+ private string scanFmt(alias AI)()
191
+ {
192
+ string s;
193
+ static if (AI.nLead > 0)
194
+ s ~= AI.nLead.to!string;
195
+ static if (AI.nOpt > 0)
196
+ s ~= AI.nOpt.to!string;
197
+ static if (AI.hasAsterisk)
198
+ s ~= "*";
199
+ static if (AI.nTrail > 0)
200
+ s ~= AI.nTrail.to!string;
201
+ static if (AI.hasKw)
202
+ s ~= ":";
203
+ static if (AI.hasBlock)
204
+ s ~= "&";
205
+ if (s.length == 0)
206
+ s = "0"; // zero-arg functions still get the
207
+ // argc == 0 arity check
208
+ return s;
209
+ }
210
+
211
+ // ------------------------------------------------------------------
212
+ // The one uniform trampoline. Registered with arity -1 for every
213
+ // def!'d function; Ruby calls it as
214
+ // VALUE f(int argc, const(VALUE)* argv, VALUE self)
215
+ // (the classic variadic C-method ABI). rb_scan_args splits argv per
216
+ // the generated format (raising the standard ArgumentError on wrong
217
+ // arity), omitted optional slots become Qnil, and wrappers
218
+ // (Rest/KwArgs/RubyBlock) + Self/Raw slots are filled directly.
219
+ // ------------------------------------------------------------------
220
+
221
+ extern(C)
222
+ VALUE rudy_trampoline_var(alias fn)(int argc, const(VALUE)* argv, VALUE self)
223
+ {
224
+ alias AI = ArgInfo!(fn);
225
+ alias P = Parameters!(typeof(&fn));
226
+ alias DEFAULTS = ParameterDefaults!(fn);
227
+ alias SLOTS = AI.posSlots;
228
+
229
+ P args; // default-initialized
230
+
231
+ // rb_scan_args targets (positional slots in scan order); optional
232
+ // slots omitted by the caller are filled with Qnil by Ruby itself.
233
+ static if (SLOTS.length > 0)
234
+ VALUE[SLOTS.length] slotVals = Qnil_;
235
+
236
+ // NB: rb_scan_args' vararg order is FORMAT ORDER — '*' slot first,
237
+ // then ':' tail, then '&' tail. Rest+block is therefore two targets:
238
+ // restAry AND tail. KwArgs + RubyBlock can't coexist (: then & would
239
+ // be fine, but a function taking both kwargs and a block with the
240
+ // same Hash/Proc split is untestable through get!T semantics — see
241
+ // the static assert below).
242
+ static if (AI.hasRest)
243
+ {
244
+ VALUE restAry = Qnil_;
245
+ static if (AI.hasBlock)
246
+ {
247
+ VALUE tail = Qnil_; // '&' after '*'
248
+ static if (SLOTS.length == 0)
249
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)), &restAry, &tail);
250
+ else static if (SLOTS.length == 1)
251
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)),
252
+ &slotVals[0], &restAry, &tail);
253
+ else static if (SLOTS.length == 2)
254
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)),
255
+ &slotVals[0], &slotVals[1], &restAry, &tail);
256
+ else static if (SLOTS.length == 3)
257
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)),
258
+ &slotVals[0], &slotVals[1], &slotVals[2], &restAry, &tail);
259
+ else
260
+ static assert(0, "def!: max 3 positional slots + special params");
261
+ }
262
+ else static if (SLOTS.length == 0)
263
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)), &restAry);
264
+ else static if (SLOTS.length == 1)
265
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)),
266
+ &slotVals[0], &restAry);
267
+ else static if (SLOTS.length == 2)
268
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)),
269
+ &slotVals[0], &slotVals[1], &restAry);
270
+ else static if (SLOTS.length == 3)
271
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)),
272
+ &slotVals[0], &slotVals[1], &slotVals[2], &restAry);
273
+ else
274
+ static assert(0, "def!: max 3 positional slots + special params");
275
+ }
276
+ else static if (AI.hasKw || AI.hasBlock)
277
+ {
278
+ VALUE tail = Qnil_; // kwargs hash / block proc
279
+ static if (SLOTS.length == 0)
280
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)), &tail);
281
+ else static if (SLOTS.length == 1)
282
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)),
283
+ &slotVals[0], &tail);
284
+ else static if (SLOTS.length == 2)
285
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)),
286
+ &slotVals[0], &slotVals[1], &tail);
287
+ else static if (SLOTS.length == 3)
288
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)),
289
+ &slotVals[0], &slotVals[1], &slotVals[2], &tail);
290
+ else
291
+ static assert(0, "def!: max 3 positional slots + special params");
292
+ }
293
+ else static if (SLOTS.length > 0)
294
+ {
295
+ static if (SLOTS.length == 1)
296
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)), &slotVals[0]);
297
+ else static if (SLOTS.length == 2)
298
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)),
299
+ &slotVals[0], &slotVals[1]);
300
+ else static if (SLOTS.length == 3)
301
+ rb_scan_args(argc, argv, toStringz(scanFmt!(AI)),
302
+ &slotVals[0], &slotVals[1], &slotVals[2]);
303
+ else
304
+ static assert(0, "def!: max 3 positional slots (extend the ladder for more)");
305
+ }
306
+
307
+ // fill wrapper params from the scan results
308
+ static if (AI.hasRest)
309
+ args[AI.restIdx] = Rest(restAry);
310
+ static if (AI.hasKw)
311
+ args[AI.kwIdx] = KwArgs(tail);
312
+ static if (AI.hasBlock)
313
+ args[AI.blkIdx] = RubyBlock(tail);
314
+ static if (AI.hasSelf)
315
+ args[AI.selfIdx] = Self(self);
316
+
317
+ // decode positional slots; D default values substitute for omitted
318
+ // optional arguments (Qnil means "not supplied"). Two-var static
319
+ // foreach: si = slot position (argv order), i = parameter index.
320
+ // NB: an AliasSeq passed as an alias template arg EXPANDS into
321
+ // separate args (a slotPos!(SLOTS, i) helper died exactly that
322
+ // way) - iterating it in foreach is the safe pattern.
323
+ static foreach (si, i; SLOTS)
324
+ {{
325
+ static if (is(DEFAULTS[i] == void))
326
+ args[i] = argFrom!(P[i])(slotVals[si]);
327
+ else
328
+ {
329
+ if (slotVals[si] == Qnil_)
330
+ {
331
+ static if (is(P[i] == VALUE))
332
+ args[i] = cast(VALUE) DEFAULTS[i];
333
+ else static if (isRaw!(P[i]))
334
+ args[i] = P[i](cast(VALUE) DEFAULTS[i]);
335
+ else
336
+ args[i] = DEFAULTS[i];
337
+ }
338
+ else
339
+ args[i] = argFrom!(P[i])(slotVals[si]);
340
+ }
341
+ }}
342
+
343
+ return to_ruby_value(fn(args));
344
+ }
345
+
346
+ // ------------------------------------------------------------------
347
+ // Trampoline selection. Every def!'d function — fixed arity, defaults,
348
+ // or wrapper params — uses the uniform argc/argv trampoline, registered
349
+ // with arity -1. (The old per-arity extern(C) shims rudy_trampoline_0..
350
+ // _3 are gone: one code path, and blocks/rest/kwargs would not fit the
351
+ // fixed-arity ABI anyway.)
352
+ // ------------------------------------------------------------------
353
+
354
+ template rudy_trampoline(alias fn)
355
+ {
356
+ alias rudy_trampoline = rudy_trampoline_var!(fn);
357
+ }
358
+
359
+ /// Registered arity for Ruby: always -1 (argc/argv style).
360
+ enum int rudy_arity(alias fn) = -1;
361
+
362
+ // ------------------------------------------------------------------
363
+ // def! — the public interface
364
+ // ------------------------------------------------------------------
365
+
366
+ public:
367
+
368
+ /// Define fn as a global function (on Object).
369
+ void def(alias fn, string name = __traits(identifier, fn))()
370
+ {
371
+ // NB: alias template params must be forwarded in the bang-list;
372
+ // def!"Object"(fn, name) misparses under GDC-14 as a runtime call.
373
+ def!("Object", fn, name)();
374
+ }
375
+
376
+ /// Define fn in the scope of `modulename` (class -> method, module ->
377
+ /// module function, anything else -> global on Object).
378
+ void def(string modulename, alias fn, string name = __traits(identifier, fn))()
379
+ {
380
+ VALUE parent = rb_const_get(rb_cObject, rb_intern(toStringz(modulename)));
381
+ alias tramp = rudy_trampoline!(fn);
382
+
383
+ // T_MODULE == 0x03 per value.d; rb_type is macro-only, classify
384
+ // via is_a?(Module)
385
+ VALUE[1] av = [rb_const_get(rb_cObject, rb_intern("Module"))];
386
+ VALUE res = rb_funcallv_public(parent, rb_intern("is_a?"), 1, av.ptr);
387
+
388
+ if (res == Qtrue_)
389
+ rb_define_module_function(parent, toStringz(name),
390
+ cast(AnyMethod) &tramp, rudy_arity!(fn));
391
+ else
392
+ rb_define_method(parent, toStringz(name),
393
+ cast(AnyMethod) &tramp, rudy_arity!(fn));
394
+ }
395
+
396
+ /// Define fn as a PRIVATE instance method (e.g. DSL-style callbacks).
397
+ void def_private(string modulename, alias fn,
398
+ string name = __traits(identifier, fn))()
399
+ {
400
+ VALUE parent = rb_const_get(rb_cObject, rb_intern(toStringz(modulename)));
401
+ alias tramp = rudy_trampoline!(fn);
402
+ rb_define_private_method(parent, toStringz(name),
403
+ cast(AnyMethod) &tramp, rudy_arity!(fn));
404
+ }
405
+
406
+ /// Define fn as a PROTECTED instance method.
407
+ void def_protected(string modulename, alias fn,
408
+ string name = __traits(identifier, fn))()
409
+ {
410
+ VALUE parent = rb_const_get(rb_cObject, rb_intern(toStringz(modulename)));
411
+ alias tramp = rudy_trampoline!(fn);
412
+ rb_define_protected_method(parent, toStringz(name),
413
+ cast(AnyMethod) &tramp, rudy_arity!(fn));
414
+ }
415
+
416
+ /// Define fn as a SINGLETON method of `modulename`'s class object
417
+ /// (i.e. a class-level method: MyClass.def!'d_thing).
418
+ void def_singleton(string modulename, alias fn,
419
+ string name = __traits(identifier, fn))()
420
+ {
421
+ VALUE klass = rb_const_get(rb_cObject, rb_intern(toStringz(modulename)));
422
+ alias tramp = rudy_trampoline!(fn);
423
+ rb_define_singleton_method(klass, toStringz(name),
424
+ cast(AnyMethod) &tramp, rudy_arity!(fn));
425
+ }
426
+
427
+ /// Define fn as a singleton method of a specific OBJECT (per-object
428
+ /// method; the object must be reachable by name at def! time).
429
+ void def_singleton_on(string objectname, alias fn,
430
+ string name = __traits(identifier, fn))()
431
+ {
432
+ // "A::B" paths walk the scope chain (rb_const_get resolves one
433
+ // constant per call, not a path)
434
+ VALUE obj = rb_cObject;
435
+ foreach (part; objectname.splitter("::"))
436
+ obj = rb_const_get(obj, rb_intern(toStringz(part)));
437
+ alias tramp = rudy_trampoline!(fn);
438
+ rb_define_singleton_method(obj, toStringz(name),
439
+ cast(AnyMethod) &tramp, rudy_arity!(fn));
440
+ }