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,33 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Fail if vendor/rudy/ has drifted from src/rudy/ (CI guard, and `rake
|
|
3
|
+
# vendor:check` locally). Runs vendor_sync.sh in a scratch copy so the working
|
|
4
|
+
# tree is never touched.
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
9
|
+
|
|
10
|
+
if [[ ! -f "$HERE/vendor/rudy/defs.d" ]]; then
|
|
11
|
+
echo "vendor_check: vendor/rudy/ is missing — run scripts/vendor_sync.sh" >&2
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
scratch="$(mktemp -d)"
|
|
16
|
+
trap 'rm -rf "$scratch"' EXIT
|
|
17
|
+
|
|
18
|
+
mkdir -p "$scratch/src" "$scratch/vendor"
|
|
19
|
+
cp -R "$HERE/src/rudy" "$scratch/src/rudy"
|
|
20
|
+
cp -R "$HERE/vendor/rudy" "$scratch/vendor/rudy"
|
|
21
|
+
|
|
22
|
+
mkdir -p "$scratch/scripts"
|
|
23
|
+
cp "$HERE/scripts/vendor_sync.sh" "$scratch/scripts/"
|
|
24
|
+
|
|
25
|
+
bash "$scratch/scripts/vendor_sync.sh" >/dev/null
|
|
26
|
+
|
|
27
|
+
if ! diff -r "$scratch/vendor/rudy" "$HERE/vendor/rudy" >/dev/null; then
|
|
28
|
+
echo "vendor_check: vendor/rudy/ has drifted from src/rudy/ — run scripts/vendor_sync.sh" >&2
|
|
29
|
+
diff -r "$scratch/vendor/rudy" "$HERE/vendor/rudy" || true
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
echo "vendor_check: vendor/rudy/ is in sync with src/rudy/"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Sync the vendored copy of the rudy4 D library (vendor/rudy/) from src/rudy/.
|
|
3
|
+
#
|
|
4
|
+
# The gem ships the D sources so a scaffolded extension builds without a rudy4
|
|
5
|
+
# checkout. `src/rudy/` stays the single source of truth; this script copies it,
|
|
6
|
+
# and `rake vendor:check` (run in CI) fails if the two have drifted.
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
11
|
+
SRC="$HERE/src/rudy"
|
|
12
|
+
DEST="$HERE/vendor/rudy"
|
|
13
|
+
|
|
14
|
+
if [[ ! -f "$SRC/defs.d" ]]; then
|
|
15
|
+
echo "vendor_sync: source library not found at $SRC" >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
rm -rf "$DEST"
|
|
20
|
+
mkdir -p "$DEST"
|
|
21
|
+
# -R preserves the rudy/bind/ nesting, which the `import rudy.bind.value;`
|
|
22
|
+
# module paths depend on.
|
|
23
|
+
cp -R "$SRC/." "$DEST/"
|
|
24
|
+
|
|
25
|
+
echo "vendor_sync: copied $(find "$DEST" -name '*.d' | wc -l | tr -d ' ') D sources into vendor/rudy/"
|
data/src/dexitest.d
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/// dexitest — acceptance-test extension for the PR#4 missing-API bindings:
|
|
2
|
+
/// rb_yield/rb_yield_values2/rb_yield_splat, rb_block_call, rb_block_given_p,
|
|
3
|
+
/// rb_rescue, rb_ensure, rb_define_singleton_method, rb_scan_args,
|
|
4
|
+
/// rb_thread_call_without_gvl, and the variadic (argc/argv) def! trampoline
|
|
5
|
+
/// for D functions with default arguments.
|
|
6
|
+
///
|
|
7
|
+
/// Also exercises singleton-method registration via the raw
|
|
8
|
+
/// rb_define_singleton_method API and the variadic def! trampoline.
|
|
9
|
+
|
|
10
|
+
module dexitest;
|
|
11
|
+
|
|
12
|
+
import rudy.bind.value;
|
|
13
|
+
import rudy.convert;
|
|
14
|
+
import rudy.defs;
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
private:
|
|
18
|
+
|
|
19
|
+
// ------------------------------------------------------------------
|
|
20
|
+
// 1. rb_yield — yield to the caller's block from a def!'d method
|
|
21
|
+
// ------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
long sum_to(long n)
|
|
24
|
+
{
|
|
25
|
+
long total = 0;
|
|
26
|
+
foreach (i; 1 .. n + 1)
|
|
27
|
+
{
|
|
28
|
+
VALUE[1] av = [rb_ll2inum(i)];
|
|
29
|
+
total += d_type!long(rb_yield(av[0])); // yield i
|
|
30
|
+
}
|
|
31
|
+
return total;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ------------------------------------------------------------------
|
|
35
|
+
// 2. rb_block_given_p + rb_yield_values2 (multiple yield args)
|
|
36
|
+
// ------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
long pair_sums(long n)
|
|
39
|
+
{
|
|
40
|
+
if (rb_block_given_p() == 0)
|
|
41
|
+
return -1; // no block: -1
|
|
42
|
+
long total = 0;
|
|
43
|
+
foreach (i; 1 .. n + 1)
|
|
44
|
+
{
|
|
45
|
+
VALUE[2] av = [rb_ll2inum(i), rb_ll2inum(i * 10)];
|
|
46
|
+
total += d_type!long(rb_yield_values2(2, av.ptr)); // |a, b| a + b
|
|
47
|
+
}
|
|
48
|
+
return total;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ------------------------------------------------------------------
|
|
52
|
+
// 3. rb_block_call — call obj.each with a C-implemented block
|
|
53
|
+
// (collects entries into a Ruby Array, D-side accumulation)
|
|
54
|
+
// ------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
__gshared VALUE block_call_sink; // rooted Ruby Array
|
|
57
|
+
|
|
58
|
+
extern (C) VALUE collect_entry(VALUE yielded, VALUE cb, int argc,
|
|
59
|
+
const(VALUE)* argv, VALUE blockarg)
|
|
60
|
+
{
|
|
61
|
+
rb_ary_push(block_call_sink, yielded);
|
|
62
|
+
return yielded;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// returns Array of elements of the array argument, collected via each
|
|
66
|
+
private VALUE dexitest_block_collect(VALUE self, VALUE ary)
|
|
67
|
+
{
|
|
68
|
+
block_call_sink = rb_ary_new();
|
|
69
|
+
rb_block_call(ary, rb_intern("each"), 0, null, &collect_entry, Qnil_);
|
|
70
|
+
return block_call_sink;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ------------------------------------------------------------------
|
|
74
|
+
// 4. rb_rescue — structured exception handling across the boundary
|
|
75
|
+
// ------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
extern (C) VALUE risky_body(VALUE data) // raises ZeroDivisionError
|
|
78
|
+
{
|
|
79
|
+
VALUE[0] av;
|
|
80
|
+
return rb_funcallv(rb_int2inum(1), rb_intern("/"), 1,
|
|
81
|
+
[rb_int2inum(0)].ptr);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
extern (C) VALUE rescue_handler(VALUE data2, VALUE exc)
|
|
85
|
+
{
|
|
86
|
+
return rb_str_new_cstr("rescued");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private VALUE dexitest_div_rescue(VALUE self, VALUE a, VALUE b)
|
|
90
|
+
{
|
|
91
|
+
// single-class rescue via rb_rescue (StandardError family)
|
|
92
|
+
return rb_rescue(&risky_body, Qnil_, &rescue_handler, Qnil_);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// body that succeeds — rb_rescue returns b_proc's value untouched
|
|
96
|
+
extern (C) VALUE safe_body(VALUE data) { return rb_int2inum(42); }
|
|
97
|
+
|
|
98
|
+
private VALUE dexitest_safe_rescue(VALUE self)
|
|
99
|
+
{
|
|
100
|
+
return rb_rescue(&safe_body, Qnil_, &rescue_handler, Qnil_);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ------------------------------------------------------------------
|
|
104
|
+
// 5. rb_ensure — ensure runs after both success and raise paths
|
|
105
|
+
// ------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
__gshared int ensure_ran = 0;
|
|
108
|
+
|
|
109
|
+
extern (C) VALUE ensure_proc(VALUE data)
|
|
110
|
+
{
|
|
111
|
+
ensure_ran = 1;
|
|
112
|
+
return Qnil_;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
extern (C) VALUE raising_body(VALUE data)
|
|
116
|
+
{
|
|
117
|
+
rb_raise(rb_eRuntimeError, "boom");
|
|
118
|
+
return Qnil_; // unreachable
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private VALUE dexitest_ensure_success(VALUE self)
|
|
122
|
+
{
|
|
123
|
+
ensure_ran = 0;
|
|
124
|
+
VALUE r = rb_ensure(&safe_body, Qnil_, &ensure_proc, Qnil_);
|
|
125
|
+
return to_ruby_value(r == rb_int2inum(42) && ensure_ran == 1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private VALUE dexitest_ensure_raise(VALUE self)
|
|
129
|
+
{
|
|
130
|
+
ensure_ran = 0;
|
|
131
|
+
VALUE r = rb_ensure(&raising_body, Qnil_, &ensure_proc, Qnil_);
|
|
132
|
+
return to_ruby_value(false); // never reached
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// does the ensure run even when the body raises? (call from Ruby rescue)
|
|
136
|
+
private VALUE dexitest_ensure_raised_flag(VALUE self)
|
|
137
|
+
{
|
|
138
|
+
return to_ruby_value(ensure_ran);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ------------------------------------------------------------------
|
|
142
|
+
// 6. rb_define_singleton_method — per-object method
|
|
143
|
+
// ------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
private VALUE singleton_hi(VALUE self)
|
|
146
|
+
{
|
|
147
|
+
return rb_str_new_cstr("hi from singleton");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private VALUE dexitest_make_singleton(VALUE self, VALUE obj)
|
|
151
|
+
{
|
|
152
|
+
rb_define_singleton_method(obj, "dexitest_greet",
|
|
153
|
+
cast(AnyMethod) &singleton_hi, 0);
|
|
154
|
+
return obj;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ------------------------------------------------------------------
|
|
158
|
+
// 7. rb_scan_args — the C-style argument scanner, argc/argv style
|
|
159
|
+
// ------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
private VALUE dexitest_scan(int argc, const(VALUE)* argv, VALUE self)
|
|
162
|
+
{
|
|
163
|
+
VALUE a = Qnil_, b = Qnil_, rest = Qnil_;
|
|
164
|
+
// "11*": one mandatory, one optional, splat the rest
|
|
165
|
+
int n = rb_scan_args(argc, argv, "11*", &a, &b, &rest);
|
|
166
|
+
VALUE res = rb_ary_new_capa(4);
|
|
167
|
+
rb_ary_push(res, rb_ll2inum(n));
|
|
168
|
+
rb_ary_push(res, a);
|
|
169
|
+
rb_ary_push(res, b);
|
|
170
|
+
rb_ary_push(res, rest);
|
|
171
|
+
return res;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ------------------------------------------------------------------
|
|
175
|
+
// 8. rb_thread_call_without_gvl — release the GVL for a pure-D nap
|
|
176
|
+
// (other Ruby threads keep running; result comes back through)
|
|
177
|
+
// ------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
extern (C) void* nap(void* data)
|
|
180
|
+
{
|
|
181
|
+
// busy-wait ~30ms without touching Ruby APIs
|
|
182
|
+
long spins = 30_000_000;
|
|
183
|
+
long acc = 0;
|
|
184
|
+
foreach (i; 0 .. spins)
|
|
185
|
+
acc += i % 7;
|
|
186
|
+
return cast(void*) acc;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
extern (C) void ubf_abort(void* data) {} // no cancellation support
|
|
190
|
+
|
|
191
|
+
private VALUE dexitest_nogvl(VALUE self)
|
|
192
|
+
{
|
|
193
|
+
void* r = rb_thread_call_without_gvl(&nap, null, &ubf_abort, null);
|
|
194
|
+
return rb_ll2inum(cast(long) r);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// same, but prove another Ruby thread ran *while* the GVL was released
|
|
198
|
+
__gshared int tick_ran = 0;
|
|
199
|
+
|
|
200
|
+
private VALUE dexitest_tick(VALUE self) { tick_ran = 1; return Qnil_; }
|
|
201
|
+
|
|
202
|
+
private VALUE dexitest_nogvl_parallel(VALUE self)
|
|
203
|
+
{
|
|
204
|
+
tick_ran = 0;
|
|
205
|
+
// Ruby-side caller spawns a thread that calls dexitest_tick; D naps
|
|
206
|
+
// without the GVL; then we check the flag. See test/exitest.rb.
|
|
207
|
+
void* r = rb_thread_call_without_gvl(&nap, null, &ubf_abort, null);
|
|
208
|
+
return to_ruby_value(tick_ran);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ------------------------------------------------------------------
|
|
212
|
+
// 9. variadic def! — D default arguments become Ruby optional args
|
|
213
|
+
// ------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
long var_sum(long a, long b = 2, long c = 30)
|
|
216
|
+
{
|
|
217
|
+
return a + b + c;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
string var_greet(string name, string greeting = "hello", string punct = "!")
|
|
221
|
+
{
|
|
222
|
+
return greeting ~ ", " ~ name ~ punct;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// mixed required/optional with raw VALUE passthrough (identity on the
|
|
226
|
+
// VALUE, plus an optional second argument that defaults to 5)
|
|
227
|
+
private Raw!VALUE var_raw(Raw!VALUE v, long n = 5)
|
|
228
|
+
{
|
|
229
|
+
if (n == 5)
|
|
230
|
+
return v; // default path: identity
|
|
231
|
+
VALUE[1] av = [rb_ll2inum(n)];
|
|
232
|
+
return Raw!VALUE(rb_funcallv(v.value, rb_intern("*"), 1, av.ptr));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
public:
|
|
236
|
+
|
|
237
|
+
pragma(mangle, "Init_dexitest")
|
|
238
|
+
extern (C) void Init_dexitest()
|
|
239
|
+
{
|
|
240
|
+
VALUE mDExitTest = rb_define_module("DExitTest");
|
|
241
|
+
|
|
242
|
+
// def!'d, conversion-handled
|
|
243
|
+
def!("DExitTest", sum_to);
|
|
244
|
+
def!("DExitTest", pair_sums);
|
|
245
|
+
|
|
246
|
+
// raw extern(C) bodies registered directly (argc/argv or VALUE style)
|
|
247
|
+
rb_define_module_function(mDExitTest, "block_collect",
|
|
248
|
+
cast(AnyMethod) &dexitest_block_collect, 1);
|
|
249
|
+
rb_define_module_function(mDExitTest, "div_rescue",
|
|
250
|
+
cast(AnyMethod) &dexitest_div_rescue, 2);
|
|
251
|
+
rb_define_module_function(mDExitTest, "safe_rescue",
|
|
252
|
+
cast(AnyMethod) &dexitest_safe_rescue, 0);
|
|
253
|
+
rb_define_module_function(mDExitTest, "ensure_success",
|
|
254
|
+
cast(AnyMethod) &dexitest_ensure_success, 0);
|
|
255
|
+
rb_define_module_function(mDExitTest, "ensure_raises",
|
|
256
|
+
cast(AnyMethod) &dexitest_ensure_raise, 0);
|
|
257
|
+
rb_define_module_function(mDExitTest, "ensure_raised_flag",
|
|
258
|
+
cast(AnyMethod) &dexitest_ensure_raised_flag, 0);
|
|
259
|
+
rb_define_module_function(mDExitTest, "make_singleton",
|
|
260
|
+
cast(AnyMethod) &dexitest_make_singleton, 1);
|
|
261
|
+
rb_define_module_function(mDExitTest, "scan",
|
|
262
|
+
cast(AnyMethod) &dexitest_scan, -1);
|
|
263
|
+
rb_define_module_function(mDExitTest, "nogvl",
|
|
264
|
+
cast(AnyMethod) &dexitest_nogvl, 0);
|
|
265
|
+
rb_define_module_function(mDExitTest, "nogvl_parallel",
|
|
266
|
+
cast(AnyMethod) &dexitest_nogvl_parallel, 0);
|
|
267
|
+
rb_define_module_function(mDExitTest, "tick",
|
|
268
|
+
cast(AnyMethod) &dexitest_tick, 0);
|
|
269
|
+
|
|
270
|
+
// variadic def! — optional args via D default values
|
|
271
|
+
def!("DExitTest", var_sum);
|
|
272
|
+
def!("DExitTest", var_greet);
|
|
273
|
+
def!("DExitTest", var_raw);
|
|
274
|
+
|
|
275
|
+
// root the block_call sink
|
|
276
|
+
block_call_sink = rb_ary_new();
|
|
277
|
+
rb_gc_register_address(&block_call_sink);
|
|
278
|
+
}
|
data/src/dextest.d
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/// dextest — revival of RuDy's old "dexter" playground, now the acceptance
|
|
2
|
+
/// test for rudy-d. Exercises def!, to_ruby_value and d_type with real
|
|
3
|
+
/// type conversions, plus a couple of raw C API calls.
|
|
4
|
+
|
|
5
|
+
module dextest;
|
|
6
|
+
|
|
7
|
+
import rudy.bind.value;
|
|
8
|
+
import rudy.convert;
|
|
9
|
+
import rudy.defs;
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------
|
|
12
|
+
// Raw C API class — dexter-style, no conversion sugar.
|
|
13
|
+
// ---------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
extern(C) VALUE dextest_return_ten(VALUE self)
|
|
16
|
+
{
|
|
17
|
+
return rb_int2inum(10);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
extern(C) VALUE dextest_initialize(VALUE self)
|
|
21
|
+
{
|
|
22
|
+
rb_iv_set(self, "@arr", rb_ary_new());
|
|
23
|
+
return self;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
extern(C) VALUE dextest_arr(VALUE self)
|
|
27
|
+
{
|
|
28
|
+
return rb_iv_get(self, "@arr");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
extern(C) VALUE dextest_add(VALUE self, VALUE obj)
|
|
32
|
+
{
|
|
33
|
+
VALUE arr = rb_iv_get(self, "@arr");
|
|
34
|
+
rb_ary_push(arr, obj);
|
|
35
|
+
return arr;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
extern(C) VALUE dextest_native_add(VALUE self, VALUE obj)
|
|
39
|
+
{
|
|
40
|
+
VALUE arr = rb_iv_get(self, "@arr");
|
|
41
|
+
VALUE val = rb_num2long(obj);
|
|
42
|
+
rb_ary_push(arr, rb_ll2inum(val + 1));
|
|
43
|
+
return arr;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
extern(C) VALUE dextest_add_strings_first_letter(VALUE self, VALUE obj)
|
|
47
|
+
{
|
|
48
|
+
VALUE str = rb_str_to_str(obj);
|
|
49
|
+
VALUE letter = rb_str_new(rb_string_value_ptr(&str), 1);
|
|
50
|
+
VALUE arr = rb_iv_get(self, "@arr");
|
|
51
|
+
rb_ary_push(arr, letter);
|
|
52
|
+
return letter;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------
|
|
56
|
+
// def!'d functions — module scope so trampolines get real pointers.
|
|
57
|
+
// ---------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
int the_new_fifteen() { return 15; }
|
|
60
|
+
|
|
61
|
+
long convert_integer_two_ways(long n) { return n; }
|
|
62
|
+
double convert_float_two_ways(double f) { return f; }
|
|
63
|
+
string convert_string_two_ways(string s) { return s; }
|
|
64
|
+
bool convert_bool_two_ways(bool b) { return b; }
|
|
65
|
+
|
|
66
|
+
long add_five(long n) { return n + 5; }
|
|
67
|
+
|
|
68
|
+
// issue #1 regression: ulong params used to receive TAGGED VALUEs (5 -> 11)
|
|
69
|
+
ulong double_it_ulong(ulong n) { return n * 2; }
|
|
70
|
+
ulong identity_ulong(ulong n) { return n; }
|
|
71
|
+
|
|
72
|
+
// opt-in raw VALUE passthrough: the function sees the tagged bits untouched
|
|
73
|
+
Raw!VALUE raw_value_id(Raw!VALUE v) { return v; }
|
|
74
|
+
|
|
75
|
+
string greet(string name, int n)
|
|
76
|
+
{
|
|
77
|
+
import std.conv;
|
|
78
|
+
return "hi " ~ name ~ " " ~ n.to!string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pragma(mangle, "Init_dextest")
|
|
82
|
+
extern(C) void Init_dextest()
|
|
83
|
+
{
|
|
84
|
+
// ---- plain C API class ------------------------------------------
|
|
85
|
+
|
|
86
|
+
VALUE DexterClass = rb_define_class("DexterClass", rb_cObject);
|
|
87
|
+
|
|
88
|
+
rb_define_method(DexterClass, "return_ten",
|
|
89
|
+
cast(AnyMethod) &dextest_return_ten, 0);
|
|
90
|
+
rb_define_method(DexterClass, "initialize",
|
|
91
|
+
cast(AnyMethod) &dextest_initialize, 0);
|
|
92
|
+
rb_define_method(DexterClass, "arr",
|
|
93
|
+
cast(AnyMethod) &dextest_arr, 0);
|
|
94
|
+
rb_define_method(DexterClass, "add",
|
|
95
|
+
cast(AnyMethod) &dextest_add, 1);
|
|
96
|
+
rb_define_method(DexterClass, "native_add",
|
|
97
|
+
cast(AnyMethod) &dextest_native_add, 1);
|
|
98
|
+
rb_define_method(DexterClass, "add_strings_first_letter",
|
|
99
|
+
cast(AnyMethod) &dextest_add_strings_first_letter, 1);
|
|
100
|
+
|
|
101
|
+
// ---- def! with automatic conversions ----------------------------
|
|
102
|
+
|
|
103
|
+
// global (Object level)
|
|
104
|
+
def!(the_new_fifteen);
|
|
105
|
+
def!(convert_integer_two_ways);
|
|
106
|
+
def!(convert_float_two_ways);
|
|
107
|
+
def!(convert_string_two_ways);
|
|
108
|
+
def!(convert_bool_two_ways);
|
|
109
|
+
def!(add_five);
|
|
110
|
+
|
|
111
|
+
// issue #1 regression tests
|
|
112
|
+
def!(double_it_ulong);
|
|
113
|
+
def!(identity_ulong);
|
|
114
|
+
def!(raw_value_id);
|
|
115
|
+
|
|
116
|
+
// module function
|
|
117
|
+
VALUE DexterModule = rb_define_module("DexterModule");
|
|
118
|
+
def!("DexterModule", greet);
|
|
119
|
+
}
|
data/src/dtypetest.d
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/// dtypetest — acceptance-test extension for rudy.typeddata.
|
|
2
|
+
///
|
|
3
|
+
/// Same Matrix as exp_wrap, but wrapped with the TypedData API instead of
|
|
4
|
+
/// rb_data_object_wrap + D-side registry: rb_data_typed_object_wrap provides
|
|
5
|
+
/// the descriptor, rb_check_typeddata does the unwrap with type checking.
|
|
6
|
+
/// Exercises: alloc func, initialize, get/set, det, to_a, GC stress,
|
|
7
|
+
/// TypeError on wrong-type unwrap.
|
|
8
|
+
|
|
9
|
+
module dtypetest;
|
|
10
|
+
|
|
11
|
+
import rudy.bind.value;
|
|
12
|
+
import rudy.convert;
|
|
13
|
+
import rudy.typeddata;
|
|
14
|
+
import core.stdc.stdlib : calloc, malloc, free;
|
|
15
|
+
import core.memory : GC;
|
|
16
|
+
|
|
17
|
+
private:
|
|
18
|
+
|
|
19
|
+
struct Matrix
|
|
20
|
+
{
|
|
21
|
+
size_t rows, cols;
|
|
22
|
+
double* data; // malloc'd; freed by dfree (rudy_typed_free frees the
|
|
23
|
+
// struct itself; data is freed here by matrix_dfree)
|
|
24
|
+
|
|
25
|
+
ref double at(size_t r, size_t c) { return data[r * cols + c]; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// dfree must free data first, then the struct — custom free callback.
|
|
29
|
+
extern (C) void matrix_dfree(void* p)
|
|
30
|
+
{
|
|
31
|
+
auto m = cast(Matrix*) p;
|
|
32
|
+
if (m)
|
|
33
|
+
{
|
|
34
|
+
if (m.data) free(m.data);
|
|
35
|
+
free(m);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
mixin RudyTypedData!(Matrix, "matrix");
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------- methods
|
|
42
|
+
|
|
43
|
+
extern (C) VALUE dtypetest_alloc(VALUE klass)
|
|
44
|
+
{
|
|
45
|
+
auto m = cast(Matrix*) calloc(1, Matrix.sizeof);
|
|
46
|
+
return matrix_wrap(klass, m);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
extern (C) VALUE dtm_initialize(VALUE self, VALUE r, VALUE c)
|
|
50
|
+
{
|
|
51
|
+
Matrix* m = matrix_get(self);
|
|
52
|
+
m.rows = cast(size_t) d_type!long(r);
|
|
53
|
+
m.cols = cast(size_t) d_type!long(c);
|
|
54
|
+
m.data = cast(double*) calloc(Matrix.sizeof, m.rows * m.cols);
|
|
55
|
+
return self;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
extern (C) VALUE dtm_get(VALUE self, VALUE r, VALUE c)
|
|
59
|
+
{
|
|
60
|
+
Matrix* m = matrix_get(self);
|
|
61
|
+
return to_ruby_value(m.at(d_type!long(r), d_type!long(c)));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
extern (C) VALUE dtm_set(VALUE self, VALUE r, VALUE c, VALUE v)
|
|
65
|
+
{
|
|
66
|
+
Matrix* m = matrix_get(self);
|
|
67
|
+
m.at(d_type!long(r), d_type!long(c)) = d_type!double(v);
|
|
68
|
+
return v;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
double det_rec(Matrix* m)
|
|
72
|
+
{
|
|
73
|
+
if (m.rows == 1) return m.at(0, 0);
|
|
74
|
+
if (m.rows == 2)
|
|
75
|
+
return m.at(0, 0) * m.at(1, 1) - m.at(0, 1) * m.at(1, 0);
|
|
76
|
+
double det = 0.0; // NB: bare `double det;` is NaN in D!
|
|
77
|
+
foreach (j; 0 .. m.cols)
|
|
78
|
+
{
|
|
79
|
+
Matrix minor;
|
|
80
|
+
minor.rows = m.rows - 1;
|
|
81
|
+
minor.cols = m.cols - 1;
|
|
82
|
+
minor.data = cast(double*) malloc(double.sizeof * minor.rows * minor.cols);
|
|
83
|
+
scope (exit) free(minor.data);
|
|
84
|
+
foreach (ri; 0 .. minor.rows)
|
|
85
|
+
foreach (ci; 0 .. minor.cols)
|
|
86
|
+
minor.at(ri, ci) = m.at(ri + 1, ci < j ? ci : ci + 1);
|
|
87
|
+
double sign = (j % 2) ? -1.0 : 1.0;
|
|
88
|
+
det += sign * m.at(0, j) * det_rec(&minor);
|
|
89
|
+
}
|
|
90
|
+
return det;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
extern (C) VALUE dtm_det(VALUE self)
|
|
94
|
+
{
|
|
95
|
+
Matrix* m = matrix_get(self);
|
|
96
|
+
return to_ruby_value(det_rec(m));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
extern (C) VALUE dtm_to_a(VALUE self)
|
|
100
|
+
{
|
|
101
|
+
Matrix* m = matrix_get(self);
|
|
102
|
+
VALUE outer = rb_ary_new_capa(cast(C_long) m.rows);
|
|
103
|
+
foreach (r; 0 .. m.rows)
|
|
104
|
+
{
|
|
105
|
+
VALUE row = rb_ary_new_capa(cast(C_long) m.cols);
|
|
106
|
+
foreach (c; 0 .. m.cols)
|
|
107
|
+
rb_ary_push(row, to_ruby_value(m.at(r, c)));
|
|
108
|
+
rb_ary_push(outer, row);
|
|
109
|
+
}
|
|
110
|
+
return outer;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// stress: allocate & drop 100_000 typed wrappers; matrix_dfree must run
|
|
114
|
+
extern (C) VALUE dtm_stress(VALUE self, VALUE n)
|
|
115
|
+
{
|
|
116
|
+
foreach (i; 0 .. d_type!long(n))
|
|
117
|
+
{
|
|
118
|
+
auto m = cast(Matrix*) calloc(1, Matrix.sizeof);
|
|
119
|
+
m.rows = m.cols = 4;
|
|
120
|
+
m.data = cast(double*) malloc(double.sizeof * 16);
|
|
121
|
+
VALUE obj = matrix_wrap(dmatrix_klass, m);
|
|
122
|
+
rb_ary_store(dtypetest_keep, 0, obj); // keep only last
|
|
123
|
+
}
|
|
124
|
+
GC.collect;
|
|
125
|
+
VALUE mod = rb_const_get(rb_cObject, rb_intern("GC"));
|
|
126
|
+
rb_funcallv(mod, rb_intern("start"), 0, null);
|
|
127
|
+
return Qnil_;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// class-level helper: unwrap another object (used to test TypeError)
|
|
131
|
+
extern (C) VALUE dtm_unwrap_other(VALUE self, VALUE obj)
|
|
132
|
+
{
|
|
133
|
+
Matrix* m = matrix_get(obj); // raises TypeError if obj isn't ours
|
|
134
|
+
return to_ruby_value(cast(long) (m.rows * m.cols));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private __gshared VALUE dmatrix_klass;
|
|
138
|
+
private __gshared VALUE dtypetest_keep;
|
|
139
|
+
|
|
140
|
+
public:
|
|
141
|
+
|
|
142
|
+
pragma(mangle, "Init_dtypetest")
|
|
143
|
+
extern (C) void Init_dtypetest()
|
|
144
|
+
{
|
|
145
|
+
VALUE cDTypeMatrix = rb_define_class("DTypeMatrix", rb_cObject);
|
|
146
|
+
rb_define_alloc_func(cDTypeMatrix, &dtypetest_alloc);
|
|
147
|
+
rb_define_method(cDTypeMatrix, "initialize", cast(AnyMethod) &dtm_initialize, 2);
|
|
148
|
+
rb_define_method(cDTypeMatrix, "[]", cast(AnyMethod) &dtm_get, 2);
|
|
149
|
+
rb_define_method(cDTypeMatrix, "[]=", cast(AnyMethod) &dtm_set, 3);
|
|
150
|
+
rb_define_method(cDTypeMatrix, "det", cast(AnyMethod) &dtm_det, 0);
|
|
151
|
+
rb_define_method(cDTypeMatrix, "to_a", cast(AnyMethod) &dtm_to_a, 0);
|
|
152
|
+
rb_define_method(cDTypeMatrix, "stress", cast(AnyMethod) &dtm_stress, 1);
|
|
153
|
+
rb_define_method(cDTypeMatrix, "unwrap_other",cast(AnyMethod) &dtm_unwrap_other, 1);
|
|
154
|
+
dmatrix_klass = cDTypeMatrix;
|
|
155
|
+
dtypetest_keep = rb_ary_new();
|
|
156
|
+
rb_gc_register_address(&dtypetest_keep);
|
|
157
|
+
VALUE m = rb_define_module("DTypeTest");
|
|
158
|
+
rb_define_const(m, "SIZEOF", to_ruby_value(Matrix.sizeof));
|
|
159
|
+
}
|