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
data/vendor/rudy/defs.d
ADDED
|
@@ -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
|
+
}
|