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,93 @@
1
+ /// exp_convert — torture the D↔Ruby conversion layer (rudy/convert.d).
2
+ ///
3
+ /// Round-trip property: for every supported type, D→Ruby→D must be
4
+ /// lossless even under adversarial values: long.min, NaN/±0.0/inf,
5
+ /// empty/NUL/binary strings (EUC-JP, non-UTF-8 bytes), frozen strings,
6
+ /// truthiness edge cases, and GC pressure mid-conversion.
7
+ /// Each extern(C) function is registered by test_exp_convert.rb and
8
+ /// driven from Ruby with hostile inputs.
9
+
10
+ module exp_convert;
11
+
12
+ import rudy.bind.value;
13
+ import rudy.convert;
14
+ import core.runtime : rt_init;
15
+ import core.memory : GC;
16
+
17
+ private:
18
+
19
+ // ---------------------------------------------------------------- round-trip identities
20
+ // Registered directly via rb_define_method: Ruby->D via d_type!T,
21
+ // return D->Ruby via to_ruby_value. Exactly the path def! uses.
22
+
23
+ extern (C) VALUE conv_long(VALUE self, VALUE v) { return to_ruby_value(d_type!long(v)); }
24
+ extern (C) VALUE conv_ulong(VALUE self, VALUE v) { return to_ruby_value(d_type!ulong(v)); }
25
+ extern (C) VALUE conv_int(VALUE self, VALUE v) { return to_ruby_value(d_type!int(v)); }
26
+ extern (C) VALUE conv_uint(VALUE self, VALUE v) { return to_ruby_value(d_type!uint(v)); }
27
+ extern (C) VALUE conv_double(VALUE self, VALUE v) { return to_ruby_value(d_type!double(v)); }
28
+ extern (C) VALUE conv_string(VALUE self, VALUE v) { return to_ruby_value(d_type!string(v)); }
29
+ extern (C) VALUE conv_bool(VALUE self, VALUE v) { return to_ruby_value(d_type!bool(v)); }
30
+
31
+ // ---------------------------------------------------------------- strings: byte-length probe
32
+ // d_type!string must decode ANY String — empty, NUL-laden, binary,
33
+ // non-UTF-8 — without throwing D-side, and preserve the byte count
34
+ // exactly (finding #12: rb_str_strlen chars-vs-bytes truncation).
35
+ // Non-String inputs raise Ruby-side (rb_str_to_str TypeError).
36
+
37
+ extern (C) VALUE conv_string_bytesize(VALUE self, VALUE v)
38
+ {
39
+ auto s = d_type!string(v); // binary-safe: copies raw bytes
40
+ return to_ruby_value(s.length); // byte length of the D copy
41
+ }
42
+
43
+ // Same probe with a GC.collect() parked between decode and re-encode:
44
+ // the D-side copy must not be the only thing keeping Ruby-side state
45
+ // sane, and the Ruby input String must not be resurrectable-but-stale.
46
+ extern (C) VALUE conv_string_stress(VALUE self, VALUE v)
47
+ {
48
+ auto s = d_type!string(v);
49
+ GC.collect();
50
+ return to_ruby_value(s);
51
+ }
52
+
53
+ // ---------------------------------------------------------------- symbols: reject, not corrupt
54
+ // There is no symbol branch in d_type!string — it is a compile-time
55
+ // static assert in the library. What must hold at runtime: passing a
56
+ // Symbol raises Ruby-side (rb_str_to_str: TypeError), and Ruby's own
57
+ // symbol<->string traffic through D stays byte-perfect.
58
+
59
+ extern (C) VALUE conv_sym_to_s(VALUE self, VALUE sym)
60
+ {
61
+ // rb_sym2str is exported but not bound; ask Ruby instead
62
+ return rb_funcallv_public(sym, rb_intern("to_s"), 0, null);
63
+ }
64
+
65
+ extern (C) VALUE conv_sym_roundtrip(VALUE self, VALUE str)
66
+ {
67
+ // String -> Symbol -> String, all via rb_funcallv_public
68
+ VALUE sym = rb_funcallv_public(str, rb_intern("to_sym"), 0, null);
69
+ return rb_funcallv_public(sym, rb_intern("to_s"), 0, null);
70
+ }
71
+
72
+ // ---------------------------------------------------------------- Init
73
+
74
+ public:
75
+
76
+ pragma(mangle, "Init_exp_convert")
77
+ extern (C) void Init_exp_convert()
78
+ {
79
+ rt_init(); // dlopen'd .so: D runtime init is our job (finding #9)
80
+
81
+ VALUE m = rb_define_module("ConvertProbe");
82
+ rb_define_module_function(m, "long", cast(AnyMethod) &conv_long, 1);
83
+ rb_define_module_function(m, "ulong", cast(AnyMethod) &conv_ulong, 1);
84
+ rb_define_module_function(m, "int", cast(AnyMethod) &conv_int, 1);
85
+ rb_define_module_function(m, "uint", cast(AnyMethod) &conv_uint, 1);
86
+ rb_define_module_function(m, "double", cast(AnyMethod) &conv_double, 1);
87
+ rb_define_module_function(m, "string", cast(AnyMethod) &conv_string, 1);
88
+ rb_define_module_function(m, "bool", cast(AnyMethod) &conv_bool, 1);
89
+ rb_define_module_function(m, "string_bytesize", cast(AnyMethod) &conv_string_bytesize, 1);
90
+ rb_define_module_function(m, "string_stress", cast(AnyMethod) &conv_string_stress, 1);
91
+ rb_define_module_function(m, "sym_to_s", cast(AnyMethod) &conv_sym_to_s, 1);
92
+ rb_define_module_function(m, "sym_roundtrip", cast(AnyMethod) &conv_sym_roundtrip, 1);
93
+ }
@@ -0,0 +1,144 @@
1
+ /// EXPERIMENT 2 — Two-way boundary crossing in a single call.
2
+ ///
3
+ /// Torture elements:
4
+ /// - D calls *back into Ruby* (rb_funcallv) mid-computation (sort block,
5
+ /// custom to_s)
6
+ /// - rb_protect around Ruby callbacks that raise
7
+ /// - nested data structures built in D, consumed in Ruby
8
+ /// - variadic-shape method (argc/argv) via a variadic D function passed
9
+ /// to rb_define_method with arity -1 — uses raw C varargs shim
10
+ ///
11
+ /// We have to hand-roll the varargs trampoline (D variadic != C varargs),
12
+ /// so this also tests how far we can get without library support for
13
+ /// variable arity.
14
+
15
+ module exp_cross;
16
+
17
+ import rudy.bind.value;
18
+ import rudy.convert;
19
+ import rudy.defs;
20
+ import std.string : toStringz;
21
+
22
+ private:
23
+
24
+ // ---- D calls a Ruby block for comparison ------------------------------
25
+ // (block invocation happens via rb_funcallv on a registered Proc in
26
+ // cross_sort_protected; kept for reference)
27
+
28
+ alias VALUE function(VALUE, VALUE) BlockFn;
29
+
30
+ extern (C) VALUE cross_sort_protected(VALUE data)
31
+ {
32
+ // data is the ExpCross module; @ary and @blk are set from Ruby
33
+ VALUE ary = rb_iv_get(data, "@ary");
34
+ VALUE blk = rb_iv_get(data, "@blk");
35
+ // sort takes a BLOCK, not a positional arg: rb_funcallv(sort, 1, blk)
36
+ // raises ArgumentError. rb_funcall_with_block passes blk as the block.
37
+ return rb_funcall_with_block(ary, rb_intern("sort"), 0, null, blk);
38
+ }
39
+
40
+ extern (C) VALUE cross_sort(VALUE self)
41
+ {
42
+ VALUE mod = rb_const_get(rb_cObject, rb_intern("ExpCross"));
43
+ int state;
44
+ VALUE res = rb_protect(&cross_sort_protected, mod, &state);
45
+ if (state)
46
+ {
47
+ rudy_raise(rb_eRuntimeError, "Ruby block raised inside D sort");
48
+ return Qnil_;
49
+ }
50
+ return res;
51
+ }
52
+
53
+ // register_block(holder) — remember block holder for later cross_sort
54
+ extern (C) VALUE cross_register(VALUE self, VALUE blk)
55
+ {
56
+ VALUE mod = rb_const_get(rb_cObject, rb_intern("ExpCross"));
57
+ rb_iv_set(mod, "@blk", blk);
58
+ return blk;
59
+ }
60
+
61
+ // register_ary(ary) — the array cross_sort will sort
62
+ extern (C) VALUE cross_register_ary(VALUE self, VALUE ary)
63
+ {
64
+ VALUE mod = rb_const_get(rb_cObject, rb_intern("ExpCross"));
65
+ rb_iv_set(mod, "@ary", ary);
66
+ return ary;
67
+ }
68
+
69
+ // ---- nested structure built in D, deep-equal from Ruby -----------------
70
+
71
+ struct Point { double x, y; }
72
+
73
+ Point[] polygon = [ Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1) ];
74
+
75
+ // Sum via a Ruby-supplied callable (Proc): fold(map)
76
+ extern (C) VALUE cross_polygon_area(VALUE self)
77
+ {
78
+ // call Ruby: points.inject(0) { |acc, (x, y)| acc + x*y } — do it via
79
+ // eval'd literal from D to prove full eval round-trip works
80
+ VALUE mod = rb_const_get(rb_cObject, rb_intern("ExpCross"));
81
+ VALUE pts = rb_iv_get(mod, "@pts");
82
+ if (pts == Qnil_)
83
+ {
84
+ // build [[x, y], ...] in D
85
+ pts = rb_ary_new_capa(cast(C_long) polygon.length);
86
+ foreach (ref p; polygon)
87
+ {
88
+ VALUE pair = rb_ary_new_capa(2);
89
+ rb_ary_push(pair, to_ruby_value(p.x));
90
+ rb_ary_push(pair, to_ruby_value(p.y));
91
+ rb_ary_push(pts, pair);
92
+ }
93
+ rb_iv_set(mod, "@pts", pts);
94
+ }
95
+ // eval inside module context so the bare `pts` local resolves — use a
96
+ // funcall on the module instead (eval with binding is not bound here)
97
+ VALUE[1] av = [pts];
98
+ VALUE code = rb_funcallv(mod, rb_intern("shoelace"), 1, av.ptr);
99
+ return code;
100
+ }
101
+
102
+ // ---- variable arity: sum any number of arguments ----------------------
103
+ // D can't receive C varargs directly; use a fixed max and forward.
104
+
105
+ extern (C) VALUE cross_sum(VALUE self, VALUE a1, VALUE a2, VALUE a3)
106
+ {
107
+ // registered with fixed arity 3; Ruby fills unsupplied slots with
108
+ // Qnil_ only when arity is negative. With fixed arity this method
109
+ // requires exactly 3 args — see experiments/NOTES.md: variable arity
110
+ // needs an argc/argv trampoline that rudy does not yet generate.
111
+ VALUE[3] av = [a1, a2, a3];
112
+ double total = 0.0; // bare `double total;` is NaN in D!
113
+ foreach (v; av)
114
+ total += d_type!double(v);
115
+ return to_ruby_value(total);
116
+ }
117
+
118
+ // ---- raise from deep inside D recursion -------------------------------
119
+
120
+ extern (C) VALUE cross_raise(VALUE self, VALUE depth)
121
+ {
122
+ return to_ruby_value(cross_raise_rec(d_type!long(depth)));
123
+ }
124
+
125
+ long cross_raise_rec(long d)
126
+ {
127
+ if (d == 0)
128
+ rudy_raise(rb_eRangeError, "bottom reached, D unwinds");
129
+ return cross_raise_rec(d - 1) + 1;
130
+ }
131
+
132
+ public:
133
+
134
+ pragma(mangle, "Init_exp_cross")
135
+ extern (C) void Init_exp_cross()
136
+ {
137
+ VALUE m = rb_define_module("ExpCross");
138
+ rb_define_module_function(m, "sort_with_block!", cast(AnyMethod) &cross_sort, 0);
139
+ rb_define_module_function(m, "register_block", cast(AnyMethod) &cross_register, 1);
140
+ rb_define_module_function(m, "register_ary", cast(AnyMethod) &cross_register_ary, 1);
141
+ rb_define_module_function(m, "polygon_area", cast(AnyMethod) &cross_polygon_area, 0);
142
+ rb_define_module_function(m, "sum3", cast(AnyMethod) &cross_sum, 3);
143
+ rb_define_module_function(m, "raise_from_deep", cast(AnyMethod) &cross_raise, 1);
144
+ }
@@ -0,0 +1,301 @@
1
+ /// exp_defargs — torture the def! argument-passing extensions (backlog
2
+ /// item 5): block parameters, *rest, **kwargs, receiver Self, visibility
3
+ /// variants (private/protected), and singleton methods.
4
+ ///
5
+ /// Every function here is registered by Init_exp_defargs via the
6
+ /// def!/def_private/def_protected/def_singleton interface itself — the
7
+ /// point is to exercise the generated trampolines, not hand-written
8
+ /// extern(C) shims.
9
+ ///
10
+ /// Torture elements:
11
+ /// - RubyBlock: call with 0..3 args, given/!given, arity, LocalJumpError
12
+ /// on block-less call, block raising through the D frame, blocks
13
+ /// calling BACK into other def!'d D methods (reentrancy).
14
+ /// - Rest: empty rest, mixed required+rest, hostile values (bignums,
15
+ /// frozen strings), to!T conversion, foreach via opApply, opIndex.
16
+ /// - KwArgs: given/!given, get!T with D defaults, symbol+string key
17
+ /// probing, to!(V,K) walk (symbol keys normalized via to_s),
18
+ /// unknown keys ignored.
19
+ /// - Self: receiver class/identity — module_function self == module,
20
+ /// singleton self == the class object / the named constant.
21
+ /// - Visibility: def_private callable via send() but not directly;
22
+ /// def_protected callable only from inside the class hierarchy.
23
+ /// - Combined layouts: optional+block, rest+block, Raw positional+block.
24
+ ///
25
+ /// Layout:
26
+ /// DDefArgs.each_twice(n) -> yields i*2 for i in 0...n
27
+ /// DDefArgs.map_twice(n) -> block-mapped array
28
+ /// DDefArgs.block_optional(a, b) -> block only when given
29
+ /// DDefArgs.raise_walk(n) -> block raises mid-walk; scope(exit)
30
+ /// DDefArgs.raise_walk_last -> scope(exit) ran?
31
+ /// DDefArgs.block_arity(blk) -> Proc#arity passthrough
32
+ /// DDefArgs.rest_sum(first, ...) -> required + *rest
33
+ /// DDefArgs.rest_join(first, ...) -> rest to!string[] join
34
+ /// DDefArgs.rest_first(...) -> opIndex into hostile values
35
+ /// DDefArgs.rest_count(...) -> rest.length
36
+ /// DDefArgs.kw_pick(**kw) -> get!T with defaults
37
+ /// DDefArgs.kw_all(**kw) -> to!(V,K) walk (Raw!VALUE out)
38
+ /// DDefArgs.self_class -> receiver's class name
39
+ /// DDefArgs::TARGET.self_is_const -> singleton_on receiver identity
40
+ /// DDefArgs.hidden_times_two -> def_private
41
+ /// DDefArgs.shielded_times_three -> def_protected
42
+ /// DDefArgs.opt_block(a, b=10, blk) -> optional + block
43
+ /// DDefArgs.rest_block(first, rest, blk)-> rest + block
44
+ /// DDefArgs.raw_block(v, n, blk) -> Raw + block
45
+ /// DDefArgs.klass_factory(x) -> def_singleton (class-level)
46
+ /// DDefArgs::COUNTER.bump(by) -> def_singleton_on (per-object)
47
+
48
+ module exp_defargs;
49
+
50
+ import rudy.bind.value;
51
+ import rudy.convert;
52
+ import rudy.args;
53
+ import rudy.defs;
54
+
55
+ private:
56
+
57
+ // lazily-interned to_s (args.d's copy is private to that module)
58
+ private __gshared ID exp_id_to_s;
59
+ private ID id_to_s() { if (!exp_id_to_s) exp_id_to_s = rb_intern("to_s"); return exp_id_to_s; }
60
+
61
+ // ------------------------------------------------------------------ blocks
62
+
63
+ // yield i*2 for i in 0...n; returns the block's last value (like times)
64
+ long each_twice(long n, RubyBlock blk)
65
+ {
66
+ if (!blk.given)
67
+ rb_raise(rb_eArgError, "block required");
68
+ VALUE last = Qnil_;
69
+ foreach (i; 0 .. n)
70
+ {
71
+ VALUE[1] av = [rb_ll2inum(i * 2)];
72
+ last = blk.call(av[0]);
73
+ }
74
+ return d_type!long(last);
75
+ }
76
+
77
+ // map: collect block results into a Ruby array. NB: returns Raw!VALUE —
78
+ // a plain VALUE return value goes through to_ruby_value, which converts
79
+ // it as a machine word (finding #17), not as the object.
80
+ Raw!VALUE map_twice(long n, RubyBlock blk)
81
+ {
82
+ if (!blk.given)
83
+ rb_raise(rb_eArgError, "block required");
84
+ VALUE outv = rb_ary_new();
85
+ foreach (i; 0 .. n)
86
+ {
87
+ VALUE[1] av = [rb_ll2inum(i)];
88
+ rb_ary_push(outv, blk.call(av[0]));
89
+ }
90
+ return Raw!VALUE(outv);
91
+ }
92
+
93
+ // block optional: different result with and without a block
94
+ string block_optional(long a, long b)
95
+ {
96
+ import std.conv : to;
97
+ if (b == 10)
98
+ return "b-was-default";
99
+ return "b=" ~ to!string(b);
100
+ }
101
+
102
+ // block raising through the D frame: the block raises, the longjmp
103
+ // unwinds the D frames; raise_walk_last must show the scope(exit)
104
+ // counter incremented (D cleanup ran despite the Ruby-side raise)
105
+ __gshared long raise_walk_count = 0;
106
+
107
+ long raise_walk(long n, RubyBlock blk)
108
+ {
109
+ scope (exit) raise_walk_count++;
110
+ foreach (i; 0 .. n)
111
+ {
112
+ VALUE[1] av = [rb_ll2inum(i)];
113
+ blk.call(av[0]); // raises when i == 2
114
+ }
115
+ return n;
116
+ }
117
+
118
+ long raise_walk_last() { return raise_walk_count; }
119
+
120
+ // block arity passthrough (Proc#arity of the caller's block)
121
+ long block_arity(RubyBlock blk)
122
+ {
123
+ return blk.arity;
124
+ }
125
+
126
+ // ------------------------------------------------------------------ rest
127
+
128
+ long rest_sum(long first, Rest rest)
129
+ {
130
+ long total = first;
131
+ foreach (v; rest) // opApply
132
+ total += d_type!long(v);
133
+ return total;
134
+ }
135
+
136
+ string rest_join(string first, Rest rest)
137
+ {
138
+ string s = first;
139
+ foreach (v; rest)
140
+ s ~= "," ~ d_type!string(v);
141
+ return s;
142
+ }
143
+
144
+ // first rest value decoded via opIndex (hostile values: near-long-max
145
+ // fixnums; real bignums raise RangeError — see test)
146
+ long rest_first(Rest rest)
147
+ {
148
+ if (rest.empty)
149
+ return -1;
150
+ return d_type!long(rest[0]); // opIndex
151
+ }
152
+
153
+ long rest_count(Rest rest)
154
+ {
155
+ return cast(long) rest.length;
156
+ }
157
+
158
+ // ------------------------------------------------------------------ kwargs
159
+
160
+ // kw.get!T with D defaults; compact fingerprint of what was seen
161
+ string kw_pick(KwArgs kw)
162
+ {
163
+ import std.conv : to;
164
+ long n = kw.get!long("n", 0);
165
+ string sep = kw.get!string("sep", "-");
166
+ bool flag = kw.get!bool("flag", false);
167
+ return "n=" ~ to!string(n) ~ " sep=" ~ sep ~ " flag=" ~ (flag ? "y" : "n");
168
+ }
169
+
170
+ // every key => value as flat "key=value" array (symbol AND string keys
171
+ // both surface; symbol keys are normalized via to_s inside KwArgs.to!).
172
+ // Values go through Ruby to_s (Object#to_s fallback) so any value type
173
+ // fingerprints — the D-side AA is V=string, and d_type!string of a
174
+ // non-String VALUE would raise.
175
+ Raw!VALUE kw_all(KwArgs kw)
176
+ {
177
+ import std.conv : to;
178
+ VALUE outv = rb_ary_new();
179
+ // to!(Raw!VALUE, string): keys normalized to D strings (symbol->to_s
180
+ // inside KwArgs.to!), values stay raw VALUEs. NB: plain VALUE as V
181
+ // would route values through d_type!ulong — rudy_check_unsigned
182
+ // probes via rb_num2dbl and a String value dies with "no implicit
183
+ // conversion to float from string". Raw!VALUE is the raw escape.
184
+ foreach (key, val; kw.to!(Raw!VALUE, string))
185
+ {
186
+ VALUE vs = rb_funcallv(val.value, id_to_s(), 0, null);
187
+ rb_ary_push(outv, to_ruby_value(key ~ "=" ~ d_type!string(vs)));
188
+ }
189
+ return Raw!VALUE(outv);
190
+ }
191
+
192
+ // kwargs given vs not: block-less style contract
193
+ string kw_given_p(KwArgs kw)
194
+ {
195
+ return kw.given ? "given" : "none";
196
+ }
197
+
198
+ // ------------------------------------------------------------------ self
199
+
200
+ // class name of the receiver — module_function self is the module itself
201
+ string self_class(Self self)
202
+ {
203
+ return d_type!string(rb_funcallv(
204
+ rb_funcallv(self.value, rb_intern("class"), 0, null),
205
+ rb_intern("to_s"), 0, null));
206
+ }
207
+
208
+ // identity: receiver == the TARGET constant object? (def_singleton_on)
209
+ bool self_is_const(Self self, Raw!VALUE c)
210
+ {
211
+ return self.value == c.value;
212
+ }
213
+
214
+ // ------------------------------------------------------------------ visibility
215
+
216
+ private long hidden_times_two(long n) { return n * 2; }
217
+ private long shielded_times_three(long n) { return n * 3; }
218
+
219
+ // ------------------------------------------------------------------ combined layouts
220
+
221
+ // optional args + block: block only when given
222
+ string opt_block(long a, long b = 10, RubyBlock blk)
223
+ {
224
+ import std.conv : to;
225
+ if (!blk.given)
226
+ return "plain:" ~ to!string(a + b);
227
+ VALUE[1] av = [rb_ll2inum(a + b)];
228
+ return d_type!string(blk.call(av[0]));
229
+ }
230
+
231
+ // rest + block: block adds a bonus to the rest total
232
+ long rest_block(long first, Rest rest, RubyBlock blk)
233
+ {
234
+ long total = first;
235
+ foreach (v; rest)
236
+ total += d_type!long(v);
237
+ if (blk.given)
238
+ {
239
+ VALUE[1] av = [rb_ll2inum(total)];
240
+ total += d_type!long(blk.call(av[0]));
241
+ }
242
+ return total;
243
+ }
244
+
245
+ // raw positional + block
246
+ Raw!VALUE raw_block(Raw!VALUE v, long n, RubyBlock blk)
247
+ {
248
+ if (!blk.given)
249
+ return v;
250
+ VALUE[1] av = [rb_ll2inum(n)];
251
+ return Raw!VALUE(blk.callv(1, av.ptr)); // block transforms the value
252
+ }
253
+
254
+ // ------------------------------------------------------------------ singletons
255
+
256
+ long klass_factory(long x) { return x * 7; }
257
+
258
+ __gshared long counter_state = 0;
259
+ long counter_bump(long by) { counter_state += by; return counter_state; }
260
+
261
+ // ------------------------------------------------------------------ registration
262
+
263
+ pragma(mangle, "Init_exp_defargs")
264
+ extern (C) void Init_exp_defargs()
265
+ {
266
+ VALUE m = rb_define_module("DDefArgs");
267
+
268
+ // constants BEFORE the singleton_on registrations: rb_const_get at
269
+ // def! time needs the target objects to exist already
270
+ rb_define_const(m, "TARGET", rb_str_new("target-obj", 10));
271
+ rb_define_const(m, "COUNTER", rb_str_new("counter-obj", 11));
272
+
273
+ def!("DDefArgs", each_twice);
274
+ def!("DDefArgs", map_twice);
275
+ def!("DDefArgs", block_optional);
276
+ def!("DDefArgs", raise_walk);
277
+ def!("DDefArgs", raise_walk_last);
278
+ def!("DDefArgs", block_arity);
279
+
280
+ def!("DDefArgs", rest_sum);
281
+ def!("DDefArgs", rest_join);
282
+ def!("DDefArgs", rest_first);
283
+ def!("DDefArgs", rest_count);
284
+
285
+ def!("DDefArgs", kw_pick);
286
+ def!("DDefArgs", kw_all);
287
+ def!("DDefArgs", kw_given_p);
288
+
289
+ def!("DDefArgs", self_class); // module_function: self == module
290
+ def_singleton_on!("DDefArgs::TARGET", self_is_const);
291
+
292
+ def_private!("DDefArgs", hidden_times_two);
293
+ def_protected!("DDefArgs", shielded_times_three);
294
+
295
+ def!("DDefArgs", opt_block);
296
+ def!("DDefArgs", rest_block);
297
+ def!("DDefArgs", raw_block);
298
+
299
+ def_singleton!("DDefArgs", klass_factory); // class-level on the module object
300
+ def_singleton_on!("DDefArgs::COUNTER", counter_bump, "bump");
301
+ }