[njs] Added initial version of Symbol primitive type.

Dmitry Volyntsev xeioex at nginx.com
Thu Nov 21 17:57:34 UTC 2019


details:   https://hg.nginx.org/njs/rev/263c75a40999
branches:  
changeset: 1247:263c75a40999
user:      Artem S. Povalyukhin <artem.povaluhin at gmail.com>
date:      Tue Nov 19 20:45:22 2019 +0300
description:
Added initial version of Symbol primitive type.

This closes #249 issue on Github.

diffstat:

 auto/sources             |    1 +
 src/njs_builtin.c        |   10 +
 src/njs_json.c           |   27 ++
 src/njs_main.h           |    1 +
 src/njs_object.c         |   14 +-
 src/njs_object_hash.h    |   10 +
 src/njs_string.c         |    8 +
 src/njs_symbol.c         |  441 +++++++++++++++++++++++++++++++++++++++++++++++
 src/njs_symbol.h         |   36 +++
 src/njs_value.c          |   17 +
 src/njs_value.h          |   76 ++++++-
 src/njs_vm.c             |    2 +
 src/njs_vm.h             |    3 +
 src/njs_vmcode.c         |   33 ++-
 src/test/njs_unit_test.c |  303 ++++++++++++++++++++++++++++++++-
 15 files changed, 955 insertions(+), 27 deletions(-)

diffs (truncated from 1392 to 1000 lines):

diff -r 884d3a86beab -r 263c75a40999 auto/sources
--- a/auto/sources	Tue Nov 19 19:32:26 2019 +0300
+++ b/auto/sources	Tue Nov 19 20:45:22 2019 +0300
@@ -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 -r 884d3a86beab -r 263c75a40999 src/njs_builtin.c
--- a/src/njs_builtin.c	Tue Nov 19 19:32:26 2019 +0300
+++ b/src/njs_builtin.c	Tue Nov 19 20:45:22 2019 +0300
@@ -60,6 +60,7 @@ static const njs_object_type_init_t *con
     &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,
@@ -1169,6 +1170,15 @@ static const njs_object_prop_t  njs_glob
 
     {
         .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"),
         .value = njs_prop_handler2(njs_top_level_constructor,
                                    NJS_OBJ_TYPE_STRING, NJS_STRING_HASH),
diff -r 884d3a86beab -r 263c75a40999 src/njs_json.c
--- a/src/njs_json.c	Tue Nov 19 19:32:26 2019 +0300
+++ b/src/njs_json.c	Tue Nov 19 20:45:22 2019 +0300
@@ -1933,6 +1933,33 @@ njs_dump_value(njs_json_stringify_t *str
 
         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 -r 884d3a86beab -r 263c75a40999 src/njs_main.h
--- a/src/njs_main.h	Tue Nov 19 19:32:26 2019 +0300
+++ b/src/njs_main.h	Tue Nov 19 20:45:22 2019 +0300
@@ -52,6 +52,7 @@
 
 #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>
diff -r 884d3a86beab -r 263c75a40999 src/njs_object.c
--- a/src/njs_object.c	Tue Nov 19 19:32:26 2019 +0300
+++ b/src/njs_object.c	Tue Nov 19 20:45:22 2019 +0300
@@ -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
 
     if (!njs_is_null_or_undefined(value)) {
         index = njs_primitive_prototype_index(value->type);
-
-        njs_set_type_object(&vm->retval, &vm->prototypes[index].object,
-                            njs_object_value_type(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, type);
 
         return NJS_OK;
     }
@@ -2189,6 +2190,8 @@ static const njs_value_t  njs_object_boo
                                      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 
         &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 
         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 -r 884d3a86beab -r 263c75a40999 src/njs_object_hash.h
--- a/src/njs_object_hash.h	Tue Nov 19 19:32:26 2019 +0300
+++ b/src/njs_object_hash.h	Tue Nov 19 20:45:22 2019 +0300
@@ -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 -r 884d3a86beab -r 263c75a40999 src/njs_string.c
--- a/src/njs_string.c	Tue Nov 19 19:32:26 2019 +0300
+++ b/src/njs_string.c	Tue Nov 19 20:45:22 2019 +0300
@@ -552,6 +552,10 @@ njs_string_constructor(njs_vm_t *vm, njs
         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 *
     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 -r 884d3a86beab -r 263c75a40999 src/njs_symbol.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/njs_symbol.c	Tue Nov 19 20:45:22 2019 +0300
@@ -0,0 +1,441 @@
+
+/*
+ * 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 } },
+};
diff -r 884d3a86beab -r 263c75a40999 src/njs_symbol.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/njs_symbol.h	Tue Nov 19 20:45:22 2019 +0300
@@ -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 -r 884d3a86beab -r 263c75a40999 src/njs_value.c
--- a/src/njs_value.c	Tue Nov 19 19:32:26 2019 +0300
+++ b/src/njs_value.c	Tue Nov 19 20:45:22 2019 +0300
@@ -47,6 +47,7 @@ const njs_value_t  njs_string_minus_infi
 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_pro
 
     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_pro
     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_va
 
     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 -r 884d3a86beab -r 263c75a40999 src/njs_value.h
--- a/src/njs_value.h	Tue Nov 19 19:32:26 2019 +0300
+++ b/src/njs_value.h	Tue Nov 19 20:45:22 2019 +0300
@@ -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_min
 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 *
 #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_va
     }
 
     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_va
     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);
+
+        } else {
+            ret = njs_value_to_primitive(vm, &primitive, value, 1);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return ret;
+            }
+
+            value = &primitive;
         }
-
-        value = &primitive;
     }
 
     return njs_primitive_value_to_string(vm, dst, value);
@@ -1047,6 +1089,10 @@ njs_values_strict_equal(const njs_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));
 }
 
diff -r 884d3a86beab -r 263c75a40999 src/njs_vm.c
--- a/src/njs_vm.c	Tue Nov 19 19:32:26 2019 +0300
+++ b/src/njs_vm.c	Tue Nov 19 20:45:22 2019 +0300
@@ -92,6 +92,8 @@ njs_vm_create(njs_vm_opt_t *options)
         }
     }
 
+    vm->symbol_generator = NJS_SYMBOL_KNOWN_MAX;
+
     return vm;
 }
 
diff -r 884d3a86beab -r 263c75a40999 src/njs_vm.h
--- a/src/njs_vm.h	Tue Nov 19 19:32:26 2019 +0300
+++ b/src/njs_vm.h	Tue Nov 19 20:45:22 2019 +0300
@@ -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 -r 884d3a86beab -r 263c75a40999 src/njs_vmcode.c
--- a/src/njs_vmcode.c	Tue Nov 19 19:32:26 2019 +0300
+++ b/src/njs_vmcode.c	Tue Nov 19 20:45:22 2019 +0300
@@ -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_valu
         &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_valu
         &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 (njs_is_string(hv)) {
-        return (njs_number(lv) == njs_string_to_number(hv, 0));
+    /* If "hv" is a symbol then "lv" can only be a numeric. */
+    if (njs_is_symbol(hv)) {
+        return 0;
     }
 
-    /* "hv" is an object and "lv" is either a string or a numeric. */
+    /* If "hv" is a string then "lv" can be a numeric or symbol. */


More information about the nginx-devel mailing list