From: Artem S. Povalyukhin Date: Tue, 19 Nov 2019 17:45:22 +0000 (+0300) Subject: Added initial version of Symbol primitive type. X-Git-Tag: 0.3.8~62 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=c4f9d95d700c6d97b0cb43d3289517997f5dc289;p=njs.git Added initial version of Symbol primitive type. This closes #249 issue on Github. --- diff --git a/auto/sources b/auto/sources index 175400d0..675e45c6 100644 --- a/auto/sources +++ b/auto/sources @@ -25,6 +25,7 @@ NJS_LIB_SRCS=" \ src/njs_vmcode.c \ src/njs_boolean.c \ src/njs_number.c \ + src/njs_symbol.c \ src/njs_string.c \ src/njs_object.c \ src/njs_object_prop.c \ diff --git a/src/njs_builtin.c b/src/njs_builtin.c index a07c7f89..5d11f3d0 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -60,6 +60,7 @@ static const njs_object_type_init_t *const &njs_array_type_init, &njs_boolean_type_init, &njs_number_type_init, + &njs_symbol_type_init, &njs_string_type_init, &njs_function_type_init, &njs_regexp_type_init, @@ -1167,6 +1168,15 @@ static const njs_object_prop_t njs_global_this_object_properties[] = .configurable = 1, }, + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("Symbol"), + .value = njs_prop_handler2(njs_top_level_constructor, + NJS_OBJ_TYPE_SYMBOL, NJS_SYMBOL_HASH), + .writable = 1, + .configurable = 1, + }, + { .type = NJS_PROPERTY_HANDLER, .name = njs_string("String"), diff --git a/src/njs_json.c b/src/njs_json.c index 3f948f42..23142673 100644 --- a/src/njs_json.c +++ b/src/njs_json.c @@ -1933,6 +1933,33 @@ njs_dump_value(njs_json_stringify_t *stringify, const njs_value_t *value, break; + case NJS_OBJECT_SYMBOL: + value = njs_object_value(value); + + ret = njs_symbol_to_string(stringify->vm, &str_val, value); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + njs_string_get(&str_val, &str); + + njs_dump("[Symbol: "); + njs_json_buf_append(stringify, (char *) str.start, str.length); + njs_dump("]"); + + break; + + case NJS_SYMBOL: + ret = njs_symbol_to_string(stringify->vm, &str_val, value); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + njs_string_get(&str_val, &str); + njs_json_buf_append(stringify, (char *) str.start, str.length); + + break; + case NJS_OBJECT_NUMBER: value = njs_object_value(value); diff --git a/src/njs_main.h b/src/njs_main.h index 7ecb8b7d..4b9c3d17 100644 --- a/src/njs_main.h +++ b/src/njs_main.h @@ -52,6 +52,7 @@ #include #include +#include #include #include #include diff --git a/src/njs_object.c b/src/njs_object.c index 864298f7..2a5a87d0 100644 --- a/src/njs_object.c +++ b/src/njs_object.c @@ -1387,7 +1387,7 @@ static njs_int_t njs_object_get_prototype_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { - uint32_t index; + uint32_t index, type; njs_value_t *value; value = njs_arg(args, nargs, 1); @@ -1399,9 +1399,10 @@ njs_object_get_prototype_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, if (!njs_is_null_or_undefined(value)) { index = njs_primitive_prototype_index(value->type); + type = njs_is_symbol(value) ? NJS_OBJECT + : njs_object_value_type(value->type); - njs_set_type_object(&vm->retval, &vm->prototypes[index].object, - njs_object_value_type(value->type)); + njs_set_type_object(&vm->retval, &vm->prototypes[index].object, type); return NJS_OK; } @@ -2189,6 +2190,8 @@ static const njs_value_t njs_object_boolean_string = njs_long_string("[object Boolean]"); static const njs_value_t njs_object_number_string = njs_long_string("[object Number]"); +static const njs_value_t njs_object_symbol_string = + njs_long_string("[object Symbol]"); static const njs_value_t njs_object_string_string = njs_long_string("[object String]"); static const njs_value_t njs_object_data_string = @@ -2220,6 +2223,7 @@ njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args, &njs_object_undefined_string, &njs_object_boolean_string, &njs_object_number_string, + &njs_object_symbol_string, &njs_object_string_string, &njs_object_data_string, @@ -2232,13 +2236,13 @@ njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args, NULL, NULL, NULL, - NULL, /* Objects. */ &njs_object_object_string, &njs_object_array_string, &njs_object_boolean_string, &njs_object_number_string, + &njs_object_symbol_string, &njs_object_string_string, &njs_object_function_string, &njs_object_regexp_string, diff --git a/src/njs_object_hash.h b/src/njs_object_hash.h index 98e41a51..e5df7a35 100644 --- a/src/njs_object_hash.h +++ b/src/njs_object_hash.h @@ -421,6 +421,16 @@ 'S'), 't'), 'r'), 'i'), 'n'), 'g') +#define NJS_SYMBOL_HASH \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add(NJS_DJB_HASH_INIT, \ + 'S'), 'y'), 'm'), 'b'), 'o'), 'l') + + #define NJS_SYNTAX_ERROR_HASH \ njs_djb_hash_add( \ njs_djb_hash_add( \ diff --git a/src/njs_string.c b/src/njs_string.c index 698c3450..c13e1bb0 100644 --- a/src/njs_string.c +++ b/src/njs_string.c @@ -552,6 +552,10 @@ njs_string_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, value = &args[1]; if (njs_slow_path(!njs_is_string(value))) { + if (!vm->top_frame->ctor && njs_is_symbol(value)) { + return njs_symbol_to_string(vm, &vm->retval, value); + } + ret = njs_value_to_string(vm, value, value); if (njs_slow_path(ret != NJS_OK)) { return ret; @@ -4273,6 +4277,10 @@ njs_primitive_value_to_string(njs_vm_t *vm, njs_value_t *dst, case NJS_NUMBER: return njs_number_to_string(vm, dst, src); + case NJS_SYMBOL: + njs_symbol_conversion_failed(vm, 1); + return NJS_ERROR; + case NJS_STRING: /* GC: njs_retain(src); */ value = src; diff --git a/src/njs_symbol.c b/src/njs_symbol.c new file mode 100644 index 00000000..015f63c6 --- /dev/null +++ b/src/njs_symbol.c @@ -0,0 +1,441 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + + +#include + + +static const njs_value_t njs_symbol_async_iterator_name = + njs_long_string("Symbol.asyncIterator"); +static const njs_value_t njs_symbol_has_instance_name = + njs_long_string("Symbol.hasInstance"); +static const njs_value_t njs_symbol_is_concat_spreadable_name = + njs_long_string("Symbol.isConcatSpreadable"); +static const njs_value_t njs_symbol_iterator_name = + njs_long_string("Symbol.iterator"); +static const njs_value_t njs_symbol_match_name = + njs_string("Symbol.match"); +static const njs_value_t njs_symbol_match_all_name = + njs_long_string("Symbol.matchAll"); +static const njs_value_t njs_symbol_replace_name = + njs_string("Symbol.replace"); +static const njs_value_t njs_symbol_search_name = + njs_string("Symbol.search"); +static const njs_value_t njs_symbol_species_name = + njs_string("Symbol.species"); +static const njs_value_t njs_symbol_split_name = + njs_string("Symbol.split"); +static const njs_value_t njs_symbol_to_primitive_name = + njs_long_string("Symbol.toPrimitive"); +static const njs_value_t njs_symbol_to_string_tag_name = + njs_long_string("Symbol.toStringTag"); +static const njs_value_t njs_symbol_unscopables_name = + njs_long_string("Symbol.unscopables"); + + +static const njs_value_t *njs_symbol_names[NJS_SYMBOL_KNOWN_MAX] = { + &njs_string_invalid, + &njs_symbol_async_iterator_name, + &njs_symbol_has_instance_name, + &njs_symbol_is_concat_spreadable_name, + &njs_symbol_iterator_name, + &njs_symbol_match_name, + &njs_symbol_match_all_name, + &njs_symbol_replace_name, + &njs_symbol_search_name, + &njs_symbol_species_name, + &njs_symbol_split_name, + &njs_symbol_to_primitive_name, + &njs_symbol_to_string_tag_name, + &njs_symbol_unscopables_name, +}; + + +njs_int_t +njs_symbol_to_string(njs_vm_t *vm, njs_value_t *dst, const njs_value_t *value) +{ + u_char *start; + const njs_value_t *name; + njs_string_prop_t string; + + static const njs_value_t string_symbol = njs_string("Symbol()"); + + name = value->data.u.value; + + if (name == NULL) { + if (njs_fast_path(njs_symbol_key(value) < NJS_SYMBOL_KNOWN_MAX)) { + + name = njs_symbol_names[njs_symbol_key(value)]; + + } else { + *dst = string_symbol; + + return NJS_OK; + } + } + + (void) njs_string_prop(&string, name); + string.length += njs_length("Symbol()"); + + start = njs_string_alloc(vm, dst, string.size + 8, string.length); + if (njs_slow_path(start == NULL)) { + return NJS_ERROR; + } + + start = njs_cpymem(start, "Symbol(", 7); + start = njs_cpymem(start, string.start, string.size); + *start = ')'; + + return NJS_OK; +} + + +static njs_int_t +njs_symbol_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_int_t ret; + njs_value_t *value, *name; + uint64_t key; + + if (njs_slow_path(vm->top_frame->ctor)) { + njs_type_error(vm, "Symbol is not a constructor"); + return NJS_ERROR; + } + + value = njs_arg(args, nargs, 1); + + if (njs_is_undefined(value)) { + name = NULL; + + } else { + if (njs_slow_path(!njs_is_string(value))) { + ret = njs_value_to_string(vm, value, value); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + + name = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t)); + if (njs_slow_path(name == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + /* GC: retain */ + *name = *value; + } + + key = ++vm->symbol_generator; + + if (njs_slow_path(key >= UINT32_MAX)) { + njs_internal_error(vm, "Symbol generator overflow"); + return NJS_ERROR; + } + + vm->retval.type = NJS_SYMBOL; + vm->retval.data.truth = 1; + vm->retval.data.magic32 = key; + vm->retval.data.u.value = name; + + return NJS_OK; +} + + +static njs_int_t +njs_symbol_for(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + njs_internal_error(vm, "not implemented"); + + return NJS_ERROR; +} + + +static njs_int_t +njs_symbol_key_for(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + njs_internal_error(vm, "not implemented"); + + return NJS_ERROR; +} + + +static const njs_object_prop_t njs_symbol_constructor_properties[] = +{ + /* Symbol.name == "Symbol". */ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("Symbol"), + .configurable = 1, + }, + + /* Symbol.length == 0. */ + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 0, 0.0), + .configurable = 1, + }, + + /* Symbol.prototype. */ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, + + /* Symbol.for(). */ + { + .type = NJS_PROPERTY, + .name = njs_string("for"), + .value = njs_native_function(njs_symbol_for, 1), + .writable = 1, + .configurable = 1, + }, + + /* Symbol.keyFor(). */ + { + .type = NJS_PROPERTY, + .name = njs_string("keyFor"), + .value = njs_native_function(njs_symbol_key_for, 1), + .writable = 1, + .configurable = 1, + }, + + /* Symbol.asyncIterator. */ + { + .type = NJS_PROPERTY, + .name = njs_string("asyncIterator"), + .value = njs_wellknown_symbol(NJS_SYMBOL_ASYNC_ITERATOR), + }, + + /* Symbol.hasInstance. */ + { + .type = NJS_PROPERTY, + .name = njs_string("hasInstance"), + .value = njs_wellknown_symbol(NJS_SYMBOL_HAS_INSTANCE), + }, + + /* Symbol.isConcatSpreadable. */ + { + .type = NJS_PROPERTY, + .name = njs_long_string("isConcatSpreadable"), + .value = njs_wellknown_symbol(NJS_SYMBOL_IS_CONCAT_SPREADABLE), + }, + + /* Symbol.iterator. */ + { + .type = NJS_PROPERTY, + .name = njs_string("iterator"), + .value = njs_wellknown_symbol(NJS_SYMBOL_ITERATOR), + }, + + /* Symbol.match. */ + { + .type = NJS_PROPERTY, + .name = njs_string("match"), + .value = njs_wellknown_symbol(NJS_SYMBOL_MATCH), + }, + + /* Symbol.matchAll. */ + { + .type = NJS_PROPERTY, + .name = njs_string("matchAll"), + .value = njs_wellknown_symbol(NJS_SYMBOL_MATCH_ALL), + }, + + /* Symbol.replace. */ + { + .type = NJS_PROPERTY, + .name = njs_string("replace"), + .value = njs_wellknown_symbol(NJS_SYMBOL_REPLACE), + }, + + /* Symbol.search. */ + { + .type = NJS_PROPERTY, + .name = njs_string("search"), + .value = njs_wellknown_symbol(NJS_SYMBOL_SEARCH), + }, + + /* Symbol.species. */ + { + .type = NJS_PROPERTY, + .name = njs_string("species"), + .value = njs_wellknown_symbol(NJS_SYMBOL_SPECIES), + }, + + /* Symbol.split. */ + { + .type = NJS_PROPERTY, + .name = njs_string("split"), + .value = njs_wellknown_symbol(NJS_SYMBOL_SPLIT), + }, + + /* Symbol.toPrimitive. */ + { + .type = NJS_PROPERTY, + .name = njs_string("toPrimitive"), + .value = njs_wellknown_symbol(NJS_SYMBOL_TO_PRIMITIVE), + }, + + /* Symbol.toStringTag. */ + { + .type = NJS_PROPERTY, + .name = njs_string("toStringTag"), + .value = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG), + }, + + /* Symbol.unscopables. */ + { + .type = NJS_PROPERTY, + .name = njs_string("unscopables"), + .value = njs_wellknown_symbol(NJS_SYMBOL_UNSCOPABLES), + }, + +}; + + +const njs_object_init_t njs_symbol_constructor_init = { + njs_symbol_constructor_properties, + njs_nitems(njs_symbol_constructor_properties), +}; + + +static njs_int_t +njs_symbol_prototype_value_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_value_t *value; + + value = &args[0]; + + if (value->type != NJS_SYMBOL) { + + if (value->type == NJS_OBJECT_SYMBOL) { + value = njs_object_value(value); + + } else { + njs_type_error(vm, "unexpected value type:%s", + njs_type_string(value->type)); + + return NJS_ERROR; + } + } + + vm->retval = *value; + + return NJS_OK; +} + + +static njs_int_t +njs_symbol_prototype_to_string(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + njs_int_t ret; + + ret = njs_symbol_prototype_value_of(vm, args, nargs, unused); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + return njs_symbol_to_string(vm, &vm->retval, &vm->retval); +} + + +static njs_int_t +njs_symbol_prototype_description(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + njs_int_t ret; + const njs_value_t *value, *name; + + ret = njs_symbol_prototype_value_of(vm, args, nargs, unused); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + value = &vm->retval; + + name = value->data.u.value; + + if (name == NULL) { + if (njs_fast_path(njs_symbol_key(value) < NJS_SYMBOL_KNOWN_MAX)) { + name = njs_symbol_names[njs_symbol_key(value)]; + + } else { + name = &njs_value_undefined; + } + } + + vm->retval = *name; + + return NJS_OK; +} + + +static const njs_object_prop_t njs_symbol_prototype_properties[] = +{ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("__proto__"), + .value = njs_prop_handler(njs_primitive_prototype_get_proto), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("valueOf"), + .value = njs_native_function(njs_symbol_prototype_value_of, 0), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("toString"), + .value = njs_native_function(njs_symbol_prototype_to_string, 0), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("description"), + .value = njs_value(NJS_INVALID, 1, NAN), + .getter = njs_native_function(njs_symbol_prototype_description, 0), + .setter = njs_value(NJS_UNDEFINED, 0, NAN), + .writable = NJS_ATTRIBUTE_UNSET, + .configurable = 1, + }, +}; + + +const njs_object_init_t njs_symbol_prototype_init = { + njs_symbol_prototype_properties, + njs_nitems(njs_symbol_prototype_properties), +}; + + +const njs_object_type_init_t njs_symbol_type_init = { + .constructor = njs_symbol_constructor, + .prototype_props = &njs_symbol_prototype_init, + .constructor_props = &njs_symbol_constructor_init, + .value = { .object = { .type = NJS_OBJECT } }, +}; diff --git a/src/njs_symbol.h b/src/njs_symbol.h new file mode 100644 index 00000000..22f60d29 --- /dev/null +++ b/src/njs_symbol.h @@ -0,0 +1,36 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NJS_SYMBOL_H_INCLUDED_ +#define _NJS_SYMBOL_H_INCLUDED_ + +typedef enum { + NJS_SYMBOL_INVALID = 0, + NJS_SYMBOL_ASYNC_ITERATOR = 1, + NJS_SYMBOL_HAS_INSTANCE = 2, + NJS_SYMBOL_IS_CONCAT_SPREADABLE = 3, + NJS_SYMBOL_ITERATOR = 4, + NJS_SYMBOL_MATCH = 5, + NJS_SYMBOL_MATCH_ALL = 6, + NJS_SYMBOL_REPLACE = 7, + NJS_SYMBOL_SEARCH = 8, + NJS_SYMBOL_SPECIES = 9, + NJS_SYMBOL_SPLIT = 10, + NJS_SYMBOL_TO_PRIMITIVE = 11, + NJS_SYMBOL_TO_STRING_TAG = 12, + NJS_SYMBOL_UNSCOPABLES = 13, +#define NJS_SYMBOL_KNOWN_MAX (NJS_SYMBOL_UNSCOPABLES + 1) +} njs_wellknown_symbol_t; + + +njs_int_t njs_symbol_to_string(njs_vm_t *vm, njs_value_t *dst, + const njs_value_t *value); + + +extern const njs_object_type_init_t njs_symbol_type_init; + + +#endif /* _NJS_SYMBOL_H_INCLUDED_ */ diff --git a/src/njs_value.c b/src/njs_value.c index 3dd425f1..f102a33b 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -47,6 +47,7 @@ const njs_value_t njs_string_minus_infinity = const njs_value_t njs_string_plus_infinity = njs_string("Infinity"); const njs_value_t njs_string_nan = njs_string("NaN"); +const njs_value_t njs_string_symbol = njs_string("symbol"); const njs_value_t njs_string_string = njs_string("string"); const njs_value_t njs_string_name = njs_string("name"); const njs_value_t njs_string_data = njs_string("data"); @@ -312,6 +313,9 @@ njs_type_string(njs_value_type_t type) case NJS_NUMBER: return "number"; + case NJS_SYMBOL: + return "symbol"; + case NJS_STRING: return "string"; @@ -333,6 +337,9 @@ njs_type_string(njs_value_type_t type) case NJS_OBJECT_NUMBER: return "object number"; + case NJS_OBJECT_SYMBOL: + return "object symbol"; + case NJS_OBJECT_STRING: return "object string"; @@ -514,6 +521,7 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value, case NJS_BOOLEAN: case NJS_NUMBER: + case NJS_SYMBOL: index = njs_primitive_prototype_index(value->type); obj = &vm->prototypes[index].object; break; @@ -534,6 +542,7 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value, case NJS_ARRAY: case NJS_OBJECT_BOOLEAN: case NJS_OBJECT_NUMBER: + case NJS_OBJECT_SYMBOL: case NJS_OBJECT_STRING: case NJS_REGEXP: case NJS_DATE: @@ -1206,3 +1215,11 @@ njs_value_to_object(njs_vm_t *vm, njs_value_t *value) return NJS_ERROR; } + +void +njs_symbol_conversion_failed(njs_vm_t *vm, njs_bool_t to_string) +{ + njs_type_error(vm, to_string + ? "Cannot convert a Symbol value to a string" + : "Cannot convert a Symbol value to a number"); +} diff --git a/src/njs_value.h b/src/njs_value.h index ed909f3f..d464b839 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -32,14 +32,16 @@ typedef enum { * a numeric value of the null and false values is zero, * a numeric value of the void value is NaN. */ - NJS_STRING = 0x04, + NJS_SYMBOL = 0x04, + + NJS_STRING = 0x05, /* The order of the above type is used in njs_is_primitive(). */ - NJS_DATA = 0x05, + NJS_DATA = 0x06, /* The type is external code. */ - NJS_EXTERNAL = 0x06, + NJS_EXTERNAL = 0x07, /* * The invalid value type is used: @@ -47,7 +49,7 @@ typedef enum { * to detect non-declared explicitly or implicitly variables, * for native property getters. */ - NJS_INVALID = 0x07, + NJS_INVALID = 0x08, /* * The object types are >= NJS_OBJECT, this is used in njs_is_object(). @@ -60,12 +62,13 @@ typedef enum { NJS_ARRAY = 0x11, NJS_OBJECT_BOOLEAN = 0x12, NJS_OBJECT_NUMBER = 0x13, - NJS_OBJECT_STRING = 0x14, - NJS_FUNCTION = 0x15, - NJS_REGEXP = 0x16, - NJS_DATE = 0x17, - NJS_OBJECT_VALUE = 0x18, -#define NJS_VALUE_TYPE_MAX (NJS_OBJECT_VALUE + 1) + NJS_OBJECT_SYMBOL = 0x14, + NJS_OBJECT_STRING = 0x15, + NJS_FUNCTION = 0x16, + NJS_REGEXP = 0x17, + NJS_DATE = 0x18, + NJS_OBJECT_VALUE = 0x19, + NJS_VALUE_TYPE_MAX } njs_value_type_t; @@ -367,6 +370,16 @@ typedef struct { } +#define njs_wellknown_symbol(key) { \ + .data = { \ + .type = NJS_SYMBOL, \ + .truth = 1, \ + .magic32 = key, \ + .u = { .value = NULL } \ + } \ +} + + #define njs_string(s) { \ .short_string = { \ .type = NJS_STRING, \ @@ -480,6 +493,10 @@ typedef struct { ((value)->type <= NJS_NUMBER) +#define njs_is_symbol(value) \ + ((value)->type == NJS_SYMBOL) + + #define njs_is_string(value) \ ((value)->type == NJS_STRING) @@ -661,6 +678,14 @@ typedef struct { *(value) = njs_value_false +#define njs_symbol_key(value) \ + ((value)->data.magic32) + + +#define njs_symbol_eq(value1, value2) \ + (njs_symbol_key(value1) == njs_symbol_key(value2)) + + extern const njs_value_t njs_value_null; extern const njs_value_t njs_value_undefined; extern const njs_value_t njs_value_false; @@ -681,6 +706,7 @@ extern const njs_value_t njs_string_minus_zero; extern const njs_value_t njs_string_minus_infinity; extern const njs_value_t njs_string_plus_infinity; extern const njs_value_t njs_string_nan; +extern const njs_value_t njs_string_symbol; extern const njs_value_t njs_string_string; extern const njs_value_t njs_string_data; extern const njs_value_t njs_string_name; @@ -874,6 +900,10 @@ njs_int_t njs_value_to_object(njs_vm_t *vm, njs_value_t *value); #include "njs_number.h" +void +njs_symbol_conversion_failed(njs_vm_t *vm, njs_bool_t to_string); + + njs_inline njs_int_t njs_value_to_number(njs_vm_t *vm, njs_value_t *value, double *dst) { @@ -890,6 +920,12 @@ njs_value_to_number(njs_vm_t *vm, njs_value_t *value, double *dst) } if (njs_slow_path(!njs_is_numeric(value))) { + + if (njs_slow_path(njs_is_symbol(value))) { + njs_symbol_conversion_failed(vm, 0); + return NJS_ERROR; + } + *dst = NAN; if (njs_is_string(value)) { @@ -1014,12 +1050,18 @@ njs_value_to_string(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value) njs_value_t primitive; if (njs_slow_path(!njs_is_primitive(value))) { - ret = njs_value_to_primitive(vm, &primitive, value, 1); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } + if (njs_slow_path(value->type == NJS_OBJECT_SYMBOL)) { + /* should fail */ + value = njs_object_value(value); - value = &primitive; + } else { + ret = njs_value_to_primitive(vm, &primitive, value, 1); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + value = &primitive; + } } return njs_primitive_value_to_string(vm, dst, value); @@ -1047,6 +1089,10 @@ njs_values_strict_equal(const njs_value_t *val1, const njs_value_t *val2) return njs_string_eq(val1, val2); } + if (njs_is_symbol(val1)) { + return njs_symbol_eq(val1, val2); + } + return (njs_object(val1) == njs_object(val2)); } diff --git a/src/njs_vm.c b/src/njs_vm.c index d4c56358..354a49da 100644 --- a/src/njs_vm.c +++ b/src/njs_vm.c @@ -92,6 +92,8 @@ njs_vm_create(njs_vm_opt_t *options) } } + vm->symbol_generator = NJS_SYMBOL_KNOWN_MAX; + return vm; } diff --git a/src/njs_vm.h b/src/njs_vm.h index 5b3398b8..748f678e 100644 --- a/src/njs_vm.h +++ b/src/njs_vm.h @@ -84,6 +84,7 @@ typedef enum { NJS_OBJ_TYPE_ARRAY, NJS_OBJ_TYPE_BOOLEAN, NJS_OBJ_TYPE_NUMBER, + NJS_OBJ_TYPE_SYMBOL, NJS_OBJ_TYPE_STRING, NJS_OBJ_TYPE_FUNCTION, NJS_OBJ_TYPE_REGEXP, @@ -232,6 +233,8 @@ struct njs_vm_s { * and NJS_PROPERTY_QUERY_DELETE modes. */ uintptr_t stash; /* njs_property_query_t * */ + + uint64_t symbol_generator; }; diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index af8ef290..925b3307 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -261,6 +261,16 @@ next: value2 = &primitive2; } + if (njs_slow_path(njs_is_symbol(value1) + || njs_is_symbol(value2))) + { + njs_symbol_conversion_failed(vm, + (op == NJS_VMCODE_ADDITION) && + (njs_is_string(value1) || njs_is_string(value2))); + + goto error; + } + retval = njs_vmcode_operand(vm, vmcode->operand1); if (op == NJS_VMCODE_ADDITION) { @@ -1397,6 +1407,7 @@ njs_vmcode_typeof(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld) &njs_string_undefined, &njs_string_boolean, &njs_string_number, + &njs_string_symbol, &njs_string_string, &njs_string_data, &njs_string_external, @@ -1408,13 +1419,13 @@ njs_vmcode_typeof(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld) &njs_string_undefined, &njs_string_undefined, &njs_string_undefined, - &njs_string_undefined, &njs_string_object, &njs_string_object, &njs_string_object, &njs_string_object, &njs_string_object, + &njs_string_object, &njs_string_function, &njs_string_object, &njs_string_object, @@ -1494,10 +1505,14 @@ again: return njs_string_eq(val1, val2); } + if (njs_is_symbol(val1)) { + return njs_symbol_eq(val1, val2); + } + return (njs_object(val1) == njs_object(val2)); } - /* Sort values as: numeric < string < objects. */ + /* Sort values as: numeric < symbol < string < objects. */ if (val1->type > val2->type) { hv = val1; @@ -1513,12 +1528,18 @@ again: return 0; } - /* If "hv" is a string then "lv" can only be a numeric. */ + /* If "hv" is a symbol then "lv" can only be a numeric. */ + if (njs_is_symbol(hv)) { + return 0; + } + + /* If "hv" is a string then "lv" can be a numeric or symbol. */ if (njs_is_string(hv)) { - return (njs_number(lv) == njs_string_to_number(hv, 0)); + return !njs_is_symbol(lv) + && (njs_number(lv) == njs_string_to_number(hv, 0)); } - /* "hv" is an object and "lv" is either a string or a numeric. */ + /* "hv" is an object and "lv" is either a string or a symbol or a numeric. */ ret = njs_value_to_primitive(vm, &primitive, hv, 0); if (ret != NJS_OK) { diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index b0642690..f1b8f59e 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -9996,6 +9996,265 @@ static njs_unit_test_t njs_test[] = njs_str("true") }, #endif + /* Symbol */ + + { njs_str("typeof Symbol"), + njs_str("function") }, + + { njs_str("this.Symbol === Symbol"), + njs_str("true") }, + + { njs_str("Symbol.name"), + njs_str("Symbol") }, + + { njs_str("Object.getOwnPropertyDescriptor(Symbol, 'name').configurable"), + njs_str("true") }, + + { njs_str("Symbol.length"), + njs_str("0") }, + + { njs_str("Object.getOwnPropertyDescriptor(Symbol, 'length').configurable"), + njs_str("true") }, + + { njs_str("typeof Symbol.for"), + njs_str("function") }, + + { njs_str("Symbol.for.length"), + njs_str("1") }, + + { njs_str("typeof Symbol.keyFor"), + njs_str("function") }, + + { njs_str("Symbol.keyFor.length"), + njs_str("1") }, + + { njs_str("Symbol.prototype.constructor === Symbol"), + njs_str("true") }, + + { njs_str("Symbol.prototype.__proto__ === Object.prototype"), + njs_str("true") }, + + { njs_str("Object.prototype.toString.call(Symbol.prototype)"), + njs_str("[object Object]") }, + + { njs_str("Symbol.prototype.toString()"), + njs_str("TypeError: unexpected value type:object") }, + + { njs_str("new Symbol()"), + njs_str("TypeError: Symbol is not a constructor") }, + + { njs_str("typeof Symbol()"), + njs_str("symbol") }, + + { njs_str("typeof Symbol('desc')"), + njs_str("symbol") }, + + { njs_str("Symbol() === Symbol()"), + njs_str("false") }, + + { njs_str("Symbol('desc') === Symbol('desc')"), + njs_str("false") }, + + { njs_str("Symbol() == Symbol()"), + njs_str("false") }, + + { njs_str("Symbol('desc') == Symbol('desc')"), + njs_str("false") }, + + { njs_str("Symbol() == true"), + njs_str("false") }, + + { njs_str("Symbol() == false"), + njs_str("false") }, + + { njs_str("Symbol() != true"), + njs_str("true") }, + + { njs_str("Symbol() != 0"), + njs_str("true") }, + + { njs_str("Symbol() != ''"), + njs_str("true") }, + + { njs_str("Symbol() != undefined"), + njs_str("true") }, + + { njs_str("typeof Object(Symbol())"), + njs_str("object") }, + + { njs_str("typeof Object(Symbol('desc'))"), + njs_str("object") }, + + { njs_str("var x = Symbol(), o = Object(x); x == o"), + njs_str("true") }, + + { njs_str("var x = Symbol(), o = Object(x); o == x"), + njs_str("true") }, + + { njs_str("var x = Symbol(), o = Object(x); x !== o"), + njs_str("true") }, + + { njs_str("var x = Symbol(), o = Object(x); x === o.valueOf()"), + njs_str("true") }, + + { njs_str("var x = Symbol(); Object(x) == Object(x)"), + njs_str("false") }, + + { njs_str("!Symbol()"), + njs_str("false") }, + + { njs_str("!!Symbol()"), + njs_str("true") }, + + { njs_str("(Symbol('a') && Symbol('b')).toString()"), + njs_str("Symbol(b)") }, + + { njs_str("(Symbol('a') || Symbol('b')).toString()"), + njs_str("Symbol(a)") }, + + { njs_str("+Symbol()"), + njs_str("TypeError: Cannot convert a Symbol value to a number") }, + + { njs_str("-Symbol()"), + njs_str("TypeError: Cannot convert a Symbol value to a number") }, + + { njs_str("~Symbol()"), + njs_str("TypeError: Cannot convert a Symbol value to a number") }, + + { njs_str("var x = Symbol(); ++x"), + njs_str("TypeError: Cannot convert a Symbol value to a number") }, + + { njs_str("var x = Symbol(); x++"), + njs_str("TypeError: Cannot convert a Symbol value to a number") }, + + { njs_str("var x = Symbol(); --x"), + njs_str("TypeError: Cannot convert a Symbol value to a number") }, + + { njs_str("var x = Symbol(); x--"), + njs_str("TypeError: Cannot convert a Symbol value to a number") }, + + { njs_str("var msg = 'Cannot convert a Symbol value to a number'," + " ops = ['+', '-', '*', '**', '/', '%', '>>>', '>>', '<<'," + " '&', '|', '^', '<', '<=', '>', '>=']," + " test = (lhs, rhs, cls, msg, stop, op) => {" + " if (stop) {" + " return stop;" + " }" + " var op = `${lhs} ${op} ${rhs}`;" + " try {" + " (new Function(op))();" + " } catch (e) {" + " if (e instanceof cls && msg == e.message) {" + " return '';" + " }" + " }" + " return `'${op}' - failed`;" + " };" + "ops.reduce(test.bind({}, 'Symbol()', '42', TypeError, msg), '') ||" + "ops.reduce(test.bind({}, '42', 'Symbol()', TypeError, msg), '');"), + njs_str("") }, + + { njs_str("Symbol() > 'abc'"), + njs_str("TypeError: Cannot convert a Symbol value to a number") }, + + { njs_str("'abc' > Symbol()"), + njs_str("TypeError: Cannot convert a Symbol value to a number") }, + + { njs_str("'abc' + Symbol()"), + njs_str("TypeError: Cannot convert a Symbol value to a string") }, + + { njs_str("Symbol() + 'abc'"), + njs_str("TypeError: Cannot convert a Symbol value to a string") }, + + { njs_str("Math.min(Symbol())"), + njs_str("TypeError: Cannot convert a Symbol value to a number") }, + + { njs_str("[Symbol(), Symbol()].join()"), + njs_str("TypeError: Cannot convert a Symbol value to a string") }, + + { njs_str("Symbol().toString()"), + njs_str("Symbol()") }, + + { njs_str("Symbol('desc').toString()"), + njs_str("Symbol(desc)") }, + + { njs_str("Symbol('α'.repeat(16)).toString()"), + njs_str("Symbol(αααααααααααααααα)") }, + + { njs_str("Symbol(undefined).toString()"), + njs_str("Symbol()") }, + + { njs_str("Symbol(null).toString()"), + njs_str("Symbol(null)") }, + + { njs_str("Symbol(123).toString()"), + njs_str("Symbol(123)") }, + + { njs_str("Symbol(false).toString()"), + njs_str("Symbol(false)") }, + + { njs_str("Symbol(Symbol()).toString()"), + njs_str("TypeError: Cannot convert a Symbol value to a string") }, + + { njs_str("var all = [ 'asyncIterator'," + " 'hasInstance'," + " 'isConcatSpreadable'," + " 'iterator'," + " 'match'," + " 'matchAll'," + " 'replace'," + " 'search'," + " 'species'," + " 'split'," + " 'toPrimitive'," + " 'toStringTag'," + " 'unscopables' ]; " + "Object.getOwnPropertyNames(Symbol)" + ".filter((x) => typeof Symbol[x] == 'symbol')" + ".length == all.length"), + njs_str("true") }, + + { njs_str("var all = [ 'asyncIterator'," + " 'hasInstance'," + " 'isConcatSpreadable'," + " 'iterator'," + " 'match'," + " 'matchAll'," + " 'replace'," + " 'search'," + " 'species'," + " 'split'," + " 'toPrimitive'," + " 'toStringTag'," + " 'unscopables' ]; " + "Object.getOwnPropertyNames(Symbol)" + ".filter((x) => typeof Symbol[x] == 'symbol')" + ".filter((x, i) => !all.includes(x))"), + njs_str("") }, + + { njs_str("Object.getOwnPropertyNames(Symbol)" + ".filter((x) => typeof Symbol[x] == 'symbol')" + ".map(x => ({ k: x, v: Symbol[x] }))" + ".every((x) => 'Symbol(Symbol.' + x.k + ')' == String(x.v))"), + njs_str("true") }, + + { njs_str("typeof Symbol.prototype.description"), + njs_str("TypeError: unexpected value type:object") }, + + { njs_str("Symbol.prototype.description = 1"), + njs_str("TypeError: Cannot set property \"description\" of object which has only a getter") }, + + { njs_str("typeof Symbol().description"), + njs_str("undefined") }, + + { njs_str("Symbol('desc').description"), + njs_str("desc") }, + + { njs_str("Symbol.iterator.description"), + njs_str("Symbol.iterator") }, + + /* String */ + { njs_str("String()"), njs_str("") }, @@ -10011,6 +10270,18 @@ static njs_unit_test_t njs_test[] = { njs_str("new String(123)"), njs_str("123") }, + { njs_str("String(Symbol())"), + njs_str("Symbol()") }, + + { njs_str("String(Symbol('desc'))"), + njs_str("Symbol(desc)") }, + + { njs_str("new String(Symbol())"), + njs_str("TypeError: Cannot convert a Symbol value to a string") }, + + { njs_str("String(Object(Symbol()))"), + njs_str("TypeError: Cannot convert a Symbol value to a string") }, + { njs_str("Object('123').length"), njs_str("3") }, @@ -10340,6 +10611,12 @@ static njs_unit_test_t njs_test[] = { njs_str("Object.prototype.toString.call(new Object(1))"), njs_str("[object Number]") }, + { njs_str("Object.prototype.toString.call(new Object(Symbol()))"), + njs_str("[object Symbol]") }, + + { njs_str("Object.prototype.toString.call(new Object(Symbol('desc')))"), + njs_str("[object Symbol]") }, + { njs_str("Object.prototype.toString.call(new Object(''))"), njs_str("[object String]") }, @@ -11064,7 +11341,7 @@ static njs_unit_test_t njs_test[] = "Object.getPrototypeOf(o) === Object.prototype"), njs_str("true") }, - { njs_str("[true, 42, '' /*, Symbol()*/]" + { njs_str("[true, 42, '', Symbol()]" ".every((x) => Object.getPrototypeOf(x) == Object.getPrototypeOf(Object(x)))"), njs_str("true") }, @@ -11582,6 +11859,7 @@ static njs_unit_test_t njs_test[] = " 'prototype'," " 'caller'," " 'arguments'," + " 'description'," " ];" " return Object.getOwnPropertyNames(o)" " .filter(v => !except.includes(v)" @@ -11593,6 +11871,7 @@ static njs_unit_test_t njs_test[] = "[" " Boolean, Boolean.prototype," " Number, Number.prototype," + " Symbol, Symbol.prototype," " String, String.prototype," " Object, Object.prototype," " Array, Array.prototype," @@ -11613,6 +11892,7 @@ static njs_unit_test_t njs_test[] = " 'prototype'," " 'caller'," " 'arguments'," + " 'description'," " ];" " return Object.getOwnPropertyNames(o)" " .filter(v => !except.includes(v)" @@ -11624,6 +11904,7 @@ static njs_unit_test_t njs_test[] = "[" " Boolean, Boolean.prototype," " Number, Number.prototype," + " Symbol, Symbol.prototype," " String, String.prototype," " Object, Object.prototype," " Array, Array.prototype," @@ -14207,6 +14488,24 @@ static njs_unit_test_t njs_test[] = { njs_str("njs.dump([0, -0])"), njs_str("[0,-0]") }, + { njs_str("njs.dump(Symbol())"), + njs_str("Symbol()") }, + + { njs_str("njs.dump(Object(Symbol()))"), + njs_str("[Symbol: Symbol()]") }, + + { njs_str("njs.dump(Symbol('desc'))"), + njs_str("Symbol(desc)") }, + + { njs_str("njs.dump(Object(Symbol('desc')))"), + njs_str("[Symbol: Symbol(desc)]") }, + + { njs_str("njs.dump(Symbol.iterator)"), + njs_str("Symbol(Symbol.iterator)") }, + + { njs_str("njs.dump(Object(Symbol.iterator))"), + njs_str("[Symbol: Symbol(Symbol.iterator)]") }, + /* Built-in methods name. */ { njs_str( @@ -14217,6 +14516,7 @@ static njs_unit_test_t njs_test[] = " 'constructor'," " 'caller'," " 'arguments'," + " 'description'," " ];" " return Object.getOwnPropertyNames(o)" " .filter(v => !except.includes(v)" @@ -14227,6 +14527,7 @@ static njs_unit_test_t njs_test[] = "[" " Boolean, Boolean.prototype," " Number, Number.prototype," + " Symbol, Symbol.prototype," " String, String.prototype," " Object, Object.prototype," " Array, Array.prototype,"