This closes #249 issue on Github.
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 \
&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,
.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"),
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);
#include <njs_boolean.h>
#include <njs_number.h>
+#include <njs_symbol.h>
#include <njs_string.h>
#include <njs_object.h>
#include <njs_object_hash.h>
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);
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;
}
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 =
&njs_object_undefined_string,
&njs_object_boolean_string,
&njs_object_number_string,
+ &njs_object_symbol_string,
&njs_object_string_string,
&njs_object_data_string,
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,
'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( \
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;
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;
--- /dev/null
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+
+#include <njs_main.h>
+
+
+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 } },
+};
--- /dev/null
+
+/*
+ * 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_ */
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");
case NJS_NUMBER:
return "number";
+ case NJS_SYMBOL:
+ return "symbol";
+
case NJS_STRING:
return "string";
case NJS_OBJECT_NUMBER:
return "object number";
+ case NJS_OBJECT_SYMBOL:
+ return "object symbol";
+
case NJS_OBJECT_STRING:
return "object string";
case NJS_BOOLEAN:
case NJS_NUMBER:
+ case NJS_SYMBOL:
index = njs_primitive_prototype_index(value->type);
obj = &vm->prototypes[index].object;
break;
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:
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");
+}
* 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:
* 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().
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;
}
+#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, \
((value)->type <= NJS_NUMBER)
+#define njs_is_symbol(value) \
+ ((value)->type == NJS_SYMBOL)
+
+
#define njs_is_string(value) \
((value)->type == NJS_STRING)
*(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;
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;
#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)
{
}
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)) {
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);
return njs_string_eq(val1, val2);
}
+ if (njs_is_symbol(val1)) {
+ return njs_symbol_eq(val1, val2);
+ }
+
return (njs_object(val1) == njs_object(val2));
}
}
}
+ vm->symbol_generator = NJS_SYMBOL_KNOWN_MAX;
+
return vm;
}
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,
* and NJS_PROPERTY_QUERY_DELETE modes.
*/
uintptr_t stash; /* njs_property_query_t * */
+
+ uint64_t symbol_generator;
};
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) {
&njs_string_undefined,
&njs_string_boolean,
&njs_string_number,
+ &njs_string_symbol,
&njs_string_string,
&njs_string_data,
&njs_string_external,
&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,
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;
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) {
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("") },
{ 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") },
{ 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]") },
"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") },
" 'prototype',"
" 'caller',"
" 'arguments',"
+ " 'description',"
" ];"
" return Object.getOwnPropertyNames(o)"
" .filter(v => !except.includes(v)"
"["
" Boolean, Boolean.prototype,"
" Number, Number.prototype,"
+ " Symbol, Symbol.prototype,"
" String, String.prototype,"
" Object, Object.prototype,"
" Array, Array.prototype,"
" 'prototype',"
" 'caller',"
" 'arguments',"
+ " 'description',"
" ];"
" return Object.getOwnPropertyNames(o)"
" .filter(v => !except.includes(v)"
"["
" Boolean, Boolean.prototype,"
" Number, Number.prototype,"
+ " Symbol, Symbol.prototype,"
" String, String.prototype,"
" Object, Object.prototype,"
" Array, Array.prototype,"
{ 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(
" 'constructor',"
" 'caller',"
" 'arguments',"
+ " 'description',"
" ];"
" return Object.getOwnPropertyNames(o)"
" .filter(v => !except.includes(v)"
"["
" Boolean, Boolean.prototype,"
" Number, Number.prototype,"
+ " Symbol, Symbol.prototype,"
" String, String.prototype,"
" Object, Object.prototype,"
" Array, Array.prototype,"