[njs] Added support for template literals.

Dmitry Volyntsev xeioex at nginx.com
Mon Apr 22 18:19:49 UTC 2019


details:   https://hg.nginx.org/njs/rev/52983554e61f
branches:  
changeset: 918:52983554e61f
user:      hongzhidao <hongzhidao at gmail.com>
date:      Mon Apr 22 19:53:41 2019 +0300
description:
Added support for template literals.

What is supported:
1) Multiline strings
`string text line 1
string text line 2`

2) Expression interpolation
`string text ${expression} string text`

3) Nested templates
4) Tagged templates

This closes #107 issue on Github.

In collaboration with Artem S. Povalyukhin.

diffstat:

 njs/njs_disassembler.c      |    2 +
 njs/njs_generator.c         |   27 +++++
 njs/njs_lexer.c             |    2 +-
 njs/njs_lexer.h             |    3 +
 njs/njs_parser.h            |    2 +
 njs/njs_parser_expression.c |   10 +-
 njs/njs_parser_terminal.c   |  199 ++++++++++++++++++++++++++++++++++++++++++++
 njs/njs_string.c            |    2 +-
 njs/njs_string.h            |    4 +
 njs/njs_vm.c                |   33 +++++++
 njs/njs_vm.h                |    8 +
 njs/test/njs_unit_test.c    |   63 +++++++++++++
 12 files changed, 352 insertions(+), 3 deletions(-)

diffs (517 lines):

diff -r 31232e755143 -r 52983554e61f njs/njs_disassembler.c
--- a/njs/njs_disassembler.c	Sun Apr 21 18:11:58 2019 +0800
+++ b/njs/njs_disassembler.c	Mon Apr 22 19:53:41 2019 +0300
@@ -29,6 +29,8 @@ static njs_code_name_t  code_names[] = {
           nxt_string("ARGUMENTS       ") },
     { njs_vmcode_regexp, sizeof(njs_vmcode_regexp_t),
           nxt_string("REGEXP          ") },
+    { njs_vmcode_template_literal, sizeof(njs_vmcode_template_literal_t),
+          nxt_string("TEMPLATE LITERAL") },
     { njs_vmcode_object_copy, sizeof(njs_vmcode_object_copy_t),
           nxt_string("OBJECT COPY     ") },
 
diff -r 31232e755143 -r 52983554e61f njs/njs_generator.c
--- a/njs/njs_generator.c	Sun Apr 21 18:11:58 2019 +0800
+++ b/njs/njs_generator.c	Mon Apr 22 19:53:41 2019 +0300
@@ -123,6 +123,8 @@ static nxt_int_t njs_generate_function(n
     njs_parser_node_t *node);
 static nxt_int_t njs_generate_regexp(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node);
+static nxt_int_t njs_generate_template_literal(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
 static nxt_int_t njs_generate_test_jump_expression(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
 static nxt_int_t njs_generate_3addr_operation(njs_vm_t *vm,
@@ -404,6 +406,9 @@ njs_generator(njs_vm_t *vm, njs_generato
     case NJS_TOKEN_REGEXP:
         return njs_generate_regexp(vm, generator, node);
 
+    case NJS_TOKEN_TEMPLATE_LITERAL:
+        return njs_generate_template_literal(vm, generator, node);
+
     case NJS_TOKEN_THIS:
     case NJS_TOKEN_OBJECT_CONSTRUCTOR:
     case NJS_TOKEN_ARRAY_CONSTRUCTOR:
@@ -1969,6 +1974,28 @@ njs_generate_regexp(njs_vm_t *vm, njs_ge
 
 
 static nxt_int_t
+njs_generate_template_literal(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node)
+{
+    nxt_int_t                      ret;
+    njs_vmcode_template_literal_t  *code;
+
+    ret = njs_generator(vm, generator, node->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(generator, njs_vmcode_template_literal_t, code,
+                      njs_vmcode_template_literal, 1, 1);
+    code->retval = node->left->index;
+
+    node->index = node->left->index;
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
 njs_generate_test_jump_expression(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
diff -r 31232e755143 -r 52983554e61f njs/njs_lexer.c
--- a/njs/njs_lexer.c	Sun Apr 21 18:11:58 2019 +0800
+++ b/njs/njs_lexer.c	Mon Apr 22 19:53:41 2019 +0300
@@ -93,7 +93,7 @@ static const uint8_t  njs_tokens[256]  n
     /* \ ] */   NJS_TOKEN_ILLEGAL,           NJS_TOKEN_CLOSE_BRACKET,
     /* ^ _ */   NJS_TOKEN_BITWISE_XOR,       NJS_TOKEN_LETTER,
 
-    /* ` a */   NJS_TOKEN_ILLEGAL,           NJS_TOKEN_LETTER,
+    /* ` a */   NJS_TOKEN_GRAVE,             NJS_TOKEN_LETTER,
     /* b c */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
     /* d e */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
     /* f g */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
diff -r 31232e755143 -r 52983554e61f njs/njs_lexer.h
--- a/njs/njs_lexer.h	Sun Apr 21 18:11:58 2019 +0800
+++ b/njs/njs_lexer.h	Mon Apr 22 19:53:41 2019 +0300
@@ -127,6 +127,9 @@ typedef enum {
 
     NJS_TOKEN_ARRAY,
 
+    NJS_TOKEN_GRAVE,
+    NJS_TOKEN_TEMPLATE_LITERAL,
+
     NJS_TOKEN_FUNCTION,
     NJS_TOKEN_FUNCTION_EXPRESSION,
     NJS_TOKEN_FUNCTION_CALL,
diff -r 31232e755143 -r 52983554e61f njs/njs_parser.h
--- a/njs/njs_parser.h	Sun Apr 21 18:11:58 2019 +0800
+++ b/njs/njs_parser.h	Mon Apr 22 19:53:41 2019 +0300
@@ -90,6 +90,8 @@ njs_token_t njs_parser_arrow_expression(
 njs_token_t njs_parser_module_lambda(njs_vm_t *vm, njs_parser_t *parser);
 njs_token_t njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser,
     njs_token_t token);
+njs_token_t njs_parser_template_literal(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *parent);
 njs_parser_node_t *njs_parser_argument(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *expr, njs_index_t index);
 njs_token_t njs_parser_property_token(njs_vm_t *vm, njs_parser_t *parser);
diff -r 31232e755143 -r 52983554e61f njs/njs_parser_expression.c
--- a/njs/njs_parser_expression.c	Sun Apr 21 18:11:58 2019 +0800
+++ b/njs/njs_parser_expression.c	Mon Apr 22 19:53:41 2019 +0300
@@ -753,7 +753,7 @@ njs_parser_call_expression(njs_vm_t *vm,
             return token;
         }
 
-        if (token != NJS_TOKEN_OPEN_PARENTHESIS) {
+        if (token != NJS_TOKEN_OPEN_PARENTHESIS && token != NJS_TOKEN_GRAVE) {
             return token;
         }
 
@@ -828,6 +828,14 @@ njs_parser_call(njs_vm_t *vm, njs_parser
 
         break;
 
+    case NJS_TOKEN_GRAVE:
+        token = njs_parser_template_literal(vm, parser, func);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        break;
+
     default:
         break;
     }
diff -r 31232e755143 -r 52983554e61f njs/njs_parser_terminal.c
--- a/njs/njs_parser_terminal.c	Sun Apr 21 18:11:58 2019 +0800
+++ b/njs/njs_parser_terminal.c	Mon Apr 22 19:53:41 2019 +0300
@@ -24,6 +24,10 @@ static njs_token_t njs_parser_array(njs_
     njs_parser_node_t *array);
 static nxt_int_t njs_parser_array_item(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *array, njs_parser_node_t *value);
+static nxt_int_t njs_parser_template_expression(njs_vm_t *vm,
+    njs_parser_t *parser);
+static nxt_int_t njs_parser_template_string(njs_vm_t *vm,
+    njs_parser_t *parser);
 static njs_token_t njs_parser_escape_string_create(njs_vm_t *vm,
     njs_parser_t *parser, njs_value_t *value);
 
@@ -81,6 +85,16 @@ njs_parser_terminal(njs_vm_t *vm, njs_pa
 
         return njs_parser_array(vm, parser, node);
 
+    case NJS_TOKEN_GRAVE:
+        nxt_thread_log_debug("JS: TEMPLATE LITERAL");
+
+        node = njs_parser_node_new(vm, parser, NJS_TOKEN_TEMPLATE_LITERAL);
+        if (nxt_slow_path(node == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        return njs_parser_template_literal(vm, parser, node);
+
     case NJS_TOKEN_DIVISION:
         node = njs_parser_node_new(vm, parser, NJS_TOKEN_REGEXP);
         if (nxt_slow_path(node == NULL)) {
@@ -688,6 +702,191 @@ njs_parser_array_item(njs_vm_t *vm, njs_
 }
 
 
+njs_token_t
+njs_parser_template_literal(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *parent)
+{
+    uint8_t            tagged_template;
+    nxt_int_t          ret;
+    nxt_bool_t         expression;
+    njs_index_t        index;
+    njs_parser_node_t  *node, *array;
+
+    tagged_template = (parent->token != NJS_TOKEN_TEMPLATE_LITERAL);
+
+    index = NJS_SCOPE_CALLEE_ARGUMENTS;
+
+    array = njs_parser_node_new(vm, parser, NJS_TOKEN_ARRAY);
+    if (nxt_slow_path(array == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    if (tagged_template) {
+        node = njs_parser_argument(vm, parser, array, index);
+        if (nxt_slow_path(node == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        parent->right = node;
+        parent = node;
+
+        index += sizeof(njs_value_t);
+
+    } else {
+        parent->left = array;
+    }
+
+    expression = 0;
+
+    for ( ;; ) {
+        ret = expression ? njs_parser_template_expression(vm, parser)
+                         : njs_parser_template_string(vm, parser);
+
+        if (ret == NXT_ERROR) {
+            njs_parser_syntax_error(vm, parser,
+                                    "Unterminated template literal");
+            return NJS_TOKEN_ILLEGAL;
+        }
+
+        node = parser->node;
+
+        if (ret == NXT_DONE) {
+            ret = njs_parser_array_item(vm, parser, array, node);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return NJS_TOKEN_ERROR;
+            }
+
+            parser->node = parent;
+
+            return njs_parser_token(vm, parser);
+        }
+
+        /* NXT_OK */
+
+        if (tagged_template && expression) {
+            node = njs_parser_argument(vm, parser, node, index);
+            if (nxt_slow_path(node == NULL)) {
+                return NJS_TOKEN_ERROR;
+            }
+
+            parent->right = node;
+            parent = node;
+
+            index += sizeof(njs_value_t);
+
+        } else {
+            ret = njs_parser_array_item(vm, parser, array, node);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return NJS_TOKEN_ERROR;
+            }
+        }
+
+        expression = !expression;
+    }
+}
+
+
+static nxt_int_t
+njs_parser_template_expression(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_token_t  token;
+
+    token = njs_parser_token(vm, parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return NXT_ERROR;
+    }
+
+    token = njs_parser_expression(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return NXT_ERROR;
+    }
+
+    if (token != NJS_TOKEN_CLOSE_BRACE) {
+        njs_parser_syntax_error(vm, parser,
+                            "Missing \"}\" in template expression");
+        return NXT_ERROR;
+    }
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_parser_template_string(njs_vm_t *vm, njs_parser_t *parser)
+{
+    u_char             *p, c;
+    nxt_int_t          ret;
+    nxt_str_t          *text;
+    nxt_bool_t         escape;
+    njs_lexer_t        *lexer;
+    njs_parser_node_t  *node;
+
+    lexer = parser->lexer;
+    text = &lexer->lexer_token->text;
+
+    text->start = lexer->start;
+
+    escape = 0;
+    p = lexer->start;
+
+    while (p < lexer->end) {
+
+        c = *p++;
+
+        if (c == '\\') {
+            if (p == lexer->end) {
+                break;
+            }
+
+            p++;
+            escape = 1;
+
+            continue;
+        }
+
+        if (c == '`') {
+            text->length = p - text->start - 1;
+            goto done;
+        }
+
+        if (c == '$') {
+            if (p < lexer->end && *p == '{') {
+                p++;
+                text->length = p - text->start - 2;
+                goto done;
+            }
+        }
+    }
+
+    return NXT_ERROR;
+
+done:
+
+    node = njs_parser_node_new(vm, parser, NJS_TOKEN_STRING);
+    if (nxt_slow_path(node == NULL)) {
+        return NXT_ERROR;
+    }
+
+    if (escape) {
+        ret = njs_parser_escape_string_create(vm, parser, &node->u.value);
+        if (nxt_slow_path(ret != NJS_TOKEN_STRING)) {
+            return NXT_ERROR;
+        }
+
+    } else {
+        ret = njs_parser_string_create(vm, &node->u.value);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NXT_ERROR;
+        }
+    }
+
+    lexer->start = p;
+    parser->node = node;
+
+    return c == '`' ? NXT_DONE : NXT_OK;
+}
+
+
 nxt_int_t
 njs_parser_string_create(njs_vm_t *vm, njs_value_t *value)
 {
diff -r 31232e755143 -r 52983554e61f njs/njs_string.c
--- a/njs/njs_string.c	Sun Apr 21 18:11:58 2019 +0800
+++ b/njs/njs_string.c	Mon Apr 22 19:53:41 2019 +0300
@@ -822,7 +822,7 @@ njs_string_prototype_to_string(njs_vm_t 
  * JavaScript 1.2, ECMAScript 3.
  */
 
-static njs_ret_t
+njs_ret_t
 njs_string_prototype_concat(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
     njs_index_t unused)
 {
diff -r 31232e755143 -r 52983554e61f njs/njs_string.h
--- a/njs/njs_string.h	Sun Apr 21 18:11:58 2019 +0800
+++ b/njs/njs_string.h	Mon Apr 22 19:53:41 2019 +0300
@@ -193,6 +193,10 @@ njs_ret_t njs_string_decode_uri_componen
 njs_index_t njs_value_index(njs_vm_t *vm, const njs_value_t *src,
     nxt_uint_t runtime);
 
+njs_ret_t njs_string_prototype_concat(njs_vm_t *vm, njs_value_t *args,
+    nxt_uint_t nargs, njs_index_t unused);
+
+
 extern const njs_object_init_t  njs_string_constructor_init;
 extern const njs_object_init_t  njs_string_prototype_init;
 extern const njs_object_init_t  njs_string_instance_init;
diff -r 31232e755143 -r 52983554e61f njs/njs_vm.c
--- a/njs/njs_vm.c	Sun Apr 21 18:11:58 2019 +0800
+++ b/njs/njs_vm.c	Mon Apr 22 19:53:41 2019 +0300
@@ -458,6 +458,39 @@ njs_vmcode_regexp(njs_vm_t *vm, njs_valu
 
 
 njs_ret_t
+njs_vmcode_template_literal(njs_vm_t *vm, njs_value_t *invld1,
+    njs_value_t *retval)
+{
+    nxt_int_t    ret;
+    njs_array_t  *array;
+    njs_value_t  *value;
+
+    static const njs_function_t  concat = {
+          .native = 1,
+          .args_offset = 1,
+          .u.native = njs_string_prototype_concat
+    };
+
+    value = njs_vmcode_operand(vm, retval);
+
+    if (!njs_is_primitive(value)) {
+        array = value->data.u.array;
+
+        ret = njs_function_activate(vm, (njs_function_t *) &concat,
+                                    &njs_string_empty, array->start,
+                                    array->length, (njs_index_t) retval, 0);
+        if (ret == NJS_APPLIED) {
+            return 0;
+        }
+
+        return NXT_ERROR;
+    }
+
+    return sizeof(njs_vmcode_template_literal_t);
+}
+
+
+njs_ret_t
 njs_vmcode_object_copy(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
 {
     njs_object_t    *object;
diff -r 31232e755143 -r 52983554e61f njs/njs_vm.h
--- a/njs/njs_vm.h	Sun Apr 21 18:11:58 2019 +0800
+++ b/njs/njs_vm.h	Mon Apr 22 19:53:41 2019 +0300
@@ -657,6 +657,12 @@ typedef struct {
 
 
 typedef struct {
+     njs_vmcode_t              code;
+     njs_index_t               retval;
+} njs_vmcode_template_literal_t;
+
+
+typedef struct {
     njs_vmcode_t               code;
     njs_index_t                retval;
     njs_function_lambda_t      *lambda;
@@ -1161,6 +1167,8 @@ njs_ret_t njs_vmcode_arguments(njs_vm_t 
     njs_value_t *invld2);
 njs_ret_t njs_vmcode_regexp(njs_vm_t *vm, njs_value_t *inlvd1,
     njs_value_t *invld2);
+njs_ret_t njs_vmcode_template_literal(njs_vm_t *vm, njs_value_t *inlvd1,
+    njs_value_t *inlvd2);
 njs_ret_t njs_vmcode_object_copy(njs_vm_t *vm, njs_value_t *value,
     njs_value_t *invld);
 
diff -r 31232e755143 -r 52983554e61f njs/test/njs_unit_test.c
--- a/njs/test/njs_unit_test.c	Sun Apr 21 18:11:58 2019 +0800
+++ b/njs/test/njs_unit_test.c	Mon Apr 22 19:53:41 2019 +0300
@@ -4207,6 +4207,69 @@ static njs_unit_test_t  njs_test[] =
                  "a.sort(function(x, y) { return x - y })"),
       nxt_string("1,") },
 
+    /* Template literal. */
+
+    { nxt_string("`"),
+      nxt_string("SyntaxError: Unterminated template literal in 1") },
+
+    { nxt_string("`$"),
+      nxt_string("SyntaxError: Unterminated template literal in 1") },
+
+    { nxt_string("`${"),
+      nxt_string("SyntaxError: Unexpected end of input in 1") },
+
+    { nxt_string("`${a"),
+      nxt_string("SyntaxError: Missing \"}\" in template expression in 1") },
+
+    { nxt_string("`${}"),
+      nxt_string("SyntaxError: Unexpected token \"}\" in 1") },
+
+    { nxt_string("`${a}"),
+      nxt_string("SyntaxError: Unterminated template literal in 1") },
+
+    { nxt_string("`${a}bc"),
+      nxt_string("SyntaxError: Unterminated template literal in 1") },
+
+    { nxt_string("`\\"),
+      nxt_string("SyntaxError: Unterminated template literal in 1") },
+
+    { nxt_string("`\\${a}bc"),
+      nxt_string("SyntaxError: Unterminated template literal in 1") },
+
+    { nxt_string("`text1\ntext2`;"),
+      nxt_string("text1\ntext2") },
+
+    { nxt_string("var o = 1; `o = \\`${o}\\``"),
+      nxt_string("o = `1`") },
+
+    { nxt_string("`\\unicode`"),
+      nxt_string("SyntaxError: Invalid Unicode code point \"\\unicode\" in 1") },
+
+    { nxt_string("var a = 5; var b = 10;"
+                 "`Fifteen is ${a + b} and \nnot ${2 * a + b}.`;"),
+      nxt_string("Fifteen is 15 and \nnot 20.") },
+
+    { nxt_string("var s = `1undefined`; s;"),
+      nxt_string("1undefined") },
+
+    { nxt_string("var s = '0'; s = `x${s += '1'}`;"),
+      nxt_string("x01") },
+
+    { nxt_string("var d = new Date(2011, 5, 24, 18, 45, 12, 625);"
+                 "var something = 'test'; var one = 1; var two = 2;"
+                 "`[${d.toISOString()}] the message contents ${something} ${one + two}`"),
+      nxt_string("[2011-06-24T18:45:12.625Z] the message contents test 3") },
+
+    { nxt_string("function isLargeScreen() { return false; }"
+                 "var item = { isCollapsed: true };"
+                 "`header ${ isLargeScreen() ? '' : `icon-${item.isCollapsed ? 'expander' : 'collapser'}` }`;"),
+      nxt_string("header icon-expander") },
+
+    { nxt_string("function foo(strings, person, age) { return `${strings[0]}${strings[1]}${person}${age}` };"
+                 "var person = 'Mike'; var age = 21;"
+                 "foo`That ${person} is a ${age}`;"),
+      nxt_string("That  is a Mike21") },
+
     /* Strings. */
 
     { nxt_string("var a = '0123456789' + '012345';"


More information about the nginx-devel mailing list