Skip to content

Commit 6884a17

Browse files
Add JS::Object#{to_i,to_f}
1 parent 807947e commit 6884a17

File tree

8 files changed

+227
-0
lines changed

8 files changed

+227
-0
lines changed

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ void rb_js_abi_host_string_free(rb_js_abi_host_string_t *ret) {
4747
ret->ptr = NULL;
4848
ret->len = 0;
4949
}
50+
void rb_js_abi_host_raw_integer_free(rb_js_abi_host_raw_integer_t *ptr) {
51+
switch ((int32_t) ptr->tag) {
52+
case 1: {
53+
rb_js_abi_host_string_free(&ptr->val.bignum);
54+
break;
55+
}
56+
}
57+
}
5058
void rb_js_abi_host_list_js_abi_value_free(rb_js_abi_host_list_js_abi_value_t *ptr) {
5159
for (size_t i = 0; i < ptr->len; i++) {
5260
rb_js_abi_host_js_abi_value_free(&ptr->ptr[i]);
@@ -119,6 +127,28 @@ void rb_js_abi_host_js_value_to_string(rb_js_abi_host_js_abi_value_t value, rb_j
119127
__wasm_import_rb_js_abi_host_js_value_to_string((value).idx, ptr);
120128
*ret0 = (rb_js_abi_host_string_t) { (char*)(*((int32_t*) (ptr + 0))), (size_t)(*((int32_t*) (ptr + 4))) };
121129
}
130+
__attribute__((import_module("rb-js-abi-host"), import_name("js-value-to-integer: func(value: handle<js-abi-value>) -> variant { f64(float64), bignum(string) }")))
131+
void __wasm_import_rb_js_abi_host_js_value_to_integer(int32_t, int32_t);
132+
void rb_js_abi_host_js_value_to_integer(rb_js_abi_host_js_abi_value_t value, rb_js_abi_host_raw_integer_t *ret0) {
133+
134+
__attribute__((aligned(8)))
135+
uint8_t ret_area[16];
136+
int32_t ptr = (int32_t) &ret_area;
137+
__wasm_import_rb_js_abi_host_js_value_to_integer((value).idx, ptr);
138+
rb_js_abi_host_raw_integer_t variant;
139+
variant.tag = (int32_t) (*((uint8_t*) (ptr + 0)));
140+
switch ((int32_t) variant.tag) {
141+
case 0: {
142+
variant.val.f64 = *((double*) (ptr + 8));
143+
break;
144+
}
145+
case 1: {
146+
variant.val.bignum = (rb_js_abi_host_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) };
147+
break;
148+
}
149+
}
150+
*ret0 = variant;
151+
}
122152
__attribute__((import_module("rb-js-abi-host"), import_name("export-js-value-to-host: func(value: handle<js-abi-value>) -> ()")))
123153
void __wasm_import_rb_js_abi_host_export_js_value_to_host(int32_t);
124154
void rb_js_abi_host_export_js_value_to_host(rb_js_abi_host_js_abi_value_t value) {

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ extern "C"
2222
void rb_js_abi_host_string_set(rb_js_abi_host_string_t *ret, const char *s);
2323
void rb_js_abi_host_string_dup(rb_js_abi_host_string_t *ret, const char *s);
2424
void rb_js_abi_host_string_free(rb_js_abi_host_string_t *ret);
25+
typedef struct {
26+
uint8_t tag;
27+
union {
28+
double f64;
29+
rb_js_abi_host_string_t bignum;
30+
} val;
31+
} rb_js_abi_host_raw_integer_t;
32+
#define RB_JS_ABI_HOST_RAW_INTEGER_F64 0
33+
#define RB_JS_ABI_HOST_RAW_INTEGER_BIGNUM 1
34+
void rb_js_abi_host_raw_integer_free(rb_js_abi_host_raw_integer_t *ptr);
2535
typedef struct {
2636
rb_js_abi_host_js_abi_value_t *ptr;
2737
size_t len;
@@ -37,6 +47,7 @@ extern "C"
3747
rb_js_abi_host_js_abi_value_t rb_js_abi_host_proc_to_js_function(uint32_t value);
3848
rb_js_abi_host_js_abi_value_t rb_js_abi_host_rb_object_to_js_rb_value(uint32_t raw_rb_abi_value);
3949
void rb_js_abi_host_js_value_to_string(rb_js_abi_host_js_abi_value_t value, rb_js_abi_host_string_t *ret0);
50+
void rb_js_abi_host_js_value_to_integer(rb_js_abi_host_js_abi_value_t value, rb_js_abi_host_raw_integer_t *ret0);
4051
void rb_js_abi_host_export_js_value_to_host(rb_js_abi_host_js_abi_value_t value);
4152
rb_js_abi_host_js_abi_value_t rb_js_abi_host_import_js_value_from_host(void);
4253
void rb_js_abi_host_js_value_typeof(rb_js_abi_host_js_abi_value_t value, rb_js_abi_host_string_t *ret0);

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ rb-object-to-js-rb-value: func(raw-rb-abi-value: u32) -> js-abi-value
1212

1313
js-value-to-string: func(value: js-abi-value) -> string
1414

15+
variant raw-integer {
16+
f64(float64),
17+
bignum(string),
18+
}
19+
20+
js-value-to-integer: func(value: js-abi-value) -> raw-integer
21+
1522
export-js-value-to-host: func(value: js-abi-value)
1623
import-js-value-from-host: func() -> js-abi-value
1724

ext/js/js-core.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,66 @@ static VALUE _rb_js_obj_to_s(VALUE obj) {
313313
return rb_str_new(ret0.ptr, ret0.len);
314314
}
315315

316+
/*
317+
* call-seq:
318+
* to_i -> integer
319+
*
320+
* Converts +self+ to an Integer:
321+
* JS.eval("return 1").to_i # => 1
322+
* JS.eval("return -1").to_i # => -1
323+
* JS.eval("return 5.8").to_i # => 5
324+
* JS.eval("return 42n").to_i # => 42
325+
* JS.eval("return '3'").to_i # => 3
326+
* JS.eval("return ''").to_f # => 0
327+
* JS.eval("return 'x'").to_i # => 0
328+
* JS.eval("return NaN").to_i # Raises FloatDomainError
329+
* JS.eval("return Infinity").to_i # Raises FloatDomainError
330+
* JS.eval("return -Infinity").to_i # Raises FloatDomainError
331+
*/
332+
static VALUE _rb_js_obj_to_i(VALUE obj) {
333+
struct jsvalue *p = check_jsvalue(obj);
334+
rb_js_abi_host_raw_integer_t ret;
335+
rb_js_abi_host_js_value_to_integer(p->abi, &ret);
336+
VALUE result;
337+
if (ret.tag == RB_JS_ABI_HOST_RAW_INTEGER_F64) {
338+
result = rb_dbl2big(ret.val.f64);
339+
} else {
340+
result = rb_cstr2inum(ret.val.bignum.ptr, 10);
341+
}
342+
rb_js_abi_host_raw_integer_free(&ret);
343+
return result;
344+
}
345+
346+
/*
347+
* call-seq:
348+
* to_f -> float
349+
*
350+
* Converts +self+ to a Float:
351+
* JS.eval("return 1").to_f # => 1.0
352+
* JS.eval("return 1.2").to_f # => 1.2
353+
* JS.eval("return -1.2").to_f # => -1.2
354+
* JS.eval("return '3.14'").to_f # => 3.14
355+
* JS.eval("return ''").to_f # => 0.0
356+
* JS.eval("return 'x'").to_f # => 0.0
357+
* JS.eval("return NaN").to_f # => Float::NAN
358+
* JS.eval("return Infinity").to_f # => Float::INFINITY
359+
* JS.eval("return -Infinity").to_f # => -Float::INFINITY
360+
*
361+
*/
362+
static VALUE _rb_js_obj_to_f(VALUE obj) {
363+
struct jsvalue *p = check_jsvalue(obj);
364+
rb_js_abi_host_raw_integer_t ret;
365+
VALUE result;
366+
rb_js_abi_host_js_value_to_integer(p->abi, &ret);
367+
if (ret.tag == RB_JS_ABI_HOST_RAW_INTEGER_F64) {
368+
result = rb_float_new(ret.val.f64);
369+
} else {
370+
result = DBL2NUM(rb_cstr_to_dbl(ret.val.bignum.ptr, FALSE));
371+
}
372+
rb_js_abi_host_raw_integer_free(&ret);
373+
return result;
374+
}
375+
316376
/*
317377
* :nodoc: all
318378
* workaround to transfer js value to js by using wit.
@@ -423,6 +483,8 @@ void Init_js() {
423483
_rb_js_import_from_js, 0);
424484
rb_define_method(rb_cJS_Object, "to_s", _rb_js_obj_to_s, 0);
425485
rb_define_alias(rb_cJS_Object, "inspect", "to_s");
486+
rb_define_method(rb_cJS_Object, "to_i", _rb_js_obj_to_i, 0);
487+
rb_define_method(rb_cJS_Object, "to_f", _rb_js_obj_to_f, 0);
426488
rb_define_singleton_method(rb_cJS_Object, "wrap", _rb_js_obj_wrap, 1);
427489

428490
rb_define_method(rb_cInteger, "to_js", _rb_js_integer_to_js, 0);

packages/npm-packages/ruby-wasm-wasi/src/bindgen/rb-js-abi-host.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
export type RawInteger = RawIntegerF64 | RawIntegerBignum;
2+
export interface RawIntegerF64 {
3+
tag: "f64",
4+
val: number,
5+
}
6+
export interface RawIntegerBignum {
7+
tag: "bignum",
8+
val: string,
9+
}
110
export function addRbJsAbiHostToImports(imports: any, obj: RbJsAbiHost, get_export: (name: string) => WebAssembly.ExportValue): void;
211
export interface RbJsAbiHost {
312
evalJs(code: string): JsAbiValue;
@@ -10,6 +19,7 @@ export interface RbJsAbiHost {
1019
procToJsFunction(value: number): JsAbiValue;
1120
rbObjectToJsRbValue(rawRbAbiValue: number): JsAbiValue;
1221
jsValueToString(value: JsAbiValue): string;
22+
jsValueToInteger(value: JsAbiValue): RawInteger;
1323
exportJsValueToHost(value: JsAbiValue): void;
1424
importJsValueFromHost(): JsAbiValue;
1525
jsValueTypeof(value: JsAbiValue): string;

packages/npm-packages/ruby-wasm-wasi/src/bindgen/rb-js-abi-host.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,31 @@ export function addRbJsAbiHostToImports(imports, obj, get_export) {
5555
data_view(memory).setInt32(arg1 + 4, len0, true);
5656
data_view(memory).setInt32(arg1 + 0, ptr0, true);
5757
};
58+
imports["rb-js-abi-host"]["js-value-to-integer: func(value: handle<js-abi-value>) -> variant { f64(float64), bignum(string) }"] = function(arg0, arg1) {
59+
const memory = get_export("memory");
60+
const realloc = get_export("cabi_realloc");
61+
const ret0 = obj.jsValueToInteger(resources0.get(arg0));
62+
const variant1 = ret0;
63+
switch (variant1.tag) {
64+
case "f64": {
65+
const e = variant1.val;
66+
data_view(memory).setInt8(arg1 + 0, 0, true);
67+
data_view(memory).setFloat64(arg1 + 8, +e, true);
68+
break;
69+
}
70+
case "bignum": {
71+
const e = variant1.val;
72+
data_view(memory).setInt8(arg1 + 0, 1, true);
73+
const ptr0 = utf8_encode(e, realloc, memory);
74+
const len0 = UTF8_ENCODED_LEN;
75+
data_view(memory).setInt32(arg1 + 12, len0, true);
76+
data_view(memory).setInt32(arg1 + 8, ptr0, true);
77+
break;
78+
}
79+
default:
80+
throw new RangeError("invalid variant specified for RawInteger");
81+
}
82+
};
5883
imports["rb-js-abi-host"]["export-js-value-to-host: func(value: handle<js-abi-value>) -> ()"] = function(arg0) {
5984
obj.exportJsValueToHost(resources0.get(arg0));
6085
};

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,19 @@ export class RubyVM {
108108
// `String(value)` always returns a string.
109109
return String(value);
110110
},
111+
jsValueToInteger(value) {
112+
if (typeof value === "number") {
113+
return { tag: "f64", val: value };
114+
} else if (typeof value === "bigint") {
115+
return { tag: "bignum", val: BigInt(value).toString(10) + "\0" };
116+
} else if (typeof value === "string") {
117+
return { tag: "bignum", val: value + "\0" };
118+
} else if (typeof value === "undefined") {
119+
return { tag: "f64", val: 0 };
120+
} else {
121+
return { tag: "f64", val: Number(value) };
122+
}
123+
},
111124
exportJsValueToHost: (value) => {
112125
// See `JsValueExporter` for the reason why we need to do this
113126
this.transport.takeJsValue(value);

packages/npm-packages/ruby-wasm-wasi/test/unit/test_object.rb

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,75 @@ def test_inspect
7272
assert_equal "undefined", JS.eval("return undefined;").inspect
7373
end
7474

75+
def test_to_i_from_number
76+
assert_equal 1, JS.eval("return 1;").to_i
77+
assert_equal -1, JS.eval("return -1;").to_i
78+
assert_equal 1, JS.eval("return 1.8;").to_i
79+
assert_equal (2**53 - 1), JS.eval("return Number.MAX_SAFE_INTEGER;").to_i
80+
assert_equal -(2**53 - 1), JS.eval("return Number.MIN_SAFE_INTEGER;").to_i
81+
assert_equal Float::MAX.to_i, JS.eval("return Number.MAX_VALUE;").to_i
82+
assert_equal 0, JS.eval("return Number.MIN_VALUE;").to_i
83+
84+
# Special values
85+
assert_raise(FloatDomainError) { JS.eval("return NaN;").to_i }
86+
assert_raise(FloatDomainError) { JS.eval("return Infinity;").to_i }
87+
assert_raise(FloatDomainError) { JS.eval("return -Infinity;").to_i }
88+
end
89+
90+
def test_to_i_from_bigint
91+
assert_equal 1, JS.eval("return 1n;").to_i
92+
assert_equal 0xffff0000, JS.eval("return 0xffff0000n;").to_i
93+
assert_equal (1 << 32), JS.eval("return (1n << 32n);").to_i
94+
assert_equal (1 << 64), JS.eval("return (1n << 64n);").to_i
95+
end
96+
97+
def test_to_i_from_non_numeric
98+
assert_equal 0, JS.eval("return null;").to_i
99+
assert_equal 0, JS.eval("return undefined;").to_i
100+
101+
# String
102+
assert_equal 0, JS.eval("return '';").to_i
103+
assert_equal 42, JS.eval("return '42';").to_i
104+
assert_equal 0, JS.eval("return 'str';").to_i
105+
assert_equal 42, JS.eval("return '42str';").to_i
106+
end
107+
108+
def test_to_f
109+
assert_equal 1.0, JS.eval("return 1;").to_f
110+
assert_equal -1.0, JS.eval("return -1;").to_f
111+
assert_true JS.eval("return NaN;").to_f.nan?
112+
assert_equal 1, JS.eval("return Infinity;").to_f.infinite?
113+
assert_equal -1, JS.eval("return -Infinity;").to_f.infinite?
114+
# Maximum positive value of IEEE 754 double-precision float
115+
# (1.0 + ((2 ** 52 - 1) * (2 ** -52.0))) * (2 ** 1023.0)
116+
assert_equal 1.7976931348623157e+308,
117+
JS.eval("return Number.MAX_VALUE;").to_f
118+
# Minimum positive value of IEEE 754 double-precision float
119+
# 1.0 * 2 ** -52 * 2**-1022 (Subnormal number)
120+
assert_equal 5.0e-324, JS.eval("return Number.MIN_VALUE;").to_f
121+
end
122+
123+
def test_to_f_from_bigint
124+
assert_true JS.eval("return 1n;").to_f.is_a?(Float)
125+
assert_equal 1, JS.eval("return 1n;").to_f
126+
assert_equal 0xffff0000, JS.eval("return 0xffff0000n;").to_f
127+
assert_equal (1 << 32), JS.eval("return (1n << 32n);").to_f
128+
assert_equal (1 << 64), JS.eval("return (1n << 64n);").to_f
129+
end
130+
131+
def test_to_f_from_non_numeric
132+
assert_equal 0, JS.eval("return null;").to_f
133+
assert_equal 0, JS.eval("return undefined;").to_f
134+
135+
# String
136+
assert_equal 42, JS.eval("return '42';").to_f
137+
assert_equal 42.5, JS.eval("return '42.5';").to_f
138+
assert_equal 0, JS.eval("return '';").to_f
139+
assert_equal 0, JS.eval("return 'str';").to_f
140+
assert_equal 42, JS.eval("return '42str';").to_f
141+
assert_equal 42.4, JS.eval("return '42.4str';").to_f
142+
end
143+
75144
def test_call
76145
assert_nothing_raised { JS.global.call(:Array) }
77146
assert_equal "1,2,3", JS.global.call(:Array, 1, 2, 3).to_s

0 commit comments

Comments
 (0)