[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