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,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
|
+
}
|