Skip to content

Commit

Permalink
Merge pull request #28 from ruby/pr-e47671d83d6d3c970cc5f6f57ba175d4f…
Browse files Browse the repository at this point in the history
…2e4d6fb

Add == / eql? / strictly_eql? methods
  • Loading branch information
kateinoigakukun authored Jul 2, 2022
2 parents e9c306e + 839b137 commit 28b2c5b
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 0 deletions.
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");
});
});

0 comments on commit 28b2c5b

Please sign in to comment.