diff --git a/examples/results.txt b/examples/results.txt index 79ff3aa..8348062 100644 --- a/examples/results.txt +++ b/examples/results.txt @@ -1469,7 +1469,7 @@ compat-table/intl/Intl.prototype.js: OK compat-table/intl/Number.prototype.toLocaleString.js: OK compat-table/intl/Object.prototype.toLocaleString.js: OK compat-table/intl/String.prototype.localeCompare.js: OK -compat-table/next/Array.isTemplateObject.js: TypeError: undefined is not a function +compat-table/next/Array.isTemplateObject.js: OK compat-table/next/AsyncDisposableStack.js: OK compat-table/next/AsyncIterator.extends.js: OK compat-table/next/AsyncIterator.from.async-iterable.js: OK diff --git a/include/common.h b/include/common.h index 8b74a7e..e49b91c 100644 --- a/include/common.h +++ b/include/common.h @@ -133,7 +133,8 @@ typedef enum { BRAND_EVENTEMITTER, BRAND_EVENTTARGET, BRAND_DISPOSABLE_STACK, - BRAND_ASYNC_DISPOSABLE_STACK + BRAND_ASYNC_DISPOSABLE_STACK, + BRAND_TEMPLATE_OBJECT } object_brand_id_t; static inline void *mantissa_chk(void *p, const char *func) { diff --git a/include/silver/opcode.h b/include/silver/opcode.h index 01b6f87..ab0a728 100644 --- a/include/silver/opcode.h +++ b/include/silver/opcode.h @@ -38,6 +38,7 @@ OP_DEF( THIS, 1, 0, 1, none) /* push current 'this' */ OP_DEF( GLOBAL, 1, 0, 1, none) /* push 'globalThis' (js->global) */ OP_DEF( OBJECT, 1, 0, 1, none) /* push empty object {} */ OP_DEF( ARRAY, 3, 0, 1, npop) /* push array from stack items */ +OP_DEF( SET_BRAND, 2, 1, 1, u8) /* obj -> obj (brand id) */ OP_DEF( REGEXP, 1, 2, 1, none) /* pattern flags -> regexp */ OP_DEF( CLOSURE, 5, 0, 1, const) /* push closure from func pool */ diff --git a/src/ant.c b/src/ant.c index 8f4fcc9..98f1cef 100644 --- a/src/ant.c +++ b/src/ant.c @@ -10152,6 +10152,11 @@ static ant_value_t builtin_Array_isArray(ant_params_t) { return mkval(T_BOOL, vtype(args[0]) == T_ARR ? 1 : 0); } +static ant_value_t builtin_Array_isTemplateObject(ant_params_t) { + if (nargs == 0) return js_false; + return js_bool(vtype(args[0]) == T_ARR && js_check_brand(args[0], BRAND_TEMPLATE_OBJECT)); +} + typedef struct { ant_value_t write_target; ant_value_t result; @@ -14817,6 +14822,7 @@ ant_t *js_create(void *buf, size_t len) { set_slot(arr_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_Array)); js_setprop_readonly_nonconfigurable(js, arr_ctor_obj, "prototype", 9, array_proto); defmethod(js, arr_ctor_obj, "isArray", 7, js_mkfun(builtin_Array_isArray)); + defmethod(js, arr_ctor_obj, "isTemplateObject", 16, js_mkfun(builtin_Array_isTemplateObject)); defmethod(js, arr_ctor_obj, "from", 4, js_mkfun(builtin_Array_from)); defmethod(js, arr_ctor_obj, "of", 2, js_mkfun(builtin_Array_of)); js_setprop(js, arr_ctor_obj, js->length_str, tov(1.0)); diff --git a/src/silver/compiler.c b/src/silver/compiler.c index 2605250..7afd9a7 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -1709,6 +1709,8 @@ void compile_expr(sv_compiler_t *c, sv_ast_t *node) { emit_op(c, OP_ROT3L); emit_op(c, OP_CALL_METHOD); emit_u16(c, 1); + emit_op(c, OP_SET_BRAND); + emit(c, BRAND_TEMPLATE_OBJECT); emit_op(c, OP_DUP); emit_op(c, OP_PUT_CONST); emit_u32(c, (uint32_t)cache_idx); diff --git a/src/silver/engine.c b/src/silver/engine.c index 5ac8865..8a3f56d 100644 --- a/src/silver/engine.c +++ b/src/silver/engine.c @@ -952,19 +952,20 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant } DISPATCH(); - L_CONST: { sv_op_const(vm, func, ip); NEXT(5); } - L_CONST_I8: { sv_op_const_i8(vm, ip); NEXT(2); } - L_CONST8: { sv_op_const8(vm, func, ip); NEXT(2); } - L_UNDEF: { sv_op_undef(vm); NEXT(1); } - L_NULL: { sv_op_null(vm); NEXT(1); } - L_TRUE: { sv_op_true(vm); NEXT(1); } - L_FALSE: { sv_op_false(vm); NEXT(1); } - L_THIS: { sv_op_this(vm, frame); NEXT(1); } - L_GLOBAL: { sv_op_global(vm, js); NEXT(1); } - L_OBJECT: { sv_op_object(vm, js, func, ip); NEXT(1); } - L_ARRAY: { sv_op_array(vm, js, ip); NEXT(3); } - - L_REGEXP: { sv_op_regexp(vm, js); NEXT(1); } + L_CONST: { sv_op_const(vm, func, ip); NEXT(5); } + L_CONST_I8: { sv_op_const_i8(vm, ip); NEXT(2); } + L_CONST8: { sv_op_const8(vm, func, ip); NEXT(2); } + L_UNDEF: { sv_op_undef(vm); NEXT(1); } + L_NULL: { sv_op_null(vm); NEXT(1); } + L_TRUE: { sv_op_true(vm); NEXT(1); } + L_FALSE: { sv_op_false(vm); NEXT(1); } + L_THIS: { sv_op_this(vm, frame); NEXT(1); } + L_GLOBAL: { sv_op_global(vm, js); NEXT(1); } + L_OBJECT: { sv_op_object(vm, js, func, ip); NEXT(1); } + L_ARRAY: { sv_op_array(vm, js, ip); NEXT(3); } + L_SET_BRAND: { sv_op_set_brand(vm, ip); NEXT(2); } + + L_REGEXP: { sv_op_regexp(vm, js); NEXT(1); } L_CLOSURE: { VM_CHECK(sv_op_closure(vm, js, frame, func, ip)); NEXT(5); } L_POP: { sv_op_pop(vm); NEXT(1); } diff --git a/src/silver/ops/literals.h b/src/silver/ops/literals.h index 0670773..dd8b457 100644 --- a/src/silver/ops/literals.h +++ b/src/silver/ops/literals.h @@ -86,6 +86,14 @@ static inline void sv_op_array(sv_vm_t *vm, ant_t *js, uint8_t *ip) { vm->stack[vm->sp++] = arr; } +static inline void sv_op_set_brand(sv_vm_t *vm, uint8_t *ip) { + if (vm->sp <= 0) return; + uint8_t brand = sv_get_u8(ip + 1); + ant_value_t obj = vm->stack[vm->sp - 1]; + if (is_object_type(obj)) + js_set_slot(obj, SLOT_BRAND, js_mknum((double)brand)); +} + // TODO: reduce duplication with regex.c static inline void sv_op_regexp(sv_vm_t *vm, ant_t *js) { ant_value_t pattern = vm->stack[vm->sp - 2]; diff --git a/tests/test_array_is_template_object.cjs b/tests/test_array_is_template_object.cjs new file mode 100644 index 0000000..05f3f08 --- /dev/null +++ b/tests/test_array_is_template_object.cjs @@ -0,0 +1,33 @@ +const assert = (cond, msg) => { + if (!cond) throw new Error(msg); +}; + +let captured; +function tag(strings) { + captured = strings; + return strings; +} + +const first = tag`a${1}b`; +const sameTextDifferentSite = tag`a${2}b`; + +function sameSite(value) { + return tag`a${value}b`; +} + +const sameSiteFirst = sameSite(1); +const sameSiteSecond = sameSite(2); + +assert(typeof Array.isTemplateObject === "function", "Array.isTemplateObject should exist"); +assert(Array.isTemplateObject(first) === true, "tagged template strings array should be a template object"); +assert(Array.isTemplateObject(first.raw) === false, "raw array should not be a template object"); +assert(Array.isTemplateObject([]) === false, "ordinary array should not be a template object"); +assert(Array.isTemplateObject(Object.freeze(["a", "b"])) === false, "frozen ordinary array should not be a template object"); +assert(Array.isTemplateObject({ raw: ["a"] }) === false, "ordinary object should not be a template object"); +assert(Array.isTemplateObject("not an array") === false, "primitive should not be a template object"); +assert(Array.isTemplateObject() === false, "missing argument should return false"); +assert(first !== sameTextDifferentSite, "different template sites should not reuse the same object"); +assert(sameSiteFirst === sameSiteSecond, "same template site should reuse the same object"); +assert(captured === sameSiteSecond, "tag should receive cached template object"); + +console.log("Array.isTemplateObject tests ok");