MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

revise entire symbol system

+1406 -222
+2
include/ant.h
··· 75 75 jsval_t js_getcurrentfunc(ant_t *); 76 76 jsval_t js_get(ant_t *, jsval_t, const char *); 77 77 jsval_t js_getprop_proto(ant_t *, jsval_t, const char *); 78 + jsval_t js_getprop_fallback(ant_t *js, jsval_t obj, const char *name); 78 79 79 80 jsoff_t js_arr_len(struct js *js, jsval_t arr); 80 81 jsval_t js_arr_get(struct js *js, jsval_t arr, jsoff_t idx); ··· 94 95 const char *js_sym_desc(ant_t *js, jsval_t sym); 95 96 96 97 jsval_t js_mksym_for(ant_t *, const char *key); 98 + jsval_t js_symbol_to_string(struct js *js, jsval_t sym); 97 99 const char *js_sym_key(jsval_t sym); 98 100 99 101 jsval_t js_mkobj(ant_t *);
+1
include/internal.h
··· 41 41 jsval_t tval; // holds last parsed numeric or string literal value 42 42 jsval_t scope; // current scope 43 43 jsval_t global; // global root object 44 + jsval_t object; // global object prototype 44 45 jsval_t this_val; // 'this' value for currently executing function 45 46 jsval_t super_val; // 'super' value for class methods 46 47 jsval_t new_target; // constructor called with 'new', undefined otherwise
+7 -1
include/modules/symbol.h
··· 14 14 const char *get_asyncIterator_sym_key(void); 15 15 const char *get_toStringTag_sym_key(void); 16 16 const char *get_observable_sym_key(void); 17 - const char *get_symbol_description_from_key(const char *sym_key, size_t key_len); 17 + const char *get_toPrimitive_sym_key(void); 18 + const char *get_hasInstance_sym_key(void); 19 + 20 + const char *get_symbol_description_from_key( 21 + const char *sym_key, 22 + size_t key_len 23 + ); 18 24 19 25 #endif
+439 -169
src/ant.c
··· 4551 4551 *has_getter_out = false; 4552 4552 *getter_out = js_mkundef(); 4553 4553 4554 - jsval_t current = obj; 4555 - while (vtype(current) == T_OBJ || vtype(current) == T_FUNC) { 4554 + for (jsval_t current = obj; is_object_type(current); ) { 4556 4555 jsoff_t current_off = (jsoff_t)vdata(current); 4557 4556 descriptor_entry_t *desc = lookup_descriptor(current_off, buf, len); 4558 4557 ··· 4566 4565 if (prop_off != 0) return prop_off; 4567 4566 4568 4567 jsval_t proto = get_proto(js, current); 4569 - if (vtype(proto) != T_OBJ && vtype(proto) != T_FUNC) break; 4568 + if (!is_object_type(proto)) break; 4570 4569 current = proto; 4571 4570 } 4572 4571 ··· 4627 4626 jsval_t js_get_proto(struct js *js, jsval_t obj) { 4628 4627 uint8_t t = vtype(obj); 4629 4628 4630 - if (t != T_OBJ && t != T_ARR && t != T_FUNC && t != T_PROMISE) return js_mknull(); 4629 + if (!is_object_type(obj)) return js_mknull(); 4631 4630 jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj)); 4632 4631 4633 4632 jsval_t proto = get_slot(js, as_obj, SLOT_PROTO); 4634 - uint8_t pt = vtype(proto); 4635 - if (pt == T_OBJ || pt == T_ARR || pt == T_FUNC) return proto; 4633 + if (is_object_type(proto)) return proto; 4636 4634 4637 - if (t == T_FUNC || t == T_ARR || t == T_PROMISE) return get_prototype_for_type(js, t); 4635 + if (t != T_OBJ) return get_prototype_for_type(js, t); 4638 4636 return js_mknull(); 4639 4637 } 4640 4638 ··· 4644 4642 4645 4643 void js_set_proto(struct js *js, jsval_t obj, jsval_t proto) { 4646 4644 uint8_t t = vtype(obj); 4647 - if (t != T_OBJ && t != T_ARR && t != T_FUNC) return; 4645 + if (!is_object_type(obj)) return; 4648 4646 4649 4647 jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj)); 4650 4648 set_slot(js, as_obj, SLOT_PROTO, proto); ··· 4749 4747 } 4750 4748 4751 4749 return 0; 4750 + } 4751 + 4752 + static jsval_t getprop_any(struct js *js, jsval_t obj, const char *key, size_t key_len) { 4753 + uint8_t t = vtype(obj); 4754 + 4755 + if (t == T_STR && key_len == 6 && memcmp(key, "length", 6) == 0) { 4756 + jsoff_t byte_len; 4757 + jsoff_t str_off = vstr(js, obj, &byte_len); 4758 + return tov(D(utf16_strlen((const char *)&js->mem[str_off], byte_len))); 4759 + } 4760 + 4761 + if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) { 4762 + jsoff_t off = lkp_proto(js, obj, key, key_len); 4763 + if (off != 0) return resolveprop(js, mkval(T_PROP, off)); 4764 + return js_mkundef(); 4765 + } 4766 + 4767 + if (t == T_OBJ || t == T_ARR || t == T_FUNC) { 4768 + jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj)); 4769 + jsoff_t off = lkp(js, as_obj, key, key_len); 4770 + if (off != 0) return resolveprop(js, mkval(T_PROP, off)); 4771 + off = lkp_proto(js, obj, key, key_len); 4772 + if (off != 0) return resolveprop(js, mkval(T_PROP, off)); 4773 + } 4774 + 4775 + return js_mkundef(); 4752 4776 } 4753 4777 4754 4778 static jsval_t try_dynamic_getter(struct js *js, jsval_t obj, const char *key, size_t key_len) { ··· 5355 5379 if (desc) return js_mkstr(js, desc, strlen(desc)); 5356 5380 return js_mkundef(); 5357 5381 } 5382 + 5383 + jsval_t sym_proto = get_ctor_proto(js, "Symbol", 6); 5384 + if (vtype(sym_proto) == T_OBJ) { 5385 + jsoff_t off = lkp(js, sym_proto, ptr, plen); 5386 + if (off != 0) return resolveprop(js, mkval(T_PROP, off)); 5387 + } 5388 + 5358 5389 return js_mkundef(); 5359 5390 } 5360 5391 ··· 5563 5594 5564 5595 jsoff_t var_pos = pos, var_len = name_len; 5565 5596 jsoff_t src_pos = pos, src_len = name_len; 5597 + 5566 5598 pos += name_len; 5567 5599 pos = skiptonext(p, len, pos, NULL); 5600 + 5601 + bool is_nested = false; 5602 + jsoff_t nested_start = 0, nested_len = 0; 5568 5603 5569 5604 if (!is_arr && !is_rest && pos < len && p[pos] == ':') { 5570 5605 pos = skiptonext(p, len, pos + 1, NULL); 5571 - jsoff_t rlen = 0; 5572 - if (parseident(&p[pos], len - pos, &rlen) == TOK_IDENTIFIER) { 5573 - var_pos = pos; var_len = rlen; 5574 - pos += rlen; 5606 + 5607 + if (pos < len && (p[pos] == '{' || p[pos] == '[')) { 5608 + is_nested = true; 5609 + nested_start = pos; 5610 + char open = p[pos], close = (open == '{') ? '}' : ']'; 5611 + int depth = 1; 5612 + pos++; 5613 + while (pos < len && depth > 0) { 5614 + if (p[pos] == open) depth++; 5615 + else if (p[pos] == close) depth--; 5616 + pos++; 5617 + } 5618 + nested_len = pos - nested_start; 5575 5619 pos = skiptonext(p, len, pos, NULL); 5620 + } else { 5621 + jsoff_t rlen = 0; 5622 + if (parseident(&p[pos], len - pos, &rlen) == TOK_IDENTIFIER) { 5623 + var_pos = pos; var_len = rlen; 5624 + pos += rlen; 5625 + pos = skiptonext(p, len, pos, NULL); 5626 + } 5576 5627 } 5577 5628 } 5578 5629 ··· 5583 5634 jsoff_t alen = js_arr_len(js, val); 5584 5635 for (jsoff_t i = idx; i < alen; i++) js_arr_push(js, rest, js_arr_get(js, val, i)); 5585 5636 prop_val = rest; 5586 - } else if (is_rest) { 5587 - prop_val = mkobj(js, 0); 5588 - } else if (is_arr) { 5589 - prop_val = js_arr_get(js, val, idx); 5590 - } else { 5591 - jsoff_t off = lkp(js, val, &p[src_pos], src_len); 5592 - prop_val = off > 0 ? resolveprop(js, mkval(T_PROP, off)) : js_mkundef(); 5637 + } else if (is_rest) prop_val = mkobj(js, 0); 5638 + else if (is_arr) prop_val = js_arr_get(js, val, idx); 5639 + else prop_val = getprop_any(js, val, &p[src_pos], src_len); 5640 + 5641 + if (is_nested) { 5642 + jsval_t r = bind_destruct_pattern(js, &p[nested_start], nested_len, prop_val, scope); 5643 + if (is_err(r)) return r; 5644 + idx++; pos = skiptonext(p, len, pos, NULL); 5645 + if (pos < len && p[pos] == ',') pos++; 5646 + continue; 5593 5647 } 5594 5648 5595 5649 if (is_rest) goto bind; ··· 6494 6548 return res; 6495 6549 } 6496 6550 6497 - static jsval_t js_call_toString(struct js *js, jsval_t value) { 6498 - jsoff_t ts_off = lkp(js, value, "toString", 8); 6499 - if (ts_off == 0) ts_off = lkp_proto(js, value, "toString", 8); 6500 - if (ts_off == 0) goto fallback; 6551 + static bool js_try_call_method(struct js *js, jsval_t obj, const char *method, size_t method_len, jsval_t *args, int nargs, jsval_t *out_result) { 6552 + jsval_t getter = js_mkundef(); bool has_getter = false; 6553 + jsoff_t off = lkp_with_getter(js, obj, method, method_len, &getter, &has_getter); 6501 6554 6502 - jsval_t ts_func = resolveprop(js, mkval(T_PROP, ts_off)); 6503 - uint8_t ft = vtype(ts_func); 6504 - if (ft != T_FUNC && ft != T_CFUNC) goto fallback; 6555 + jsval_t fn; 6556 + if (has_getter) { 6557 + fn = call_proto_accessor(js, obj, getter, true, NULL, 0, false); 6558 + if (is_err(fn)) { *out_result = fn; return true; } 6559 + } else if (off != 0) { 6560 + fn = resolveprop(js, mkval(T_PROP, off)); 6561 + } else return false; 6562 + 6563 + uint8_t ft = vtype(fn); 6564 + if (ft != T_FUNC && ft != T_CFUNC) return false; 6565 + 6566 + js_parse_state_t saved_state; 6567 + JS_SAVE_STATE(js, saved_state); 6568 + uint8_t saved_flags = js->flags; 6505 6569 6506 6570 jsval_t saved_this = js->this_val; 6507 - js->this_val = value; 6571 + js->this_val = obj; 6572 + push_this(obj); 6573 + 6508 6574 jsval_t result; 6575 + if (ft == T_CFUNC) result = ((jsval_t (*)(struct js *, jsval_t *, int))vdata(fn))(js, args, nargs); 6576 + else result = call_js_with_args(js, fn, args, nargs); 6509 6577 6510 - if (ft == T_CFUNC) { 6511 - result = ((jsval_t (*)(struct js *, jsval_t *, int))vdata(ts_func))(js, NULL, 0); 6512 - } else { 6513 - jsval_t func_obj = mkval(T_OBJ, vdata(ts_func)); 6514 - jsoff_t fnlen; 6515 - const char *code_str = get_func_code(js, func_obj, &fnlen); 6516 - if (!code_str) goto restore_fallback; 6517 - 6518 - jsval_t closure_scope = get_slot(js, func_obj, SLOT_SCOPE); 6519 - if (vtype(closure_scope) == T_UNDEF) closure_scope = js->scope; 6520 - 6521 - result = call_js(js, code_str, fnlen, closure_scope); 6522 - } 6578 + bool had_throw = (js->flags & F_THROW) != 0; 6579 + jsval_t thrown = js->thrown_value; 6523 6580 6581 + pop_this(); 6524 6582 js->this_val = saved_this; 6583 + 6584 + JS_RESTORE_STATE(js, saved_state); 6585 + js->flags = saved_flags; 6586 + 6587 + if (had_throw) { 6588 + js->flags |= F_THROW; 6589 + js->thrown_value = thrown; 6590 + } 6591 + 6592 + *out_result = result; 6593 + return true; 6594 + } 6595 + 6596 + static jsval_t js_call_method(struct js *js, jsval_t obj, const char *method, size_t method_len, jsval_t *args, int nargs) { 6597 + jsval_t result; 6598 + if (!js_try_call_method(js, obj, method, method_len, args, nargs, &result)) return js_mkundef(); 6599 + return result; 6600 + } 6601 + 6602 + static jsval_t js_call_toString(struct js *js, jsval_t value) { 6603 + jsval_t result = js_call_method(js, value, "toString", 8, NULL, 0); 6604 + 6605 + if (is_err(result)) return result; 6525 6606 if (vtype(result) == T_STR) return result; 6526 6607 6527 6608 uint8_t rtype = vtype(result); 6609 + if (rtype == T_UNDEF) { 6610 + goto fallback; 6611 + } 6612 + 6528 6613 if (rtype != T_OBJ && rtype != T_ARR && rtype != T_FUNC) { 6529 6614 char buf[256]; 6530 6615 size_t len = tostr(js, result, buf, sizeof(buf)); 6531 6616 return js_mkstr(js, buf, len); 6532 6617 } 6533 6618 6534 - restore_fallback: 6535 - js->this_val = saved_this; 6536 6619 fallback:; 6537 6620 char buf[4096]; 6538 6621 size_t len = tostr(js, value, buf, sizeof(buf)); ··· 6540 6623 } 6541 6624 6542 6625 static jsval_t js_call_valueOf(struct js *js, jsval_t value) { 6543 - jsoff_t off = lkp(js, value, "valueOf", 7); 6544 - if (off == 0) off = lkp_proto(js, value, "valueOf", 7); 6545 - if (off == 0) return value; 6626 + jsval_t result = js_call_method(js, value, "valueOf", 7, NULL, 0); 6627 + if (vtype(result) == T_UNDEF) return value; 6628 + return result; 6629 + } 6630 + 6631 + static inline bool is_primitive(jsval_t v) { 6632 + uint8_t t = vtype(v); 6633 + return t == T_STR || t == T_NUM || t == T_BOOL || t == T_NULL || t == T_UNDEF || t == T_SYMBOL || t == T_BIGINT; 6634 + } 6635 + 6636 + static jsval_t try_exotic_to_primitive(struct js *js, jsval_t value, int hint) { 6637 + const char *tp_key = get_toPrimitive_sym_key(); 6638 + if (!tp_key || !tp_key[0]) return mkval(T_UNDEF, 0); 6546 6639 6547 - jsval_t fn = resolveprop(js, mkval(T_PROP, off)); 6548 - uint8_t ft = vtype(fn); 6549 - if (ft != T_FUNC && ft != T_CFUNC) return value; 6640 + jsoff_t tp_off = lkp(js, value, tp_key, strlen(tp_key)); 6641 + if (tp_off == 0) tp_off = lkp_proto(js, value, tp_key, strlen(tp_key)); 6642 + if (tp_off == 0) return mkval(T_UNDEF, 0); 6550 6643 6551 - jsval_t saved = js->this_val; 6552 - js->this_val = value; 6644 + jsval_t tp_fn = resolveprop(js, mkval(T_PROP, tp_off)); 6645 + uint8_t ft = vtype(tp_fn); 6646 + 6647 + if (ft == T_UNDEF) return mkval(T_UNDEF, 0); 6648 + if (ft != T_FUNC && ft != T_CFUNC) { 6649 + return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol.toPrimitive is not a function"); 6650 + } 6651 + 6652 + const char *hint_str = hint == 1 ? "string" : (hint == 2 ? "number" : "default"); 6653 + jsval_t hint_arg = js_mkstr(js, hint_str, strlen(hint_str)); 6654 + jsval_t result = js_call_method(js, value, tp_key, strlen(tp_key), &hint_arg, 1); 6655 + 6656 + if (is_err(result) || is_primitive(result)) return result; 6657 + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert object to primitive value"); 6658 + } 6659 + 6660 + static jsval_t try_ordinary_to_primitive(struct js *js, jsval_t value, int hint) { 6661 + static const char *names[] = {"valueOf", "toString"}; 6662 + static const size_t lens[] = {7, 8}; 6663 + 6664 + int first = (hint == 1); 6553 6665 jsval_t result; 6554 6666 6555 - if (ft == T_CFUNC) { 6556 - result = ((jsval_t (*)(struct js *, jsval_t *, int))vdata(fn))(js, NULL, 0); 6557 - } else { 6558 - jsval_t func_obj = mkval(T_OBJ, vdata(fn)); 6559 - jsoff_t fnlen; 6560 - const char *code_str = get_func_code(js, func_obj, &fnlen); 6561 - if (!code_str) { js->this_val = saved; return value; } 6562 - 6563 - jsval_t closure_scope = get_slot(js, func_obj, SLOT_SCOPE); 6564 - if (vtype(closure_scope) == T_UNDEF) closure_scope = js->scope; 6565 - 6566 - result = call_js(js, code_str, fnlen, closure_scope); 6667 + for (int i = 0; i < 2; i++) { 6668 + int idx = first ^ i; 6669 + if (js_try_call_method(js, value, names[idx], lens[idx], NULL, 0, &result)) 6670 + if (is_err(result) || is_primitive(result)) return result; 6567 6671 } 6568 6672 6569 - js->this_val = saved; 6570 - return result; 6673 + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert object to primitive value"); 6674 + } 6675 + 6676 + static jsval_t js_to_primitive(struct js *js, jsval_t value, int hint) { 6677 + if (is_primitive(value)) return value; 6678 + if (!is_object_type(value)) return value; 6679 + 6680 + jsval_t result = try_exotic_to_primitive(js, value, hint); 6681 + if (vtype(result) != T_UNDEF) return result; 6682 + 6683 + return try_ordinary_to_primitive(js, value, hint); 6571 6684 } 6572 6685 6573 6686 static inline bool strict_eq_values(struct js *js, jsval_t l, jsval_t r) { ··· 6584 6697 6585 6698 static inline jsval_t coerce_to_str(struct js *js, jsval_t v) { 6586 6699 if (vtype(v) == T_STR) return v; 6587 - if (vtype(v) == T_ARR) { 6588 - char buf[1024]; 6589 - size_t len = array_to_string(js, v, buf, sizeof(buf)); 6590 - return js_mkstr(js, buf, len); 6700 + 6701 + if (is_object_type(v)) { 6702 + jsval_t prim = js_to_primitive(js, v, 1); 6703 + if (is_err(prim)) return prim; 6704 + if (vtype(prim) == T_STR) return prim; 6705 + return js_tostring_val(js, prim); 6706 + } 6707 + 6708 + return js_tostring_val(js, v); 6709 + } 6710 + 6711 + static inline jsval_t coerce_to_str_concat(struct js *js, jsval_t v) { 6712 + if (vtype(v) == T_STR) return v; 6713 + 6714 + if (is_object_type(v)) { 6715 + jsval_t prim = js_to_primitive(js, v, 0); 6716 + if (is_err(prim)) return prim; 6717 + if (vtype(prim) == T_STR) return prim; 6718 + return js_tostring_val(js, prim); 6591 6719 } 6720 + 6592 6721 return js_tostring_val(js, v); 6593 6722 } 6594 6723 ··· 6660 6789 6661 6790 L_TOK_UMINUS: 6662 6791 if (vtype(r) == T_BIGINT) return bigint_neg(js, r); 6792 + if (is_object_type(r)) { 6793 + jsval_t prim = js_to_primitive(js, r, 2); 6794 + if (is_err(prim)) return prim; 6795 + return tov(-js_to_number(js, prim)); 6796 + } 6663 6797 return tov(-js_to_number(js, r)); 6664 6798 6665 6799 L_TOK_UPLUS: 6666 6800 if (vtype(r) == T_BIGINT) return js_mkerr(js, "Cannot convert a BigInt value to a number"); 6801 + if (is_object_type(r)) { 6802 + jsval_t prim = js_to_primitive(js, r, 2); 6803 + if (is_err(prim)) return prim; 6804 + return tov(js_to_number(js, prim)); 6805 + } 6667 6806 return tov(js_to_number(js, r)); 6668 6807 6669 6808 L_TOK_TILDA: return tov((double)(~js_to_int32(js_to_number(js, r)))); ··· 6719 6858 if (vtype(lu) == T_BIGINT && vtype(ru) == T_BIGINT) return bigint_add(js, lu, ru); 6720 6859 if (vtype(lu) == T_BIGINT || vtype(ru) == T_BIGINT) return js_mkerr(js, "Cannot mix BigInt value and other types"); 6721 6860 if (is_non_numeric(lu) || is_non_numeric(ru) || (vtype(lu) == T_STR && vtype(ru) == T_STR)) { 6722 - jsval_t l_str = coerce_to_str(js, l); 6861 + jsval_t l_str = coerce_to_str_concat(js, l); 6723 6862 if (is_err(l_str)) return l_str; 6724 - jsval_t r_str = coerce_to_str(js, r); 6863 + jsval_t r_str = coerce_to_str_concat(js, r); 6725 6864 if (is_err(r_str)) return r_str; 6726 6865 return do_string_op(js, op, l_str, r_str); 6727 6866 } ··· 6890 7029 if (is_err(expr_result)) return expr_result; 6891 7030 expr_result = resolveprop(js, expr_result); 6892 7031 7032 + if (vtype(expr_result) == T_SYMBOL) { 7033 + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert a Symbol value to a string"); 7034 + } 7035 + 6893 7036 if (vtype(expr_result) != T_STR) { 6894 - const char *str = js_str(js, expr_result); 6895 - expr_result = js_mkstr(js, str, strlen(str)); 7037 + expr_result = coerce_to_str(js, expr_result); 7038 + if (is_err(expr_result)) return expr_result; 6896 7039 } 6897 7040 6898 7041 parts[part_count++] = expr_result; ··· 9093 9236 jsval_t obj = js_mkundef(); 9094 9237 if (exe) { 9095 9238 obj = resolveprop(js, v); 9096 - if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR) { 9097 - return js_mkerr(js, "cannot destructure non-object"); 9098 - } 9239 + uint8_t ot = vtype(obj); 9240 + if (ot == T_NULL || ot == T_UNDEF) return js_mkerr(js, "cannot destructure null or undefined"); 9099 9241 } 9100 9242 9101 9243 js_parse_state_t end_state; ··· 9176 9318 obj_destruct_rest:; 9177 9319 jsval_t rest_obj = mkobj(js, 0); 9178 9320 if (is_err(rest_obj)) return rest_obj; 9179 - jsoff_t scan = loadoff(js, (jsoff_t) vdata(obj)) & ~(3U | FLAGMASK); 9180 - while (scan < js->brk && scan != 0) { 9181 - jsoff_t header = loadoff(js, scan); 9182 - if (is_slot_prop(header)) { scan = next_prop(header); continue; } 9183 - 9184 - const char *key; jsoff_t klen; 9185 - get_prop_key(js, scan, &key, &klen); 9186 - bool is_picked = false; 9187 - jsoff_t picked_len = js_arr_len(js, picked_keys); 9188 - for (jsoff_t i = 0; i < picked_len; i++) { 9189 - jsval_t pk = js_arr_get(js, picked_keys, i); 9190 - if (vtype(pk) != T_STR) continue; 9191 - jsoff_t pklen, pkoff = vstr(js, pk, &pklen); 9192 - if (klen == pklen && memcmp(key, &js->mem[pkoff], klen) == 0) { is_picked = true; break; } 9321 + uint8_t obj_type = vtype(obj); 9322 + if (obj_type == T_OBJ || obj_type == T_ARR) { 9323 + jsoff_t scan = loadoff(js, (jsoff_t) vdata(obj)) & ~(3U | FLAGMASK); 9324 + while (scan < js->brk && scan != 0) { 9325 + jsoff_t header = loadoff(js, scan); 9326 + if (is_slot_prop(header)) { scan = next_prop(header); continue; } 9327 + 9328 + const char *key; jsoff_t klen; 9329 + get_prop_key(js, scan, &key, &klen); 9330 + bool is_picked = false; 9331 + jsoff_t picked_len = js_arr_len(js, picked_keys); 9332 + for (jsoff_t i = 0; i < picked_len; i++) { 9333 + jsval_t pk = js_arr_get(js, picked_keys, i); 9334 + if (vtype(pk) != T_STR) continue; 9335 + jsoff_t pklen, pkoff = vstr(js, pk, &pklen); 9336 + if (klen == pklen && memcmp(key, &js->mem[pkoff], klen) == 0) { is_picked = true; break; } 9337 + } 9338 + if (!is_picked && !(klen == STR_PROTO_LEN && memcmp(key, STR_PROTO, STR_PROTO_LEN) == 0)) { 9339 + jsval_t val = get_prop_val(js, scan); 9340 + jsval_t key_str = js_mkstr(js, key, klen); 9341 + if (is_err(key_str)) return key_str; 9342 + jsval_t res = setprop(js, rest_obj, key_str, val); 9343 + if (is_err(res)) return res; 9344 + } 9345 + scan = next_prop(header); 9193 9346 } 9194 - if (!is_picked && !(klen == STR_PROTO_LEN && memcmp(key, STR_PROTO, STR_PROTO_LEN) == 0)) { 9195 - jsval_t val = get_prop_val(js, scan); 9196 - jsval_t key_str = js_mkstr(js, key, klen); 9197 - if (is_err(key_str)) return key_str; 9198 - jsval_t res = setprop(js, rest_obj, key_str, val); 9199 - if (is_err(res)) return res; 9200 - } 9201 - scan = next_prop(header); 9202 9347 } 9203 9348 { 9204 9349 const char *vn = &js->code[var_off]; ··· 9214 9359 if (is_err(sk)) return sk; 9215 9360 js_arr_push(js, picked_keys, sk); 9216 9361 9217 - jsoff_t poff = lkp(js, obj, &js->code[src_off], src_len); 9218 - jsval_t nobj = poff > 0 ? resolveprop(js, mkval(T_PROP, poff)) : js_mkundef(); 9219 - 9362 + jsval_t nobj = getprop_any(js, obj, &js->code[src_off], src_len); 9220 9363 jsoff_t pattern_end = js->pos; 9364 + 9221 9365 js->pos = nested_pattern_start; 9222 9366 js->consumed = 1; 9223 9367 ··· 9244 9388 const char *ivn = &js->code[ivoff]; 9245 9389 if (lkp_scope(js, js->scope, ivn, ivlen) > 0) return js_mkerr(js, "'%.*s' already declared", (int)ivlen, ivn); 9246 9390 9247 - jsoff_t ipoff = lkp(js, nobj, &js->code[isoff], islen); 9248 - jsval_t ival = ipoff > 0 ? resolveprop(js, mkval(T_PROP, ipoff)) : js_mkundef(); 9391 + jsval_t ival = getprop_any(js, nobj, &js->code[isoff], islen); 9249 9392 jsval_t ix = mkprop(js, js->scope, js_mkstr(js, ivn, ivlen), ival, is_const ? CONSTMASK : 0); 9250 9393 if (is_err(ix)) return ix; 9251 9394 ··· 9269 9412 jsval_t sk = js_mkstr(js, &js->code[src_off], src_len); 9270 9413 if (is_err(sk)) return sk; 9271 9414 js_arr_push(js, picked_keys, sk); 9272 - 9273 - jsoff_t poff = lkp(js, obj, &js->code[src_off], src_len); 9274 - jsval_t pval = poff > 0 ? resolveprop(js, mkval(T_PROP, poff)) : js_mkundef(); 9415 + jsval_t pval = getprop_any(js, obj, &js->code[src_off], src_len); 9275 9416 9276 9417 if (vtype(pval) == T_UNDEF && default_len > 0) { 9277 9418 pval = js_eval_slice(js, default_off, default_len); ··· 11909 12050 return js_stmt_impl(js); 11910 12051 } 11911 12052 12053 + jsval_t js_symbol_to_string(struct js *js, jsval_t sym) { 12054 + const char *desc = js_sym_desc(js, sym); 12055 + if (!desc) return js_mkstr(js, "Symbol()", 8); 12056 + 12057 + size_t desc_len = strlen(desc); 12058 + size_t total = 7 + desc_len + 1; 12059 + char *buf = malloc(total + 1); 12060 + if (!buf) return js_mkerr(js, "out of memory"); 12061 + 12062 + memcpy(buf, "Symbol(", 7); 12063 + memcpy(buf + 7, desc, desc_len); 12064 + buf[7 + desc_len] = ')'; 12065 + buf[total] = '\0'; 12066 + 12067 + jsval_t result = js_mkstr(js, buf, total); 12068 + free(buf); 12069 + return result; 12070 + } 12071 + 11912 12072 static jsval_t builtin_String(struct js *js, jsval_t *args, int nargs) { 11913 12073 jsval_t sval; 12074 + 11914 12075 if (nargs == 0) { 11915 12076 sval = js_mkstr(js, "", 0); 12077 + } else if (vtype(args[0]) == T_STR) { 12078 + sval = args[0]; 12079 + } else if (vtype(args[0]) == T_SYMBOL) { 12080 + sval = js_symbol_to_string(js, args[0]); 12081 + if (is_err(sval)) return sval; 11916 12082 } else { 11917 - jsval_t arg = args[0]; 11918 - if (vtype(arg) == T_STR) { 11919 - sval = arg; 11920 - } else { 11921 - const char *str = js_str(js, arg); 11922 - sval = js_mkstr(js, str, strlen(str)); 11923 - } 12083 + sval = coerce_to_str(js, args[0]); 12084 + if (is_err(sval)) return sval; 11924 12085 } 12086 + 11925 12087 jsval_t string_proto = js_get_ctor_proto(js, "String", 6); 11926 12088 if (is_unboxed_obj(js, js->this_val, string_proto)) { 11927 12089 set_slot(js, js->this_val, SLOT_PRIMITIVE, sval); ··· 12583 12745 return this_val; 12584 12746 } 12585 12747 12748 + static jsval_t builtin_Error_toString(struct js *js, jsval_t *args, int nargs) { 12749 + jsval_t this_val = js_getthis(js); 12750 + 12751 + jsval_t name = js_get(js, this_val, "name"); 12752 + if (vtype(name) == T_UNDEF) name = js_mkstr(js, "Error", 5); 12753 + else if (vtype(name) != T_STR) { 12754 + const char *s = js_str(js, name); 12755 + name = js_mkstr(js, s, strlen(s)); 12756 + } 12757 + 12758 + jsval_t msg = js_get(js, this_val, "message"); 12759 + if (vtype(msg) == T_UNDEF) msg = js_mkstr(js, "", 0); 12760 + else if (vtype(msg) != T_STR) { 12761 + const char *s = js_str(js, msg); 12762 + msg = js_mkstr(js, s, strlen(s)); 12763 + } 12764 + 12765 + jsoff_t name_len, msg_len; 12766 + jsoff_t name_off = vstr(js, name, &name_len); 12767 + jsoff_t msg_off = vstr(js, msg, &msg_len); 12768 + 12769 + const char *name_str = (const char *)&js->mem[name_off]; 12770 + const char *msg_str = (const char *)&js->mem[msg_off]; 12771 + 12772 + if (name_len == 0) return msg; 12773 + if (msg_len == 0) return name; 12774 + 12775 + size_t total = name_len + 2 + msg_len; 12776 + char *buf = malloc(total + 1); 12777 + if (!buf) return js_mkerr(js, "out of memory"); 12778 + 12779 + memcpy(buf, name_str, name_len); 12780 + buf[name_len] = ':'; buf[name_len + 1] = ' '; 12781 + memcpy(buf + name_len + 2, msg_str, msg_len); 12782 + buf[total] = '\0'; 12783 + 12784 + jsval_t result = js_mkstr(js, buf, total); 12785 + free(buf); return result; 12786 + } 12787 + 12586 12788 static jsval_t builtin_AggregateError(struct js *js, jsval_t *args, int nargs) { 12587 12789 bool is_new = (vtype(js->new_target) != T_UNDEF); 12588 12790 jsval_t this_val = js->this_val; ··· 13146 13348 } 13147 13349 13148 13350 static jsval_t builtin_Date_toString(struct js *js, jsval_t *args, int nargs) { 13149 - (void) args; 13150 - (void) nargs; 13151 13351 double ms = date_get_time(js, js->this_val); 13152 13352 if (isnan(ms)) return js_mkstr(js, "Invalid Date", 12); 13353 + 13153 13354 time_t t = (time_t)(ms / 1000.0); 13154 - char *s = ctime(&t); 13155 - size_t len = strlen(s); 13156 - if (len > 0 && s[len - 1] == '\n') len--; 13157 - return js_mkstr(js, s, len); 13355 + struct tm *tm_local = localtime(&t); 13356 + if (!tm_local) return js_mkstr(js, "Invalid Date", 12); 13357 + 13358 + struct tm local_copy = *tm_local; 13359 + struct tm *gm = gmtime(&t); 13360 + long offset_sec = (long)difftime(mktime(&local_copy), mktime(gm)); 13361 + int offset_hours = (int)(offset_sec / 3600); 13362 + int offset_mins = (int)(labs(offset_sec) % 3600) / 60; 13363 + 13364 + char tz[32], buf[80]; 13365 + strftime(tz, sizeof(tz), "%Z", tm_local); 13366 + strftime(buf, sizeof(buf), "%a %b %d %Y %H:%M:%S", tm_local); 13367 + size_t n = strlen(buf); 13368 + n += snprintf(buf + n, sizeof(buf) - n, " GMT%+03d%02d (%s)", offset_hours, offset_mins, tz); 13369 + return js_mkstr(js, buf, n); 13158 13370 } 13159 13371 13160 13372 static jsval_t builtin_Date_valueOf(struct js *js, jsval_t *args, int nargs) { ··· 13987 14199 uint8_t t = vtype(obj); 13988 14200 13989 14201 if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) return get_prototype_for_type(js, t); 13990 - if (t == T_OBJ || t == T_ARR || t == T_FUNC) return get_proto(js, obj); 14202 + if (is_object_type(obj)) return get_proto(js, obj); 13991 14203 13992 14204 return js_mknull(); 13993 14205 } ··· 14649 14861 return mkval(T_ARR, vdata(arr)); 14650 14862 } 14651 14863 14864 + // refactor with desc when SLOT_SYMBOL 14865 + static jsval_t builtin_object_getOwnPropertySymbols(struct js *js, jsval_t *args, int nargs) { 14866 + if (nargs == 0) return mkarr(js); 14867 + jsval_t obj = args[0]; 14868 + 14869 + uint8_t t = vtype(obj); 14870 + if (t != T_OBJ && t != T_ARR && t != T_FUNC) return mkarr(js); 14871 + if (t == T_FUNC) obj = mkval(T_OBJ, vdata(obj)); 14872 + 14873 + jsval_t arr = mkarr(js); jsoff_t idx = 0; 14874 + jsoff_t next = loadoff(js, (jsoff_t)vdata(obj)) & ~(3U | FLAGMASK); 14875 + 14876 + while (next < js->brk && next != 0) { 14877 + jsoff_t header = loadoff(js, next); 14878 + if (!is_slot_prop(header)) { 14879 + jsoff_t koff = loadoff(js, next + (jsoff_t)sizeof(next)); 14880 + jsoff_t klen = offtolen(loadoff(js, koff)); 14881 + const char *key = (char *)&js->mem[koff + sizeof(koff)]; 14882 + 14883 + if (klen >= 9 && memcmp(key, "__sym_", 6) == 0 && memcmp(key + klen - 2, "__", 2) == 0) { 14884 + uint64_t sym_id = 0; 14885 + for (const char *p = key + 6; *p >= '0' && *p <= '9'; p++) sym_id = sym_id * 10 + (uint64_t)(*p - '0'); 14886 + char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)idx++); 14887 + js_mkprop_fast(js, arr, idxstr, idxlen, mkval(T_SYMBOL, (sym_id & PROPREF_PAYLOAD) << PROPREF_KEY_SHIFT)); 14888 + } 14889 + } 14890 + next = next_prop(header); 14891 + } 14892 + 14893 + js_mkprop_fast(js, arr, "length", 6, tov((double)idx)); 14894 + return mkval(T_ARR, vdata(arr)); 14895 + } 14896 + 14652 14897 static jsval_t builtin_object_isExtensible(struct js *js, jsval_t *args, int nargs) { 14653 14898 if (nargs == 0) return js_true; 14654 14899 ··· 14771 15016 obj = resolveprop(js, obj); 14772 15017 uint8_t t = vtype(obj); 14773 15018 14774 - if (t == T_OBJ || t == T_ARR || t == T_FUNC) { 14775 - jsval_t check_obj = (t == T_FUNC) ? mkval(T_OBJ, vdata(obj)) : obj; 15019 + if (is_object_type(obj)) { 15020 + jsval_t check_obj = (t != T_OBJ) ? mkval(T_OBJ, vdata(obj)) : obj; 14776 15021 const char *tostr_tag_key = get_toStringTag_sym_key(); 14777 15022 jsoff_t tag_off = lkp(js, check_obj, tostr_tag_key, strlen(tostr_tag_key)); 14778 15023 if (tag_off == 0) tag_off = lkp_proto(js, check_obj, tostr_tag_key, strlen(tostr_tag_key)); ··· 14792 15037 const char *type_name = NULL; 14793 15038 14794 15039 switch (t) { 14795 - case T_UNDEF: type_name = "Undefined"; break; 14796 - case T_NULL: type_name = "Null"; break; 14797 - case T_BOOL: type_name = "Boolean"; break; 14798 - case T_NUM: type_name = "Number"; break; 14799 - case T_STR: type_name = "String"; break; 14800 - case T_ARR: type_name = "Array"; break; 14801 - case T_FUNC: type_name = "Function"; break; 14802 - case T_ERR: type_name = "Error"; break; 14803 - case T_BIGINT: type_name = "BigInt"; break; 14804 - case T_OBJ: type_name = "Object"; break; 14805 - default: type_name = "Unknown"; break; 15040 + case T_UNDEF: type_name = "Undefined"; break; 15041 + case T_NULL: type_name = "Null"; break; 15042 + case T_BOOL: type_name = "Boolean"; break; 15043 + case T_NUM: type_name = "Number"; break; 15044 + case T_STR: type_name = "String"; break; 15045 + case T_ARR: type_name = "Array"; break; 15046 + case T_FUNC: type_name = "Function"; break; 15047 + case T_ERR: type_name = "Error"; break; 15048 + case T_BIGINT: type_name = "BigInt"; break; 15049 + case T_PROMISE: type_name = "Promise"; break; 15050 + case T_OBJ: type_name = "Object"; break; 15051 + default: type_name = "Unknown"; break; 14806 15052 } 14807 15053 14808 15054 char buf[256]; ··· 15057 15303 15058 15304 if (elem_off != 0) { 15059 15305 jsval_t elem = resolveprop(js, mkval(T_PROP, elem_off)); 15060 - if (vtype(elem) == T_STR) { 15061 - jsoff_t elem_len, elem_off_str = vstr(js, elem, &elem_len); 15306 + uint8_t et = vtype(elem); 15307 + if (et == T_NULL || et == T_UNDEF) continue; 15308 + 15309 + const char *elem_str = NULL; 15310 + size_t elem_len = 0; 15311 + char numstr[64]; 15312 + jsval_t str_val = js_mkundef(); 15313 + 15314 + if (et == T_STR) { 15315 + jsoff_t soff, slen; 15316 + soff = vstr(js, elem, &slen); 15317 + elem_str = (const char *)&js->mem[soff]; 15318 + elem_len = slen; 15319 + } else if (et == T_NUM) { 15320 + snprintf(numstr, sizeof(numstr), "%g", tod(elem)); 15321 + elem_str = numstr; 15322 + elem_len = strlen(numstr); 15323 + } else if (et == T_BOOL) { 15324 + elem_str = vdata(elem) ? "true" : "false"; 15325 + elem_len = strlen(elem_str); 15326 + } else if (et == T_ARR || et == T_OBJ || et == T_FUNC || et == T_BIGINT) { 15327 + str_val = to_string_val(js, elem); 15328 + 15329 + if (is_err(str_val)) { 15330 + free(result); 15331 + return str_val; 15332 + } 15333 + 15334 + if (vtype(str_val) == T_STR) { 15335 + jsoff_t soff, slen; 15336 + soff = vstr(js, str_val, &slen); 15337 + elem_str = (const char *)&js->mem[soff]; 15338 + elem_len = slen; 15339 + } 15340 + } 15341 + 15342 + 15343 + if (elem_str && elem_len > 0) { 15062 15344 if (result_len + elem_len >= capacity) { 15063 15345 capacity = (result_len + elem_len + 1) * 2; 15064 15346 char *new_result = (char *)ant_realloc(result, capacity); 15065 - if (!new_result) return js_mkerr(js, "oom"); 15347 + if (!new_result) { free(result); return js_mkerr(js, "oom"); } 15066 15348 result = new_result; 15067 15349 } 15068 - memcpy(result + result_len, &js->mem[elem_off_str], elem_len); 15350 + memcpy(result + result_len, elem_str, elem_len); 15069 15351 result_len += elem_len; 15070 - } else if (vtype(elem) == T_NUM) { 15071 - char numstr[32]; 15072 - snprintf(numstr, sizeof(numstr), "%g", tod(elem)); 15073 - size_t num_len = strlen(numstr); 15074 - if (result_len + num_len >= capacity) { 15075 - capacity = (result_len + num_len + 1) * 2; 15076 - char *new_result = (char *)ant_realloc(result, capacity); 15077 - if (!new_result) return js_mkerr(js, "oom"); 15078 - result = new_result; 15079 - } 15080 - memcpy(result + result_len, numstr, num_len); 15081 - result_len += num_len; 15082 - } else if (vtype(elem) == T_BOOL) { 15083 - const char *boolstr = vdata(elem) ? "true" : "false"; 15084 - size_t bool_len = strlen(boolstr); 15085 - if (result_len + bool_len >= capacity) { 15086 - capacity = (result_len + bool_len + 1) * 2; 15087 - char *new_result = (char *)ant_realloc(result, capacity); 15088 - if (!new_result) return js_mkerr(js, "oom"); 15089 - result = new_result; 15090 - } 15091 - memcpy(result + result_len, boolstr, bool_len); 15092 - result_len += bool_len; 15093 15352 } 15094 15353 } 15095 15354 } 15355 + 15096 15356 jsval_t ret = js_mkstr(js, result, result_len); 15097 - free(result); 15098 - return ret; 15357 + free(result); return ret; 15099 15358 } 15100 15359 15101 15360 static jsval_t builtin_array_includes(struct js *js, jsval_t *args, int nargs) { ··· 16540 16799 } 16541 16800 16542 16801 static jsval_t builtin_array_toString(struct js *js, jsval_t *args, int nargs) { 16543 - (void) args; 16544 - (void) nargs; 16545 16802 jsval_t arr = js->this_val; 16546 - jsval_t sep_args[1] = { js_mkstr(js, ",", 1) }; 16547 - jsval_t old_this = js->this_val; 16548 - js->this_val = arr; 16549 - jsval_t result = builtin_array_join(js, sep_args, 1); 16550 - js->this_val = old_this; 16551 - return result; 16803 + 16804 + jsval_t join_result; 16805 + if (js_try_call_method(js, arr, "join", 4, NULL, 0, &join_result)) { 16806 + if (is_err(join_result)) return join_result; 16807 + return join_result; 16808 + } 16809 + 16810 + return js_mkstr(js, "[object Object]", 15); 16552 16811 } 16553 16812 16554 16813 static jsval_t builtin_array_toLocaleString(struct js *js, jsval_t *args, int nargs) { ··· 20770 21029 set_proto(js, error_proto, object_proto); 20771 21030 setprop(js, error_proto, ANT_STRING("name"), ANT_STRING("Error")); 20772 21031 setprop(js, error_proto, ANT_STRING("message"), js_mkstr(js, "", 0)); 21032 + setprop(js, error_proto, js_mkstr(js, "toString", 8), js_mkfun(builtin_Error_toString)); 20773 21033 20774 21034 jsval_t err_ctor_obj = mkobj(js, 0); 20775 21035 set_proto(js, err_ctor_obj, function_proto); ··· 20875 21135 setprop(js, promise_proto, js_mkstr(js, "then", 4), js_mkfun(builtin_promise_then)); 20876 21136 setprop(js, promise_proto, js_mkstr(js, "catch", 5), js_mkfun(builtin_promise_catch)); 20877 21137 setprop(js, promise_proto, js_mkstr(js, "finally", 7), js_mkfun(builtin_promise_finally)); 21138 + // Symbol.toStringTag is set in init_symbol_module after symbols are initialized 20878 21139 20879 21140 jsval_t obj_func_obj = mkobj(js, 0); 20880 21141 set_proto(js, obj_func_obj, function_proto); ··· 20896 21157 setprop(js, obj_func_obj, js_mkstr(js, "fromEntries", 11), js_mkfun(builtin_object_fromEntries)); 20897 21158 setprop(js, obj_func_obj, js_mkstr(js, "getOwnPropertyDescriptor", 24), js_mkfun(builtin_object_getOwnPropertyDescriptor)); 20898 21159 setprop(js, obj_func_obj, js_mkstr(js, "getOwnPropertyNames", 19), js_mkfun(builtin_object_getOwnPropertyNames)); 21160 + setprop(js, obj_func_obj, js_mkstr(js, "getOwnPropertySymbols", 21), js_mkfun(builtin_object_getOwnPropertySymbols)); 20899 21161 setprop(js, obj_func_obj, js_mkstr(js, "isExtensible", 12), js_mkfun(builtin_object_isExtensible)); 20900 21162 setprop(js, obj_func_obj, js_mkstr(js, "preventExtensions", 17), js_mkfun(builtin_object_preventExtensions)); 20901 21163 setprop(js, obj_func_obj, ANT_STRING("name"), ANT_STRING("Object")); ··· 21115 21377 21116 21378 set_proto(js, glob, object_proto); 21117 21379 21380 + js->object = object_proto; 21118 21381 js->owns_mem = false; 21119 21382 js->max_size = 0; 21120 21383 ··· 21338 21601 size_t key_len = strlen(key); 21339 21602 jsoff_t off = lkp_proto(js, obj, key, key_len); 21340 21603 return off == 0 ? js_mkundef() : resolveprop(js, mkval(T_PROP, off)); 21604 + } 21605 + 21606 + jsval_t js_getprop_fallback(struct js *js, jsval_t obj, const char *name) { 21607 + jsval_t val = js_get(js, obj, name); 21608 + if (vtype(val) == T_UNDEF) { 21609 + val = js_getprop_proto(js, obj, name); 21610 + } return val; 21341 21611 } 21342 21612 21343 21613 typedef struct {
+10 -2
src/modules/buffer.c
··· 1659 1659 } 1660 1660 1661 1661 void init_buffer_module() { 1662 - struct js *js = rt->js; 1663 - jsval_t glob = js_glob(js); 1662 + ant_t *js = rt->js; 1664 1663 1664 + jsval_t glob = js->global; 1665 + jsval_t object_proto = js->object; 1666 + 1665 1667 jsval_t arraybuffer_ctor_obj = js_mkobj(js); 1666 1668 jsval_t arraybuffer_proto = js_mkobj(js); 1669 + js_set_proto(js, arraybuffer_proto, object_proto); 1667 1670 1668 1671 js_set(js, arraybuffer_proto, "slice", js_mkfun(js_arraybuffer_slice)); 1669 1672 js_set(js, arraybuffer_proto, "transfer", js_mkfun(js_arraybuffer_transfer)); ··· 1678 1681 js_set(js, glob, "ArrayBuffer", js_obj_to_func(arraybuffer_ctor_obj)); 1679 1682 1680 1683 jsval_t typedarray_proto = js_mkobj(js); 1684 + js_set_proto(js, typedarray_proto, object_proto); 1685 + 1681 1686 js_set(js, typedarray_proto, "slice", js_mkfun(js_typedarray_slice)); 1682 1687 js_set(js, typedarray_proto, "subarray", js_mkfun(js_typedarray_subarray)); 1683 1688 js_set(js, typedarray_proto, "fill", js_mkfun(js_typedarray_fill)); ··· 1710 1715 1711 1716 jsval_t dataview_ctor_obj = js_mkobj(js); 1712 1717 jsval_t dataview_proto = js_mkobj(js); 1718 + js_set_proto(js, dataview_proto, object_proto); 1713 1719 1714 1720 js_set(js, dataview_proto, "getUint8", js_mkfun(js_dataview_getUint8)); 1715 1721 js_set(js, dataview_proto, "setUint8", js_mkfun(js_dataview_setUint8)); ··· 1731 1737 1732 1738 jsval_t sharedarraybuffer_ctor_obj = js_mkobj(js); 1733 1739 jsval_t sharedarraybuffer_proto = js_mkobj(js); 1740 + js_set_proto(js, sharedarraybuffer_proto, object_proto); 1734 1741 1735 1742 js_set(js, sharedarraybuffer_proto, "slice", js_mkfun(js_arraybuffer_slice)); 1736 1743 js_set(js, sharedarraybuffer_proto, get_toStringTag_sym_key(), js_mkstr(js, "SharedArrayBuffer", 17)); ··· 1743 1750 1744 1751 jsval_t buffer_ctor_obj = js_mkobj(js); 1745 1752 jsval_t buffer_proto = js_mkobj(js); 1753 + js_set_proto(js, buffer_proto, object_proto); 1746 1754 1747 1755 js_set(js, buffer_proto, "toString", js_mkfun(js_buffer_toString)); 1748 1756 js_set(js, buffer_proto, "toBase64", js_mkfun(js_buffer_toBase64));
+9 -1
src/modules/collections.c
··· 886 886 887 887 void init_collections_module(void) { 888 888 ant_t *js = rt->js; 889 - jsval_t glob = js_glob(js); 889 + 890 + jsval_t glob = js->global; 891 + jsval_t object_proto = js->object; 890 892 891 893 const char *iter_key = get_iterator_sym_key(); 892 894 const char *toStringTag_key = get_toStringTag_sym_key(); 893 895 894 896 jsval_t map_proto = js_mkobj(js); 897 + js_set_proto(js, map_proto, object_proto); 895 898 js_set(js, map_proto, "set", js_mkfun(map_set)); 896 899 js_set(js, map_proto, "get", js_mkfun(map_get)); 897 900 js_set(js, map_proto, "has", js_mkfun(map_has)); ··· 913 916 js_set(js, glob, "Map", js_obj_to_func(map_ctor)); 914 917 915 918 jsval_t set_proto = js_mkobj(js); 919 + js_set_proto(js, set_proto, object_proto); 916 920 js_set(js, set_proto, "add", js_mkfun(set_add)); 917 921 js_set(js, set_proto, "has", js_mkfun(set_has)); 918 922 js_set(js, set_proto, "delete", js_mkfun(set_delete)); ··· 933 937 js_set(js, glob, "Set", js_obj_to_func(set_ctor)); 934 938 935 939 jsval_t weakmap_proto = js_mkobj(js); 940 + js_set_proto(js, weakmap_proto, object_proto); 936 941 js_set(js, weakmap_proto, "set", js_mkfun(weakmap_set)); 937 942 js_set(js, weakmap_proto, "get", js_mkfun(weakmap_get)); 938 943 js_set(js, weakmap_proto, "has", js_mkfun(weakmap_has)); ··· 947 952 js_set(js, glob, "WeakMap", js_obj_to_func(weakmap_ctor)); 948 953 949 954 jsval_t weakset_proto = js_mkobj(js); 955 + js_set_proto(js, weakset_proto, object_proto); 950 956 js_set(js, weakset_proto, "add", js_mkfun(weakset_add)); 951 957 js_set(js, weakset_proto, "has", js_mkfun(weakset_has)); 952 958 js_set(js, weakset_proto, "delete", js_mkfun(weakset_delete)); ··· 960 966 js_set(js, glob, "WeakSet", js_obj_to_func(weakset_ctor)); 961 967 962 968 jsval_t weakref_proto = js_mkobj(js); 969 + js_set_proto(js, weakref_proto, object_proto); 963 970 js_set(js, weakref_proto, "deref", js_mkfun(weakref_deref)); 964 971 js_set(js, weakref_proto, toStringTag_key, js_mkstr(js, "WeakRef", 7)); 965 972 ··· 971 978 js_set(js, glob, "WeakRef", js_obj_to_func(weakref_ctor)); 972 979 973 980 jsval_t finreg_proto = js_mkobj(js); 981 + js_set_proto(js, finreg_proto, object_proto); 974 982 js_set(js, finreg_proto, "register", js_mkfun(finreg_register)); 975 983 js_set(js, finreg_proto, "unregister", js_mkfun(finreg_unregister)); 976 984 js_set(js, finreg_proto, toStringTag_key, js_mkstr(js, "FinalizationRegistry", 20));
+104 -45
src/modules/symbol.c
··· 8 8 #include "internal.h" 9 9 #include "modules/symbol.h" 10 10 11 - static jsval_t g_iterator_sym = 0; 12 - static jsval_t g_asyncIterator_sym = 0; 13 - static jsval_t g_toStringTag_sym = 0; 14 - static jsval_t g_hasInstance_sym = 0; 15 - static jsval_t g_observable_sym = 0; 11 + typedef struct { 12 + jsval_t sym; 13 + char key[32]; 14 + } wellknown_sym_t; 15 + 16 + static wellknown_sym_t g_iterator = {0}; 17 + static wellknown_sym_t g_asyncIterator = {0}; 18 + static wellknown_sym_t g_toStringTag = {0}; 19 + static wellknown_sym_t g_hasInstance = {0}; 20 + static wellknown_sym_t g_observable = {0}; 21 + static wellknown_sym_t g_toPrimitive = {0}; 16 22 17 - static char g_iter_sym_key[32] = {0}; 18 - static char g_asyncIter_sym_key[32] = {0}; 19 - static char g_toStringTag_sym_key[32] = {0}; 20 - static char g_observable_sym_key[32] = {0}; 23 + jsval_t get_iterator_symbol(void) { return g_iterator.sym; } 24 + jsval_t get_asyncIterator_symbol(void) { return g_asyncIterator.sym; } 25 + jsval_t get_observable_symbol(void) { return g_observable.sym; } 26 + 27 + const char *get_iterator_sym_key(void) { return g_iterator.key; } 28 + const char *get_asyncIterator_sym_key(void) { return g_asyncIterator.key; } 29 + const char *get_toStringTag_sym_key(void) { return g_toStringTag.key; } 30 + const char *get_observable_sym_key(void) { return g_observable.key; } 31 + const char *get_toPrimitive_sym_key(void) { return g_toPrimitive.key; } 32 + const char *get_hasInstance_sym_key(void) { return g_hasInstance.key; } 33 + 34 + static const struct { jsval_t *sym; const char *name; } sym_table[] = { 35 + { &g_iterator.sym, "Symbol.iterator" }, 36 + { &g_asyncIterator.sym, "Symbol.asyncIterator" }, 37 + { &g_toStringTag.sym, "Symbol.toStringTag" }, 38 + { &g_hasInstance.sym, "Symbol.hasInstance" }, 39 + { &g_observable.sym, "Symbol.observable" }, 40 + { &g_toPrimitive.sym, "Symbol.toPrimitive" }, 41 + }; 21 42 22 - jsval_t get_iterator_symbol(void) { return g_iterator_sym; } 23 - jsval_t get_asyncIterator_symbol(void) { return g_asyncIterator_sym; } 24 - jsval_t get_observable_symbol(void) { return g_observable_sym; } 43 + const char *get_symbol_description_from_key(const char *sym_key, size_t key_len) { 44 + if (key_len < 9 || memcmp(sym_key, "__sym_", 6) != 0) return NULL; 45 + 46 + uint64_t id = 0; 47 + for (const char *p = sym_key + 6; *p >= '0' && *p <= '9'; p++) id = id * 10 + (*p - '0'); 48 + 49 + for (size_t i = 0; i < sizeof(sym_table) / sizeof(sym_table[0]); i++) { 50 + if (js_sym_id(*sym_table[i].sym) == id) return sym_table[i].name; 51 + } 52 + 53 + return "Symbol()"; 54 + } 25 55 26 - const char *get_iterator_sym_key(void) { return g_iter_sym_key; } 27 - const char *get_asyncIterator_sym_key(void) { return g_asyncIter_sym_key; } 28 - const char *get_toStringTag_sym_key(void) { return g_toStringTag_sym_key; } 29 - const char *get_observable_sym_key(void) { return g_observable_sym_key; } 56 + static inline void init_symbol(struct js *js, wellknown_sym_t *sym_var, const char *name) { 57 + sym_var->sym = js_mksym(js, name); 58 + snprintf(sym_var->key, sizeof(sym_var->key), "__sym_%llu__", (unsigned long long)js_sym_id(sym_var->sym)); 59 + } 30 60 31 61 static jsval_t builtin_Symbol(struct js *js, jsval_t *args, int nargs) { 32 62 const char *desc = NULL; ··· 58 88 return js_mkstr(js, key, strlen(key)); 59 89 } 60 90 91 + static jsval_t builtin_Symbol_toString(struct js *js, jsval_t *args, int nargs) { 92 + jsval_t this_val = js_getthis(js); 93 + 94 + if (vtype(this_val) != T_SYMBOL) { 95 + return js_mkerr(js, "Symbol.prototype.toString requires a symbol"); 96 + } 97 + 98 + return js_symbol_to_string(js, this_val); 99 + } 100 + 61 101 static jsval_t iterator_next(struct js *js, jsval_t *args, int nargs) { 62 - (void)args; (void)nargs; 63 102 jsval_t this_val = js_getthis(js); 64 103 65 104 jsval_t arr = js_get(js, this_val, "__arr"); ··· 87 126 } 88 127 89 128 static jsval_t array_iterator(struct js *js, jsval_t *args, int nargs) { 90 - (void)args; (void)nargs; 91 129 jsval_t arr = js_getthis(js); 92 130 93 131 jsval_t len_val = js_get(js, arr, "length"); ··· 103 141 } 104 142 105 143 static jsval_t string_iterator_next(struct js *js, jsval_t *args, int nargs) { 106 - (void)args; (void)nargs; 107 144 jsval_t this_val = js_getthis(js); 108 145 109 146 jsval_t str = js_get(js, this_val, "__str"); ··· 145 182 return iter; 146 183 } 147 184 148 - const char *get_symbol_description_from_key(const char *sym_key, size_t key_len) { 149 - if ( 150 - key_len < 9 || sym_key[0] != '_' || sym_key[1] != '_' || 151 - sym_key[2] != 's' || sym_key[3] != 'y' || sym_key[4] != 'm' || sym_key[5] != '_' 152 - ) return NULL; 185 + static jsval_t date_toPrimitive(struct js *js, jsval_t *args, int nargs) { 186 + jsval_t this_val = js_getthis(js); 187 + 188 + const char *hint = "default"; 189 + if (nargs > 0 && vtype(args[0]) == T_STR) { 190 + hint = js_getstr(js, args[0], NULL); 191 + } bool prefer_string = (hint == NULL || strcmp(hint, "number") != 0); 192 + 193 + const char *methods[2] = { 194 + prefer_string ? "toString" : "valueOf", 195 + prefer_string ? "valueOf" : "toString" 196 + }; 153 197 154 - if (g_iter_sym_key[0] && strncmp(sym_key, g_iter_sym_key, key_len) == 0 && g_iter_sym_key[key_len] == '\0') return "Symbol.iterator"; 155 - if (g_toStringTag_sym_key[0] && strncmp(sym_key, g_toStringTag_sym_key, key_len) == 0 && g_toStringTag_sym_key[key_len] == '\0') return "Symbol.toStringTag"; 198 + for (int i = 0; i < 2; i++) { 199 + jsval_t method = js_getprop_fallback(js, this_val, methods[i]); 200 + if (vtype(method) == T_FUNC || vtype(method) == T_CFUNC) { 201 + jsval_t result = js_call_with_this(js, method, this_val, NULL, 0); 202 + if (is_err(result) || !is_object_type(result)) return result; 203 + } 204 + } 156 205 157 - return "Symbol()"; 206 + return js_mkerr(js, "Cannot convert object to primitive value"); 158 207 } 159 208 209 + 160 210 void init_symbol_module(void) { 161 211 struct js *js = rt->js; 162 212 163 - g_iterator_sym = js_mksym(js, "Symbol.iterator"); 164 - g_asyncIterator_sym = js_mksym(js, "Symbol.asyncIterator"); 165 - g_toStringTag_sym = js_mksym(js, "Symbol.toStringTag"); 166 - g_hasInstance_sym = js_mksym(js, "Symbol.hasInstance"); 167 - g_observable_sym = js_mksym(js, "Symbol.observable"); 168 - 169 - snprintf(g_iter_sym_key, sizeof(g_iter_sym_key), "__sym_%llu__", (unsigned long long)js_sym_id(g_iterator_sym)); 170 - snprintf(g_asyncIter_sym_key, sizeof(g_asyncIter_sym_key), "__sym_%llu__", (unsigned long long)js_sym_id(g_asyncIterator_sym)); 171 - snprintf(g_toStringTag_sym_key, sizeof(g_toStringTag_sym_key), "__sym_%llu__", (unsigned long long)js_sym_id(g_toStringTag_sym)); 172 - snprintf(g_observable_sym_key, sizeof(g_observable_sym_key), "__sym_%llu__", (unsigned long long)js_sym_id(g_observable_sym)); 213 + init_symbol(js, &g_iterator, "Symbol.iterator"); 214 + init_symbol(js, &g_asyncIterator, "Symbol.asyncIterator"); 215 + init_symbol(js, &g_toStringTag, "Symbol.toStringTag"); 216 + init_symbol(js, &g_observable, "Symbol.observable"); 217 + init_symbol(js, &g_toPrimitive, "Symbol.toPrimitive"); 218 + init_symbol(js, &g_hasInstance, "Symbol.hasInstance"); 219 + 220 + jsval_t symbol_proto = js_mkobj(js); 221 + js_set(js, symbol_proto, "toString", js_mkfun(builtin_Symbol_toString)); 173 222 174 223 jsval_t symbol_ctor = js_mkobj(js); 175 224 js_set_slot(js, symbol_ctor, SLOT_CFUNC, js_mkfun(builtin_Symbol)); 176 225 js_setprop(js, symbol_ctor, js_mkstr(js, "for", 3), js_mkfun(builtin_Symbol_for)); 177 226 js_set(js, symbol_ctor, "keyFor", js_mkfun(builtin_Symbol_keyFor)); 227 + js_set(js, symbol_ctor, "prototype", symbol_proto); 178 228 179 - js_set(js, symbol_ctor, "iterator", g_iterator_sym); 180 - js_set(js, symbol_ctor, "asyncIterator", g_asyncIterator_sym); 181 - js_set(js, symbol_ctor, "toStringTag", g_toStringTag_sym); 182 - js_set(js, symbol_ctor, "hasInstance", g_hasInstance_sym); 183 - js_set(js, symbol_ctor, "observable", g_observable_sym); 229 + js_set(js, symbol_ctor, "iterator", g_iterator.sym); 230 + js_set(js, symbol_ctor, "asyncIterator", g_asyncIterator.sym); 231 + js_set(js, symbol_ctor, "toStringTag", g_toStringTag.sym); 232 + js_set(js, symbol_ctor, "hasInstance", g_hasInstance.sym); 233 + js_set(js, symbol_ctor, "observable", g_observable.sym); 234 + js_set(js, symbol_ctor, "toPrimitive", g_toPrimitive.sym); 184 235 185 236 jsval_t func_symbol = js_obj_to_func(symbol_ctor); 186 237 js_set(js, js_glob(js), "Symbol", func_symbol); 187 238 188 - // set internal types before module ready 239 + // set internal types before ant module snapshot 189 240 js_set(js, rt->ant_obj, get_toStringTag_sym_key(), js_mkstr(js, "Ant", 3)); 190 241 191 242 jsval_t array_ctor = js_get(js, js_glob(js), "Array"); 192 243 jsval_t array_proto = js_get(js, array_ctor, "prototype"); 193 - js_set(js, array_proto, g_iter_sym_key, js_mkfun(array_iterator)); 244 + js_set(js, array_proto, g_iterator.key, js_mkfun(array_iterator)); 194 245 195 246 jsval_t string_ctor = js_get(js, js_glob(js), "String"); 196 247 jsval_t string_proto = js_get(js, string_ctor, "prototype"); 197 - js_set(js, string_proto, g_iter_sym_key, js_mkfun(string_iterator)); 248 + js_set(js, string_proto, g_iterator.key, js_mkfun(string_iterator)); 249 + 250 + jsval_t date_ctor = js_get(js, js_glob(js), "Date"); 251 + jsval_t date_proto = js_get(js, date_ctor, "prototype"); 252 + js_set(js, date_proto, g_toPrimitive.key, js_mkfun(date_toPrimitive)); 253 + 254 + jsval_t promise_ctor = js_get(js, js_glob(js), "Promise"); 255 + jsval_t promise_proto = js_get(js, promise_ctor, "prototype"); 256 + js_set(js, promise_proto, g_toStringTag.key, js_mkstr(js, "Promise", 7)); 198 257 }
+2 -4
src/runtime.c
··· 92 92 code_arena_current = NULL; 93 93 } 94 94 95 - struct ant_runtime *ant_runtime_init(struct js *js, int argc, char **argv, struct arg_file *ls_p) { 95 + struct ant_runtime *ant_runtime_init(ant_t *js, int argc, char **argv, struct arg_file *ls_p) { 96 96 runtime = (struct ant_runtime){ 97 97 .js = js, 98 98 .ant_obj = js_newobj(js), 99 - .flags = 0, 100 - .argc = argc, 101 - .argv = argv, 99 + .flags = 0, .argc = argc, .argv = argv, 102 100 .ls_fp = (ls_p && ls_p->count > 0) ? ls_p->filename[0] : NULL, 103 101 }; 104 102
+42
tests/sym_date.js
··· 1 + let d = new Date(); 2 + 3 + // Test 1: Default behavior 4 + console.log(d[Symbol.toPrimitive]('default')); // Should call toString 5 + 6 + // Test 2: String hint 7 + console.log(d[Symbol.toPrimitive]('string')); // Should call toString 8 + 9 + // Test 3: Number hint 10 + console.log(d[Symbol.toPrimitive]('number')); // Should call valueOf (timestamp) 11 + 12 + // Test 4: Coercion tests 13 + console.log(d + ''); // Uses default hint -> string 14 + console.log(+d); // Uses number hint -> number 15 + console.log(d - 0); // Uses number hint -> number 16 + 17 + // Test 5: Verify order - SAVE ORIGINAL METHODS FIRST 18 + const originalValueOf = Date.prototype.valueOf; 19 + const originalToString = Date.prototype.toString; 20 + 21 + Date.prototype.valueOf = function () { 22 + console.log('valueOf called'); 23 + return originalValueOf.call(this); // Call the SAVED original 24 + }; 25 + Date.prototype.toString = function () { 26 + console.log('toString called'); 27 + return originalToString.call(this); // Call the SAVED original 28 + }; 29 + 30 + let d2 = new Date(); 31 + console.log('\nTesting string hint:'); 32 + d2[Symbol.toPrimitive]('string'); // Should log: toString only (returns primitive) 33 + 34 + console.log('\nTesting number hint:'); 35 + d2[Symbol.toPrimitive]('number'); // Should log: valueOf only (returns primitive) 36 + 37 + console.log('\nTesting default hint:'); 38 + d2[Symbol.toPrimitive]('default'); // Should log: toString only (returns primitive) 39 + 40 + // Restore original methods 41 + Date.prototype.valueOf = originalValueOf; 42 + Date.prototype.toString = originalToString;
+35
tests/sym_id.js
··· 1 + console.log('=== SYMBOL KEY STRINGIFICATION TEST ===\n'); 2 + 3 + const obj = {}; 4 + 5 + obj[Symbol.iterator] = 'iter-value'; 6 + obj[Symbol.asyncIterator] = 'async-iter-value'; 7 + obj[Symbol.toStringTag] = 'tag-value'; 8 + obj[Symbol.hasInstance] = 'has-instance-value'; 9 + obj[Symbol.observable] = 'observable-value'; 10 + obj[Symbol.toPrimitive] = 'to-primitive-value'; 11 + obj[Symbol('custom')] = 'custom-value'; 12 + obj[Symbol()] = 'anonymous-value'; 13 + obj.normalKey = 'normal-value'; 14 + 15 + console.log('Object with symbol keys:'); 16 + console.log(obj); 17 + 18 + console.log('\nIndividual access:'); 19 + console.log(' [Symbol.iterator]: ' + obj[Symbol.iterator]); 20 + console.log(' [Symbol.toStringTag]: ' + obj[Symbol.toStringTag]); 21 + 22 + console.log('\nObject.keys (should NOT include symbols):'); 23 + console.log(Object.keys(obj)); 24 + 25 + console.log('\nObject.getOwnPropertySymbols (if supported):'); 26 + try { 27 + console.log(Object.getOwnPropertySymbols(obj)); 28 + } catch (e) { 29 + console.log(' Not supported: ' + e.message); 30 + } 31 + 32 + console.log('\nJSON.stringify (symbols should be skipped):'); 33 + console.log(JSON.stringify(obj)); 34 + 35 + console.log('\n=== DONE ===');
+139
tests/test-coercion-matrix.js
··· 1 + // ============================================ 2 + // TEST: String coercion comparison matrix 3 + // ============================================ 4 + // Compares: concat, template, String(), toString() 5 + // ============================================ 6 + 7 + console.log("=== STRING COERCION MATRIX ===\n"); 8 + 9 + function safe(fn) { 10 + try { 11 + const result = fn(); 12 + if (result === undefined) return "undefined"; 13 + if (result === null) return "null"; 14 + return result; 15 + } catch (e) { 16 + return "ERROR:" + e.name; 17 + } 18 + } 19 + 20 + function test(name, value) { 21 + console.log(name + ":"); 22 + console.log(" concat: " + safe(function() { return "" + value; })); 23 + console.log(" template: " + safe(function() { return `${value}`; })); 24 + console.log(" String(): " + safe(function() { return String(value); })); 25 + if (value !== null && value !== undefined) { 26 + console.log(" toString: " + safe(function() { return value.toString(); })); 27 + } 28 + console.log(""); 29 + } 30 + 31 + // Primitives 32 + console.log("--- PRIMITIVES ---\n"); 33 + test("number 42", 42); 34 + test("number 0", 0); 35 + test("number -1", -1); 36 + test("number NaN", NaN); 37 + test("number Infinity", Infinity); 38 + test("string 'hello'", "hello"); 39 + test("string ''", ""); 40 + test("boolean true", true); 41 + test("boolean false", false); 42 + test("null", null); 43 + test("undefined", undefined); 44 + test("BigInt 123n", 123n); 45 + 46 + // Symbol 47 + console.log("--- SYMBOL ---\n"); 48 + test("Symbol('test')", Symbol("test")); 49 + 50 + // Arrays 51 + console.log("--- ARRAYS ---\n"); 52 + test("[]", []); 53 + test("[1, 2, 3]", [1, 2, 3]); 54 + test("[[1,2], [3,4]]", [[1, 2], [3, 4]]); 55 + test("[null, undefined]", [null, undefined]); 56 + test("['a', 'b']", ["a", "b"]); 57 + 58 + // Functions 59 + console.log("--- FUNCTIONS ---\n"); 60 + test("() => 1", () => 1); 61 + test("function() {}", function() {}); 62 + function namedFn() { return 1; } 63 + test("named function", namedFn); 64 + test("async () => 1", async () => 1); 65 + 66 + // Objects 67 + console.log("--- OBJECTS ---\n"); 68 + test("{}", {}); 69 + test("{ a: 1 }", { a: 1 }); 70 + test("{ a: 1, b: 2 }", { a: 1, b: 2 }); 71 + 72 + // Built-in objects 73 + console.log("--- BUILT-IN OBJECTS ---\n"); 74 + test("new Date(0)", new Date(0)); 75 + test("new Date()", new Date()); 76 + test("/test/gi", /test/gi); 77 + test("new Error('oops')", new Error("oops")); 78 + test("new TypeError('bad')", new TypeError("bad")); 79 + test("new Map([['a',1]])", new Map([["a", 1]])); 80 + test("new Set([1,2,3])", new Set([1, 2, 3])); 81 + test("new WeakMap()", new WeakMap()); 82 + test("new WeakSet()", new WeakSet()); 83 + test("new ArrayBuffer(8)", new ArrayBuffer(8)); 84 + test("new Uint8Array([1,2])", new Uint8Array([1, 2])); 85 + test("Promise.resolve(1)", Promise.resolve(1)); 86 + 87 + // Custom toString 88 + console.log("--- CUSTOM toString (CRASH TESTS) ---\n"); 89 + 90 + console.log("native toString (should work):"); 91 + test("{ toString: Object.prototype.toString }", { toString: Object.prototype.toString }); 92 + 93 + console.log("user toString (may crash):"); 94 + const userToString = { toString: function() { return "custom"; } }; 95 + console.log(" direct call works: " + userToString.toString()); 96 + test("{ toString: fn => 'custom' }", userToString); 97 + 98 + console.log("arrow toString (may crash):"); 99 + const arrowToString = { toString: () => "arrow" }; 100 + console.log(" direct call works: " + arrowToString.toString()); 101 + test("{ toString: () => 'arrow' }", arrowToString); 102 + 103 + // Custom valueOf 104 + console.log("--- CUSTOM valueOf ---\n"); 105 + test("{ valueOf: fn => 42 }", { valueOf: function() { return 42; } }); 106 + test("{ valueOf: fn => 'val' }", { valueOf: function() { return "val"; } }); 107 + 108 + // Symbol.toPrimitive 109 + console.log("--- Symbol.toPrimitive ---\n"); 110 + const withPrimitive = {}; 111 + withPrimitive[Symbol.toPrimitive] = function(hint) { return "hint:" + hint; }; 112 + test("{ [Symbol.toPrimitive]: fn }", withPrimitive); 113 + 114 + // Class instances 115 + console.log("--- CLASS INSTANCES ---\n"); 116 + 117 + class WithToString { 118 + toString() { return "class-toString"; } 119 + } 120 + console.log("class with toString method:"); 121 + const inst1 = new WithToString(); 122 + console.log(" direct call works: " + inst1.toString()); 123 + test("new WithToString()", inst1); 124 + 125 + class WithValueOf { 126 + valueOf() { return "class-valueOf"; } 127 + } 128 + test("new WithValueOf()", new WithValueOf()); 129 + 130 + class PlainClass {} 131 + test("new PlainClass()", new PlainClass()); 132 + 133 + console.log("=== SUMMARY ===\n"); 134 + console.log("Check for:"); 135 + console.log("1. Missing output after test name = SILENT CRASH"); 136 + console.log("2. '[Function: ...]' instead of function source = INSPECT BUG"); 137 + console.log("3. '{ ... }' or '[ ... ]' with spaces = INSPECT BUG"); 138 + console.log("4. Different results between concat and template = BUG"); 139 + console.log("");
+80
tests/test-concat-crash.js
··· 1 + // ============================================ 2 + // TEST: Concatenation crashes with user-defined toString 3 + // ============================================ 4 + // Bug: "" + obj crashes silently when obj has user-defined toString 5 + // ============================================ 6 + 7 + console.log("=== CONCAT CRASH TESTS ===\n"); 8 + 9 + // --- BASELINE (should work) --- 10 + 11 + console.log("1. No custom toString:"); 12 + const obj1 = {}; 13 + console.log(" result: " + ("" + obj1)); 14 + 15 + console.log("\n2. toString = native function:"); 16 + const obj2 = { toString: Object.prototype.toString }; 17 + console.log(" result: " + ("" + obj2)); 18 + 19 + console.log("\n3. toString = Array.prototype.toString:"); 20 + const obj3 = { toString: Array.prototype.toString }; 21 + console.log(" result: " + ("" + obj3)); 22 + 23 + console.log("\n4. toString = console.log (native):"); 24 + const obj4 = { toString: console.log }; 25 + console.log(" result: " + ("" + obj4)); 26 + 27 + // --- CRASH TESTS (these crash silently) --- 28 + 29 + console.log("\n5. toString = user function:"); 30 + const obj5 = { toString: function() { return "custom"; } }; 31 + console.log(" expected: custom"); 32 + console.log(" direct call: " + obj5.toString()); 33 + const result5 = "" + obj5; 34 + console.log(" concat: " + result5); 35 + console.log(" (if missing, CRASH)"); 36 + 37 + console.log("\n6. toString = arrow function:"); 38 + const obj6 = { toString: () => "arrow" }; 39 + console.log(" expected: arrow"); 40 + console.log(" direct call: " + obj6.toString()); 41 + const result6 = "" + obj6; 42 + console.log(" concat: " + result6); 43 + console.log(" (if missing, CRASH)"); 44 + 45 + console.log("\n7. toString = user function returning number:"); 46 + const obj7 = { toString: function() { return 42; } }; 47 + console.log(" expected: 42"); 48 + console.log(" direct call: " + obj7.toString()); 49 + const result7 = "" + obj7; 50 + console.log(" concat: " + result7); 51 + console.log(" (if missing, CRASH)"); 52 + 53 + console.log("\n8. Inherited toString from prototype:"); 54 + function MyClass() {} 55 + MyClass.prototype.toString = function() { return "inherited"; }; 56 + const obj8 = new MyClass(); 57 + console.log(" expected: inherited"); 58 + console.log(" direct call: " + obj8.toString()); 59 + const result8 = "" + obj8; 60 + console.log(" concat: " + result8); 61 + console.log(" (if missing, CRASH)"); 62 + 63 + console.log("\n9. toString as getter returning function:"); 64 + const obj9 = { get toString() { return function() { return "getter"; }; } }; 65 + console.log(" expected: getter"); 66 + const result9 = "" + obj9; 67 + console.log(" concat: " + result9); 68 + 69 + console.log("\n10. toString = non-function value:"); 70 + const obj10 = { toString: "not a function" }; 71 + console.log(" expected: TypeError"); 72 + try { 73 + const result10 = "" + obj10; 74 + console.log(" concat: " + result10); 75 + console.log(" ERROR: should have thrown TypeError!"); 76 + } catch (e) { 77 + console.log(" threw: " + e.name + " (correct)"); 78 + } 79 + 80 + console.log("\n=== DONE ===");
+158
tests/test-string-constructor.js
··· 1 + // ============================================ 2 + // TEST: String() uses inspect instead of toString 3 + // ============================================ 4 + // Bug: String(obj) uses inspect-style output instead of calling toString() 5 + // ============================================ 6 + 7 + console.log("=== String() CONSTRUCTOR TESTS ===\n"); 8 + 9 + // --- PRIMITIVES --- 10 + 11 + console.log("1. Primitives:"); 12 + console.log(" String(42): expected '42', got '" + String(42) + "'"); 13 + console.log(" String('hi'): expected 'hi', got '" + String("hi") + "'"); 14 + console.log(" String(true): expected 'true', got '" + String(true) + "'"); 15 + console.log(" String(false): expected 'false', got '" + String(false) + "'"); 16 + console.log(" String(null): expected 'null', got '" + String(null) + "'"); 17 + console.log(" String(undefined): expected 'undefined', got '" + String(undefined) + "'"); 18 + console.log(" String(123n): expected '123', got '" + String(123n) + "'"); 19 + 20 + // --- ARRAYS --- 21 + 22 + console.log("\n2. Array [1, 2, 3]:"); 23 + const arr1 = [1, 2, 3]; 24 + console.log(" expected: '1,2,3'"); 25 + console.log(" String(): '" + String(arr1) + "'"); 26 + console.log(" toString: '" + arr1.toString() + "'"); 27 + 28 + console.log("\n3. Nested array [[1,2], [3,4]]:"); 29 + const arr2 = [[1, 2], [3, 4]]; 30 + console.log(" expected: '1,2,3,4'"); 31 + console.log(" String(): '" + String(arr2) + "'"); 32 + console.log(" toString: '" + arr2.toString() + "'"); 33 + 34 + console.log("\n4. Empty array []:"); 35 + const arr3 = []; 36 + console.log(" expected: ''"); 37 + console.log(" String(): '" + String(arr3) + "'"); 38 + 39 + console.log("\n5. Array with objects:"); 40 + const arr4 = [{ a: 1 }, { b: 2 }]; 41 + console.log(" expected: '[object Object],[object Object]'"); 42 + console.log(" String(): '" + String(arr4) + "'"); 43 + 44 + // --- FUNCTIONS --- 45 + 46 + console.log("\n6. Arrow function:"); 47 + const fn1 = () => 1; 48 + console.log(" expected: '() => 1'"); 49 + console.log(" String(): '" + String(fn1) + "'"); 50 + console.log(" toString: '" + fn1.toString() + "'"); 51 + 52 + console.log("\n7. Named function:"); 53 + function namedFn() { return 1; } 54 + console.log(" expected: 'function namedFn() { return 1; }'"); 55 + console.log(" String(): '" + String(namedFn) + "'"); 56 + console.log(" toString: '" + namedFn.toString() + "'"); 57 + 58 + console.log("\n8. Async function:"); 59 + async function asyncFn() { return 1; } 60 + console.log(" expected: 'async function asyncFn() { return 1; }'"); 61 + console.log(" String(): '" + String(asyncFn) + "'"); 62 + console.log(" toString: '" + asyncFn.toString() + "'"); 63 + 64 + // --- OBJECTS --- 65 + 66 + console.log("\n9. Plain object {}:"); 67 + const obj1 = {}; 68 + console.log(" expected: '[object Object]'"); 69 + console.log(" String(): '" + String(obj1) + "'"); 70 + 71 + console.log("\n10. Object with properties:"); 72 + const obj2 = { a: 1, b: 2 }; 73 + console.log(" expected: '[object Object]'"); 74 + console.log(" String(): '" + String(obj2) + "'"); 75 + 76 + console.log("\n11. Object with custom toString:"); 77 + const obj3 = { toString: function() { return "custom-toString"; } }; 78 + console.log(" expected: 'custom-toString'"); 79 + console.log(" toString: '" + obj3.toString() + "'"); 80 + console.log(" String(): '" + String(obj3) + "'"); 81 + 82 + console.log("\n12. Object with valueOf only:"); 83 + const obj4 = { valueOf: function() { return "custom-valueOf"; } }; 84 + console.log(" expected: 'custom-valueOf' or '[object Object]'"); 85 + console.log(" String(): '" + String(obj4) + "'"); 86 + 87 + console.log("\n13. Object with both toString and valueOf:"); 88 + const obj5 = { 89 + toString: function() { return "from-toString"; }, 90 + valueOf: function() { return "from-valueOf"; } 91 + }; 92 + console.log(" expected: 'from-toString' (toString has priority)"); 93 + console.log(" String(): '" + String(obj5) + "'"); 94 + 95 + // --- BUILT-IN OBJECTS --- 96 + 97 + console.log("\n14. Date:"); 98 + const date = new Date(0); 99 + console.log(" expected: date string like 'Thu Jan 01 1970...'"); 100 + console.log(" String(): '" + String(date) + "'"); 101 + console.log(" toString: '" + date.toString() + "'"); 102 + 103 + console.log("\n15. RegExp:"); 104 + const re = /test/gi; 105 + console.log(" expected: '/test/gi'"); 106 + console.log(" String(): '" + String(re) + "'"); 107 + console.log(" toString: '" + re.toString() + "'"); 108 + 109 + console.log("\n16. Error:"); 110 + const err = new Error("oops"); 111 + console.log(" expected: 'Error: oops'"); 112 + console.log(" String(): '" + String(err) + "'"); 113 + console.log(" toString: '" + err.toString() + "'"); 114 + 115 + console.log("\n17. Map:"); 116 + const map = new Map([["a", 1]]); 117 + console.log(" expected: '[object Map]'"); 118 + console.log(" String(): '" + String(map) + "'"); 119 + 120 + console.log("\n18. Set:"); 121 + const set = new Set([1, 2, 3]); 122 + console.log(" expected: '[object Set]'"); 123 + console.log(" String(): '" + String(set) + "'"); 124 + 125 + // --- SYMBOL --- 126 + 127 + console.log("\n19. Symbol:"); 128 + const sym = Symbol("test"); 129 + console.log(" expected: 'Symbol(test)'"); 130 + console.log(" String(): '" + String(sym) + "'"); 131 + console.log(" toString: '" + sym.toString() + "'"); 132 + 133 + // --- Symbol.toPrimitive --- 134 + 135 + console.log("\n20. Object with Symbol.toPrimitive:"); 136 + const obj6 = {}; 137 + obj6[Symbol.toPrimitive] = function(hint) { return "toPrimitive-" + hint; }; 138 + console.log(" expected: 'toPrimitive-string'"); 139 + console.log(" String(): '" + String(obj6) + "'"); 140 + 141 + // --- CLASS INSTANCES --- 142 + 143 + console.log("\n21. Class instance with toString:"); 144 + class MyClass { 145 + toString() { return "MyClass instance"; } 146 + } 147 + const inst = new MyClass(); 148 + console.log(" expected: 'MyClass instance'"); 149 + console.log(" toString: '" + inst.toString() + "'"); 150 + console.log(" String(): '" + String(inst) + "'"); 151 + 152 + console.log("\n22. Class instance without toString:"); 153 + class PlainClass {} 154 + const inst2 = new PlainClass(); 155 + console.log(" expected: '[object Object]'"); 156 + console.log(" String(): '" + String(inst2) + "'"); 157 + 158 + console.log("\n=== DONE ===");
+135
tests/test-template-literal.js
··· 1 + // ============================================ 2 + // TEST: Template literals use inspect instead of toString 3 + // ============================================ 4 + // Bug: `${obj}` uses inspect-style output instead of calling toString() 5 + // ============================================ 6 + 7 + console.log("=== TEMPLATE LITERAL TESTS ===\n"); 8 + 9 + // --- PRIMITIVES --- 10 + 11 + console.log("1. Primitives:"); 12 + console.log(" number: expected '42', got '" + `${42}` + "'"); 13 + console.log(" string: expected 'hi', got '" + `${"hi"}` + "'"); 14 + console.log(" boolean: expected 'true', got '" + `${true}` + "'"); 15 + console.log(" null: expected 'null', got '" + `${null}` + "'"); 16 + console.log(" undefined: expected 'undefined', got '" + `${undefined}` + "'"); 17 + console.log(" BigInt: expected '123', got '" + `${123n}` + "'"); 18 + 19 + // --- ARRAYS --- 20 + 21 + console.log("\n2. Array [1, 2, 3]:"); 22 + const arr1 = [1, 2, 3]; 23 + console.log(" expected: '1,2,3'"); 24 + console.log(" concat: '" + ("" + arr1) + "'"); 25 + console.log(" template: '" + `${arr1}` + "'"); 26 + console.log(" toString: '" + arr1.toString() + "'"); 27 + 28 + console.log("\n3. Nested array [[1,2], [3,4]]:"); 29 + const arr2 = [[1, 2], [3, 4]]; 30 + console.log(" expected: '1,2,3,4'"); 31 + console.log(" concat: '" + ("" + arr2) + "'"); 32 + console.log(" template: '" + `${arr2}` + "'"); 33 + console.log(" toString: '" + arr2.toString() + "'"); 34 + 35 + console.log("\n4. Empty array []:"); 36 + const arr3 = []; 37 + console.log(" expected: ''"); 38 + console.log(" concat: '" + ("" + arr3) + "'"); 39 + console.log(" template: '" + `${arr3}` + "'"); 40 + 41 + // --- FUNCTIONS --- 42 + 43 + console.log("\n5. Arrow function:"); 44 + const fn1 = () => 1; 45 + console.log(" expected: '() => 1'"); 46 + console.log(" concat: '" + ("" + fn1) + "'"); 47 + console.log(" template: '" + `${fn1}` + "'"); 48 + console.log(" toString: '" + fn1.toString() + "'"); 49 + 50 + console.log("\n6. Named function:"); 51 + function namedFn() { return 1; } 52 + console.log(" expected: 'function namedFn() { return 1; }'"); 53 + console.log(" concat: '" + ("" + namedFn) + "'"); 54 + console.log(" template: '" + `${namedFn}` + "'"); 55 + 56 + console.log("\n7. Function expression:"); 57 + const fn2 = function() { return 1; }; 58 + console.log(" expected: 'function() { return 1; }'"); 59 + console.log(" concat: '" + ("" + fn2) + "'"); 60 + console.log(" template: '" + `${fn2}` + "'"); 61 + 62 + // --- OBJECTS --- 63 + 64 + console.log("\n8. Plain object {}:"); 65 + const obj1 = {}; 66 + console.log(" expected: '[object Object]'"); 67 + console.log(" concat: '" + ("" + obj1) + "'"); 68 + console.log(" template: '" + `${obj1}` + "'"); 69 + 70 + console.log("\n9. Object with properties {a:1, b:2}:"); 71 + const obj2 = { a: 1, b: 2 }; 72 + console.log(" expected: '[object Object]'"); 73 + console.log(" concat: '" + ("" + obj2) + "'"); 74 + console.log(" template: '" + `${obj2}` + "'"); 75 + 76 + console.log("\n10. Object with custom toString:"); 77 + const obj3 = { toString: function() { return "custom"; } }; 78 + console.log(" expected: 'custom'"); 79 + console.log(" toString: '" + obj3.toString() + "'"); 80 + console.log(" template: '" + `${obj3}` + "'"); 81 + 82 + // --- BUILT-IN OBJECTS --- 83 + 84 + console.log("\n11. Date:"); 85 + const date = new Date(0); 86 + console.log(" expected: 'Thu Jan 01 1970...' (locale date string)"); 87 + console.log(" concat: '" + ("" + date) + "'"); 88 + console.log(" template: '" + `${date}` + "'"); 89 + 90 + console.log("\n12. RegExp /test/gi:"); 91 + const re = /test/gi; 92 + console.log(" expected: '/test/gi'"); 93 + console.log(" concat: '" + ("" + re) + "'"); 94 + console.log(" template: '" + `${re}` + "'"); 95 + 96 + console.log("\n13. Error:"); 97 + const err = new Error("oops"); 98 + console.log(" expected: 'Error: oops'"); 99 + console.log(" concat: '" + ("" + err) + "'"); 100 + console.log(" template: '" + `${err}` + "'"); 101 + 102 + console.log("\n14. Map:"); 103 + const map = new Map([["a", 1]]); 104 + console.log(" expected: '[object Map]'"); 105 + console.log(" concat: '" + ("" + map) + "'"); 106 + console.log(" template: '" + `${map}` + "'"); 107 + 108 + console.log("\n15. Set:"); 109 + const set = new Set([1, 2, 3]); 110 + console.log(" expected: '[object Set]'"); 111 + console.log(" concat: '" + ("" + set) + "'"); 112 + console.log(" template: '" + `${set}` + "'"); 113 + 114 + // --- SYMBOL (should throw) --- 115 + 116 + console.log("\n16. Symbol (should throw TypeError):"); 117 + const sym = Symbol("test"); 118 + console.log(" toString: '" + sym.toString() + "'"); 119 + try { 120 + console.log(" template: '" + `${sym}` + "'"); 121 + console.log(" ERROR: should have thrown!"); 122 + } catch (e) { 123 + console.log(" template: threw " + e.name + " (correct)"); 124 + } 125 + 126 + // --- QUINE TEST --- 127 + 128 + console.log("\n17. Quine test (self-referencing function):"); 129 + const $ = function(_) { return "$=" + $ + ";$()"; }; 130 + console.log(" expected: '$=function(_) { return \"$=\" + $ + \";$()\"; };$()'"); 131 + console.log(" concat: '" + $() + "'"); 132 + const $2 = function(_) { return `$=${$2};$()`; }; 133 + console.log(" template: '" + $2() + "'"); 134 + 135 + console.log("\n=== DONE ===");
+243
tests/test-valueof-toprimitive.js
··· 1 + // ============================================ 2 + // TEST: valueOf and Symbol.toPrimitive behavior 3 + // ============================================ 4 + // Bug: valueOf is never called, toPrimitive may not work 5 + // ============================================ 6 + 7 + console.log('=== valueOf AND Symbol.toPrimitive TESTS ===\n'); 8 + 9 + // --- valueOf TESTS --- 10 + 11 + console.log('SECTION 1: valueOf\n'); 12 + 13 + console.log('1. Only valueOf, returns string:'); 14 + const obj1 = { 15 + valueOf: function () { 16 + return 'from-valueOf'; 17 + } 18 + }; 19 + console.log(" expected concat: 'from-valueOf'"); 20 + console.log(" concat: '" + ('' + obj1) + "'"); 21 + console.log(" String(): '" + String(obj1) + "'"); 22 + 23 + console.log('\n2. Only valueOf, returns number:'); 24 + const obj2 = { 25 + valueOf: function () { 26 + return 42; 27 + } 28 + }; 29 + console.log(" expected concat: '42'"); 30 + console.log(" concat: '" + ('' + obj2) + "'"); 31 + console.log(" String(): '" + String(obj2) + "'"); 32 + 33 + console.log('\n3. Only valueOf, returns boolean:'); 34 + const obj3 = { 35 + valueOf: function () { 36 + return true; 37 + } 38 + }; 39 + console.log(" expected concat: 'true'"); 40 + console.log(" concat: '" + ('' + obj3) + "'"); 41 + 42 + console.log('\n4. valueOf returns object (should fallback to toString):'); 43 + const obj4 = { 44 + valueOf: function () { 45 + return {}; 46 + } 47 + }; 48 + console.log(" expected: '[object Object]' (fallback)"); 49 + console.log(" concat: '" + ('' + obj4) + "'"); 50 + 51 + console.log('\n5. valueOf returns null:'); 52 + const obj5 = { 53 + valueOf: function () { 54 + return null; 55 + } 56 + }; 57 + console.log(" expected: 'null'"); 58 + console.log(" concat: '" + ('' + obj5) + "'"); 59 + 60 + console.log('\n6. valueOf returns undefined:'); 61 + const obj6 = { 62 + valueOf: function () { 63 + return undefined; 64 + } 65 + }; 66 + console.log(" expected: 'undefined'"); 67 + console.log(" concat: '" + ('' + obj6) + "'"); 68 + 69 + // --- toString + valueOf priority --- 70 + 71 + console.log('\n\nSECTION 2: toString vs valueOf priority\n'); 72 + 73 + console.log('7. Both toString and valueOf (user functions):'); 74 + const obj7 = { 75 + toString: function () { 76 + return 'from-toString'; 77 + }, 78 + valueOf: function () { 79 + return 'from-valueOf'; 80 + } 81 + }; 82 + console.log(" expected: 'from-toString' (toString has priority for string hint)"); 83 + console.log(" direct toString: '" + obj7.toString() + "'"); 84 + console.log(" direct valueOf: '" + obj7.valueOf() + "'"); 85 + const result7 = '' + obj7; 86 + console.log(" concat: '" + result7 + "'"); 87 + console.log(' (if missing, CRASH)'); 88 + 89 + console.log('\n8. toString throws, valueOf works:'); 90 + const obj8 = { 91 + toString: function () { 92 + throw new Error('toString error'); 93 + }, 94 + valueOf: function () { 95 + return 'from-valueOf'; 96 + } 97 + }; 98 + console.log(" expected: 'from-valueOf' (fallback) or Error"); 99 + try { 100 + console.log(" concat: '" + ('' + obj8) + "'"); 101 + } catch (e) { 102 + console.log(' error: ' + e.message); 103 + } 104 + 105 + console.log('\n9. toString returns object, valueOf returns string:'); 106 + const obj9 = { 107 + toString: function () { 108 + return {}; 109 + }, 110 + valueOf: function () { 111 + return 'from-valueOf'; 112 + } 113 + }; 114 + console.log(" expected: 'from-valueOf' (fallback)"); 115 + try { 116 + console.log(" concat: '" + ('' + obj9) + "'"); 117 + } catch (e) { 118 + console.log(' error: ' + e.message); 119 + } 120 + 121 + console.log('\n10. Both return objects (should throw TypeError):'); 122 + const obj10 = { 123 + toString: function () { 124 + return {}; 125 + }, 126 + valueOf: function () { 127 + return {}; 128 + } 129 + }; 130 + console.log(' expected: TypeError'); 131 + try { 132 + console.log(" concat: '" + ('' + obj10) + "'"); 133 + } catch (e) { 134 + console.log(' error: ' + e.name + ': ' + e.message); 135 + } 136 + 137 + // --- Symbol.toPrimitive --- 138 + 139 + console.log('\n\nSECTION 3: Symbol.toPrimitive\n'); 140 + 141 + console.log('11. toPrimitive returns string:'); 142 + const obj11 = {}; 143 + obj11[Symbol.toPrimitive] = function (hint) { 144 + return 'primitive-' + hint; 145 + }; 146 + console.log(" expected concat: 'primitive-default' or 'primitive-string'"); 147 + console.log(" concat: '" + ('' + obj11) + "'"); 148 + console.log(" String(): '" + String(obj11) + "'"); 149 + console.log(" template: '" + `${obj11}` + "'"); 150 + 151 + console.log('\n12. toPrimitive returns number:'); 152 + const obj12 = {}; 153 + obj12[Symbol.toPrimitive] = function (hint) { 154 + return 99; 155 + }; 156 + console.log(" expected: '99'"); 157 + console.log(" concat: '" + ('' + obj12) + "'"); 158 + 159 + console.log('\n13. toPrimitive overrides toString and valueOf:'); 160 + const obj13 = { 161 + toString: function () { 162 + return 'from-toString'; 163 + }, 164 + valueOf: function () { 165 + return 'from-valueOf'; 166 + } 167 + }; 168 + obj13[Symbol.toPrimitive] = function (hint) { 169 + return 'from-toPrimitive'; 170 + }; 171 + console.log(" expected: 'from-toPrimitive' (toPrimitive has highest priority)"); 172 + console.log(" concat: '" + ('' + obj13) + "'"); 173 + 174 + console.log('\n14. toPrimitive returns object (should throw):'); 175 + const obj14 = {}; 176 + obj14[Symbol.toPrimitive] = function (hint) { 177 + return {}; 178 + }; 179 + console.log(' expected: TypeError'); 180 + try { 181 + console.log(" concat: '" + ('' + obj14) + "'"); 182 + } catch (e) { 183 + console.log(' error: ' + e.name + ': ' + e.message); 184 + } 185 + 186 + console.log('\n15. toPrimitive throws:'); 187 + const obj15 = {}; 188 + obj15[Symbol.toPrimitive] = function (hint) { 189 + throw new Error('toPrimitive error'); 190 + }; 191 + console.log(' expected: Error: toPrimitive error'); 192 + try { 193 + console.log(" concat: '" + ('' + obj15) + "'"); 194 + } catch (e) { 195 + if (e && e.message) { 196 + console.log(' error: ' + e.message); 197 + } else { 198 + console.log(' error: ' + String(e)); 199 + } 200 + } 201 + 202 + console.log('\n16. toPrimitive is not a function:'); 203 + const obj16 = {}; 204 + obj16[Symbol.toPrimitive] = 'not a function'; 205 + console.log(' expected: TypeError or fallback to toString'); 206 + try { 207 + console.log(" concat: '" + ('' + obj16) + "'"); 208 + } catch (e) { 209 + console.log(' error: ' + e.name + ': ' + e.message); 210 + } 211 + 212 + // --- Hint values --- 213 + 214 + console.log('\n\nSECTION 4: Hint values\n'); 215 + 216 + console.log('17. Check hint values:'); 217 + const hints = []; 218 + const obj17 = {}; 219 + obj17[Symbol.toPrimitive] = function (hint) { 220 + hints.push(hint); 221 + return 'ok'; 222 + }; 223 + 224 + console.log(" concat '' + obj:"); 225 + let r1 = '' + obj17; 226 + console.log(" hint was: '" + hints[hints.length - 1] + "'"); 227 + 228 + console.log(' String(obj):'); 229 + let r2 = String(obj17); 230 + console.log(" hint was: '" + hints[hints.length - 1] + "'"); 231 + 232 + console.log(' template `${obj}`:'); 233 + let r3 = `${obj17}`; 234 + console.log(" hint was: '" + hints[hints.length - 1] + "'"); 235 + 236 + console.log(' +obj (unary plus, number hint):'); 237 + let r4 = +obj17; 238 + console.log(" hint was: '" + hints[hints.length - 1] + "'"); 239 + 240 + console.log('\n All hints collected: [' + hints.join(', ') + ']'); 241 + console.log(" Expected: ['default', 'string', 'string', 'number']"); 242 + 243 + console.log('\n=== DONE ===');