Skip to content

Add == / eql? / strictly_eql? methods #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ext/js/bindgen/rb-js-abi-host.wit
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import-js-value-from-host: function() -> js-abi-value

js-value-typeof: function(value: js-abi-value) -> string

js-value-equal: function(lhs: js-abi-value, rhs: js-abi-value) -> bool
js-value-strictly-equal: function(lhs: js-abi-value, rhs: js-abi-value) -> bool

reflect-apply: function(target: js-abi-value, this-argument: js-abi-value, arguments: list<js-abi-value>) -> js-abi-value
reflect-construct: function(target: js-abi-value, arguments: list<js-abi-value>) -> js-abi-value
reflect-delete-property: function(target: js-abi-value, property-key: string) -> bool
Expand Down
43 changes: 43 additions & 0 deletions ext/js/js-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,45 @@ static VALUE _rb_js_obj_aset(VALUE obj, VALUE key, VALUE val) {
return val;
}

/*
* call-seq:
* js_value.strictly_eql?(other) -> boolean
*
* Performs "===" comparison, a.k.a the "Strict Equality Comparison"
* algorithm defined in the ECMAScript.
* https://262.ecma-international.org/11.0/#sec-strict-equality-comparison
*/
static VALUE _rb_js_obj_strictly_eql(VALUE obj, VALUE other) {
struct jsvalue *lhs = check_jsvalue(obj);
struct jsvalue *rhs = check_jsvalue(other);
bool result = rb_js_abi_host_js_value_strictly_equal(lhs->abi, rhs->abi);
return RBOOL(result);
}

/*
* call-seq:
* js_value.==(other) -> boolean
* js_value.eql?(other) -> boolean
*
* Performs "==" comparison, a.k.a the "Abstract Equality Comparison"
* algorithm defined in the ECMAScript.
* https://262.ecma-international.org/11.0/#sec-abstract-equality-comparison
*/
static VALUE _rb_js_obj_eql(VALUE obj, VALUE other) {
struct jsvalue *lhs = check_jsvalue(obj);
struct jsvalue *rhs = check_jsvalue(other);
bool result = rb_js_abi_host_js_value_equal(lhs->abi, rhs->abi);
return RBOOL(result);
}

/*
* :nodoc: all
*/
static VALUE _rb_js_obj_hash(VALUE obj) {
// TODO(katei): Track the JS object id in JS side as Pyodide and Swift JavaScriptKit do.
return Qnil;
}

/*
* call-seq:
* js_value.call(name, *args) -> JS::Object
Expand Down Expand Up @@ -337,6 +376,10 @@ void Init_js() {
rb_define_alloc_func(rb_cJS_Object, jsvalue_s_allocate);
rb_define_method(rb_cJS_Object, "[]", _rb_js_obj_aref, 1);
rb_define_method(rb_cJS_Object, "[]=", _rb_js_obj_aset, 2);
rb_define_method(rb_cJS_Object, "strictly_eql?", _rb_js_obj_strictly_eql, 1);
rb_define_method(rb_cJS_Object, "eql?", _rb_js_obj_eql, 1);
rb_define_method(rb_cJS_Object, "==", _rb_js_obj_eql, 1);
rb_define_method(rb_cJS_Object, "hash", _rb_js_obj_hash, 0);
rb_define_method(rb_cJS_Object, "call", _rb_js_obj_call, -1);
rb_define_method(rb_cJS_Object, "typeof", _rb_js_obj_typeof, 0);
rb_define_method(rb_cJS_Object, "__export_to_js", _rb_js_export_to_js, 0);
Expand Down
6 changes: 6 additions & 0 deletions packages/npm-packages/ruby-wasm-wasi/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ export class RubyVM {
jsValueTypeof(value) {
return typeof value;
},
jsValueEqual(lhs, rhs) {
return lhs == rhs;
},
jsValueStrictlyEqual(lhs, rhs) {
return lhs === rhs;
},
reflectApply: function (target, thisArgument, args) {
return Reflect.apply(target as any, thisArgument, args);
},
Expand Down
44 changes: 44 additions & 0 deletions packages/npm-packages/ruby-wasm-wasi/test/js_from_rb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,33 @@ describe("Manipulation of JS from Ruby", () => {
expect(vm.eval(code).toString()).toBe(String(props.result));
});

test.each([
{ lhs: `24`, rhs: `24`, result: true },
{ lhs: `null`, rhs: `null`, result: true },
{ lhs: `undefined`, rhs: `undefined`, result: true },
{ lhs: `"str"`, rhs: `"str"`, result: true },
{ lhs: `48`, rhs: `24`, result: false },
{ lhs: `NaN`, rhs: `NaN`, result: false },
])("JS::Object#== (%s)", async (props) => {
const vm = await initRubyVM();
const methodResult = `require "js"; JS.eval('return ${props.lhs}').eql?(JS.eval('return ${props.rhs}'))`;
expect(vm.eval(methodResult).toString()).toBe(String(props.result));

const operatorResult = `require "js"; JS.eval('return ${props.lhs}') == JS.eval('return ${props.rhs}')`;
expect(vm.eval(operatorResult).toString()).toBe(String(props.result));
});

test.each([
{ lhs: `24`, rhs: `24`, result: true },
{ lhs: `null`, rhs: `null`, result: true },
{ lhs: `undefined`, rhs: `undefined`, result: true },
{ lhs: `new String("str")`, rhs: `"str"`, result: false },
])("JS::Object#strictly_eql? (%s)", async (props) => {
const vm = await initRubyVM();
const result = `require "js"; JS.eval('return ${props.lhs}').strictly_eql?(JS.eval('return ${props.rhs}'))`;
expect(vm.eval(result).toString()).toBe(String(props.result));
});

test.each([
{ expr: "JS.global[:Object]", result: Object },
{ expr: "JS.global[:Object][:keys]", result: Object.keys },
Expand Down Expand Up @@ -180,4 +207,21 @@ describe("Manipulation of JS from Ruby", () => {
const o1Clone = results.call("at", vm.eval("1"));
expect(o1.toString()).toEqual(o1Clone.toString());
});

test("Guard null", async () => {
const vm = await initRubyVM();
const result = vm.eval(`
require "js"
intrinsics = JS.eval(<<-JS)
return {
returnNull(v) { return null },
returnUndef(v) { return undefined },
}
JS
js_null = JS.eval("return null")
o1 = intrinsics.call(:returnNull)
o1 == js_null
`);
expect(result.toString()).toEqual("true");
});
});