Skip to content

Commit fbd6d5e

Browse files
Implement Proc#to_js and add ability to pass blocks to JS::Object#call (#36)
* Add Proc#to_js to allow passing functions as call arguments * Allow passing blocks to JS::Object#call * Use rb_abi_lend_object to keep reference to wrapped Proc * Add test that holds wrapped Proc in a variable before calling it * Add documentation for Proc#to_js
1 parent 81f24cc commit fbd6d5e

File tree

4 files changed

+66
-7
lines changed

4 files changed

+66
-7
lines changed

ext/js/bindgen/rb-js-abi-host.wit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ global-this: function() -> js-abi-value
77
int-to-js-number: function(value: s32) -> js-abi-value
88
string-to-js-string: function(value: string) -> js-abi-value
99
bool-to-js-bool: function(value: bool) -> js-abi-value
10+
proc-to-js-function: function(value: u32) -> js-abi-value
1011
rb-object-to-js-rb-value: function(raw-rb-abi-value: u32) -> js-abi-value
1112

1213
js-value-to-string: function(value: js-abi-value) -> string

ext/js/js-core.c

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extern VALUE rb_cInteger;
1515
extern VALUE rb_cString;
1616
extern VALUE rb_cTrueClass;
1717
extern VALUE rb_cFalseClass;
18+
extern VALUE rb_cProc;
1819

1920
// from js/js-core.c
2021
void rb_abi_lend_object(VALUE obj);
@@ -246,8 +247,12 @@ static VALUE _rb_js_obj_call(int argc, VALUE *argv, VALUE obj) {
246247
struct jsvalue *abi_method = check_jsvalue(method);
247248

248249
rb_js_abi_host_list_js_abi_value_t abi_args;
249-
abi_args.ptr = ALLOCA_N(rb_js_abi_host_js_abi_value_t, argc - 1);
250-
abi_args.len = argc - 1;
250+
int function_arguments_count = argc;
251+
if(!rb_block_given_p())
252+
function_arguments_count -= 1;
253+
254+
abi_args.ptr = ALLOCA_N(rb_js_abi_host_js_abi_value_t, function_arguments_count);
255+
abi_args.len = function_arguments_count;
251256
for (int i = 1; i < argc; i++) {
252257
VALUE arg = _rb_js_try_convert(rb_mJS, argv[i]);
253258
if (arg == Qnil) {
@@ -256,6 +261,12 @@ static VALUE _rb_js_obj_call(int argc, VALUE *argv, VALUE obj) {
256261
}
257262
abi_args.ptr[i - 1] = check_jsvalue(arg)->abi;
258263
}
264+
265+
if(rb_block_given_p()) {
266+
VALUE proc = rb_block_proc();
267+
abi_args.ptr[function_arguments_count - 1] = check_jsvalue(_rb_js_try_convert(rb_mJS, proc))->abi;
268+
}
269+
259270
return jsvalue_s_new(
260271
rb_js_abi_host_reflect_apply(abi_method->abi, p->abi, &abi_args));
261272
}
@@ -365,6 +376,17 @@ static VALUE _rb_js_false_to_js(VALUE obj) {
365376
return jsvalue_s_new(rb_js_abi_host_bool_to_js_bool(false));
366377
}
367378

379+
/*
380+
* call-seq:
381+
* to_js -> JS::Object
382+
*
383+
* Returns +self+ as a JS::Object.
384+
*/
385+
static VALUE _rb_js_proc_to_js(VALUE obj) {
386+
rb_abi_lend_object(obj);
387+
return jsvalue_s_new(rb_js_abi_host_proc_to_js_function((uint32_t) obj));
388+
}
389+
368390
/*
369391
* JavaScript interoperations module
370392
*/
@@ -395,4 +417,5 @@ void Init_js() {
395417
rb_define_method(rb_cString, "to_js", _rb_js_string_to_js, 0);
396418
rb_define_method(rb_cTrueClass, "to_js", _rb_js_true_to_js, 0);
397419
rb_define_method(rb_cFalseClass, "to_js", _rb_js_false_to_js, 0);
420+
rb_define_method(rb_cProc, "to_js", _rb_js_proc_to_js, 0);
398421
}

packages/npm-packages/ruby-wasm-wasi/src/index.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,14 @@ export class RubyVM {
9494
boolToJsBool: (value) => {
9595
return value;
9696
},
97+
procToJsFunction: (rawRbAbiValue) => {
98+
const rbValue = this.rbValueofPointer(rawRbAbiValue);
99+
return (...args) => {
100+
rbValue.call('call', ...args.map(arg => this.wrap(arg)));
101+
}
102+
},
97103
rbObjectToJsRbValue: (rawRbAbiValue) => {
98-
const abiValue = new (RbAbi.RbAbiValue as any)(
99-
rawRbAbiValue,
100-
this.guest
101-
);
102-
return new RbValue(abiValue, this, this.privateObject());
104+
return this.rbValueofPointer(rawRbAbiValue);
103105
},
104106
jsValueToString: (value) => {
105107
// According to the [spec](https://tc39.es/ecma262/multipage/text-processing.html#sec-string-constructor-string-value)
@@ -216,6 +218,11 @@ export class RubyVM {
216218
exceptionFormatter: this.exceptionFormatter,
217219
};
218220
}
221+
222+
private rbValueofPointer(pointer: number): RbValue {
223+
const abiValue = new (RbAbi.RbAbiValue as any)(pointer, this.guest);
224+
return new RbValue(abiValue, this, this.privateObject());
225+
}
219226
}
220227

221228
/**

packages/npm-packages/ruby-wasm-wasi/test/js_from_rb.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,34 @@ describe("Manipulation of JS from Ruby", () => {
122122
expr: `JS.global[:Math].call(:min, JS.eval("return 1"), JS.eval("return 2"))`,
123123
result: 1,
124124
},
125+
{
126+
expr: `
127+
function_to_call = JS.eval('return { a: (callback) => { callback(1) } }')
128+
b = nil
129+
function_to_call.call(:a, Proc.new { |a| b = a })
130+
b
131+
`,
132+
result: 1
133+
},
134+
{
135+
expr: `
136+
function_to_call = JS.eval('return { a: (callback) => { callback(1) } }')
137+
b = nil
138+
function_to_call.call(:a) { |a| b = a }
139+
b
140+
`,
141+
result: 1
142+
},
143+
{
144+
expr: `
145+
function_to_call = JS.eval('let callback; return { a: (c) => { callback = c }, b: () => { callback(1) } }')
146+
b = nil
147+
function_to_call.call(:a) { |a| b = a }
148+
function_to_call.call(:b)
149+
b
150+
`,
151+
result: 1
152+
}
125153
])(`JS::Object#call (%s)`, async (props) => {
126154
const vm = await initRubyVM();
127155
const result = vm.eval(`

0 commit comments

Comments
 (0)