Skip to content

Instantly share code, notes, and snippets.

@lexborisov
Created April 12, 2021 07:08
Show Gist options
  • Save lexborisov/f313e33ee9971d29287951d33ba256c2 to your computer and use it in GitHub Desktop.
Save lexborisov/f313e33ee9971d29287951d33ba256c2 to your computer and use it in GitHub Desktop.
# HG changeset patch
# User Alexander Borisov <alexander.borisov@nginx.com>
# Date 1618211209 -10800
# Mon Apr 12 10:06:49 2021 +0300
# Node ID c77b6d6fdfee365deb8bf3180ee8b00918619905
# Parent 8583f3bdaeb94e039de420d1cdeba302ab5fcdd4
Introduced let implementation.
diff -r 8583f3bdaeb9 -r c77b6d6fdfee src/njs_builtin.c
--- a/src/njs_builtin.c Mon Apr 12 10:05:59 2021 +0300
+++ b/src/njs_builtin.c Mon Apr 12 10:06:49 2021 +0300
@@ -967,6 +967,11 @@ njs_global_this_prop_handler(njs_vm_t *v
}
node = (njs_variable_node_t *) rb_node;
+
+ if (node->variable->type == NJS_VARIABLE_LET) {
+ return NJS_DECLINED;
+ }
+
value = njs_scope_valid_value(vm, node->variable->index);
if (setval != NULL) {
diff -r 8583f3bdaeb9 -r c77b6d6fdfee src/njs_disassembler.c
--- a/src/njs_disassembler.c Mon Apr 12 10:05:59 2021 +0300
+++ b/src/njs_disassembler.c Mon Apr 12 10:06:49 2021 +0300
@@ -135,6 +135,18 @@ static njs_code_name_t code_names[] = {
{ NJS_VMCODE_THROW, sizeof(njs_vmcode_throw_t),
njs_str("THROW ") },
+
+ { NJS_VMCODE_LET, sizeof(njs_vmcode_variable_t),
+ njs_str("LET ") },
+
+ { NJS_VMCODE_LET_UPDATE, sizeof(njs_vmcode_variable_t),
+ njs_str("LET UPDATE ") },
+
+ { NJS_VMCODE_REFERENCE, sizeof(njs_vmcode_variable_t),
+ njs_str("REFERENCE ") },
+
+ { NJS_VMCODE_NOT_INITIALIZATION, sizeof(njs_vmcode_variable_t),
+ njs_str("NOT INIT ") },
};
diff -r 8583f3bdaeb9 -r c77b6d6fdfee src/njs_generator.c
--- a/src/njs_generator.c Mon Apr 12 10:05:59 2021 +0300
+++ b/src/njs_generator.c Mon Apr 12 10:06:49 2021 +0300
@@ -67,6 +67,8 @@ static njs_int_t njs_generate_variable(n
njs_variable_t **retvar);
static njs_int_t njs_generate_var_statement(njs_vm_t *vm,
njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_let(njs_vm_t *vm, njs_generator_t *generator,
+ njs_parser_node_t *node, njs_variable_t *var);
static njs_int_t njs_generate_if_statement(njs_vm_t *vm,
njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_cond_expression(njs_vm_t *vm,
@@ -79,6 +81,10 @@ static njs_int_t njs_generate_do_while_s
njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_for_statement(njs_vm_t *vm,
njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_for_let_update(njs_vm_t *vm,
+ njs_generator_t *generator, njs_parser_node_t *node, size_t depth);
+static njs_int_t njs_generate_for_resolve_closure(njs_vm_t *vm,
+ njs_parser_node_t *node, size_t depth);
static njs_int_t njs_generate_for_in_statement(njs_vm_t *vm,
njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_start_block(njs_vm_t *vm,
@@ -261,6 +267,9 @@ static njs_int_t njs_generate_reference_
##__VA_ARGS__)
+#define NJS_GENERATE_MAX_DEPTH 4096
+
+
static const njs_str_t no_label = njs_str("");
static const njs_str_t return_label = njs_str("@return");
/* GCC and Clang complain about NULL argument passed to memcmp(). */
@@ -277,6 +286,7 @@ njs_generate(njs_vm_t *vm, njs_generator
switch (node->token_type) {
case NJS_TOKEN_VAR:
+ case NJS_TOKEN_LET:
return njs_generate_var_statement(vm, generator, node);
case NJS_TOKEN_IF:
@@ -479,7 +489,7 @@ njs_generator(njs_vm_t *vm, njs_generato
{
njs_int_t ret;
- if (njs_slow_path(generator->count++ > 4096)) {
+ if (njs_slow_path(generator->count++ > NJS_GENERATE_MAX_DEPTH)) {
njs_range_error(vm, "Maximum call stack size exceeded");
return NJS_ERROR;
}
@@ -591,6 +601,7 @@ njs_generate_name(njs_vm_t *vm, njs_gene
njs_parser_node_t *node)
{
njs_variable_t *var;
+ njs_vmcode_variable_t *code;
njs_vmcode_function_t *function;
var = njs_variable_reference(vm, node);
@@ -609,6 +620,23 @@ njs_generate_name(njs_vm_t *vm, njs_gene
function->lambda = var->value.data.u.lambda;
var->init = 1;
+
+ } else if (var->type == NJS_VARIABLE_LET) {
+ if (var->check == NJS_VARIABLE_CHECK_PROGRESS) {
+ njs_generate_code(generator, njs_vmcode_variable_t, code,
+ NJS_VMCODE_NOT_INITIALIZATION, 0, node);
+ code->dst = node->index;
+
+ return NJS_OK;
+ }
+
+ if (var->check == NJS_VARIABLE_NOT_CHECK) {
+ njs_generate_code(generator, njs_vmcode_variable_t, code,
+ NJS_VMCODE_REFERENCE, 0, node);
+ code->dst = node->index;
+
+ var->check = NJS_VARIABLE_CHECK;
+ }
}
return NJS_OK;
@@ -620,6 +648,7 @@ njs_generate_variable(njs_vm_t *vm, njs_
njs_parser_node_t *node, njs_reference_type_t type, njs_variable_t **retvar)
{
njs_variable_t *var;
+ njs_vmcode_variable_t *code;
njs_vmcode_function_t *function;
var = njs_variable_reference(vm, node);
@@ -640,19 +669,28 @@ njs_generate_variable(njs_vm_t *vm, njs_
}
}
- if (var->init || node->index != var->index) {
+ if (var->init) {
return NJS_OK;
}
- if (var->have_lambda) {
+ if (var->have_lambda && node->index == var->index) {
njs_generate_code(generator, njs_vmcode_function_t, function,
NJS_VMCODE_FUNCTION, 1, node);
function->retval = node->index;
function->lambda = var->value.data.u.lambda;
+
+ var->init = 1;
+
+ } else if (var->type == NJS_VARIABLE_LET
+ && var->check == NJS_VARIABLE_NOT_CHECK && retvar == NULL)
+ {
+ njs_generate_code(generator, njs_vmcode_variable_t, code,
+ NJS_VMCODE_REFERENCE, 0, node);
+ code->dst = node->index;
+
+ var->check = NJS_VARIABLE_CHECK;
}
- var->init = 1;
-
return NJS_OK;
}
@@ -673,21 +711,32 @@ njs_generate_var_statement(njs_vm_t *vm,
return NJS_ERROR;
}
+ ret = njs_generate_let(vm, generator, node, var);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
lvalue->index = var->index;
expr = node->right;
if (expr == NULL) {
/* Variable is only declared. */
+ var->init = 1;
return NJS_OK;
}
expr->dest = lvalue;
+ var->check = NJS_VARIABLE_CHECK_PROGRESS;
+
ret = njs_generator(vm, generator, expr);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
+ var->init = 1;
+ var->check = NJS_VARIABLE_CHECK;
+
/*
* lvalue and expression indexes are equal if the expression is an
* empty object or expression result is stored directly in variable.
@@ -705,6 +754,22 @@ njs_generate_var_statement(njs_vm_t *vm,
static njs_int_t
+njs_generate_let(njs_vm_t *vm, njs_generator_t *generator,
+ njs_parser_node_t *node, njs_variable_t *var)
+{
+ njs_vmcode_variable_t *code;
+
+ if (var->type == NJS_VARIABLE_LET) {
+ njs_generate_code(generator, njs_vmcode_variable_t, code,
+ NJS_VMCODE_LET, 0, node);
+ code->dst = var->index;
+ }
+
+ return NJS_OK;
+}
+
+
+static njs_int_t
njs_generate_if_statement(njs_vm_t *vm, njs_generator_t *generator,
njs_parser_node_t *node)
{
@@ -1101,7 +1166,7 @@ njs_generate_for_statement(njs_vm_t *vm,
{
njs_int_t ret;
njs_jump_off_t jump_offset, loop_offset;
- njs_parser_node_t *condition, *update;
+ njs_parser_node_t *condition, *update, *init;
njs_vmcode_jump_t *jump;
njs_vmcode_cond_jump_t *cond_jump;
@@ -1125,9 +1190,20 @@ njs_generate_for_statement(njs_vm_t *vm,
return ret;
}
+ init = node->left;
node = node->right;
condition = node->left;
+ /*
+ * Closures can occur in conditional and loop updates. This must be
+ * foreseen in order to generate optimized code for let updates.
+ */
+
+ ret = njs_generate_for_resolve_closure(vm, condition, generator->count);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
/* GCC complains about uninitialized jump_offset. */
jump_offset = 0;
@@ -1154,10 +1230,20 @@ njs_generate_for_statement(njs_vm_t *vm,
/* The loop update. */
+ update = node->right;
+
+ ret = njs_generate_for_resolve_closure(vm, update, generator->count);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ ret = njs_generate_for_let_update(vm, generator, init, generator->count);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
njs_generate_patch_block(vm, generator, generator->block->continuation);
- update = node->right;
-
ret = njs_generator(vm, generator, update);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
@@ -1198,13 +1284,95 @@ njs_generate_for_statement(njs_vm_t *vm,
static njs_int_t
+njs_generate_for_let_update(njs_vm_t *vm, njs_generator_t *generator,
+ njs_parser_node_t *node, size_t depth)
+{
+ njs_parser_node_t *let;
+ njs_vmcode_variable_t *code_var;
+ njs_variable_reference_t *ref;
+
+ if (node == NULL) {
+ return NJS_OK;
+ }
+
+ if (depth >= NJS_GENERATE_MAX_DEPTH) {
+ return NJS_ERROR;
+ }
+
+ if (node->token_type != NJS_TOKEN_STATEMENT) {
+ return NJS_OK;
+ }
+
+ let = node->right;
+
+ if (let->token_type != NJS_TOKEN_LET) {
+ return NJS_OK;
+ }
+
+ ref = &let->left->u.reference;
+
+ if (ref->variable->closure) {
+ njs_generate_code(generator, njs_vmcode_variable_t, code_var,
+ NJS_VMCODE_LET_UPDATE, 0, let);
+ code_var->dst = let->left->index;
+ }
+
+ return njs_generate_for_let_update(vm, generator, node->left, depth + 1);
+}
+
+
+static njs_int_t
+njs_generate_for_resolve_closure(njs_vm_t *vm, njs_parser_node_t *node,
+ size_t depth)
+{
+ njs_int_t ret;
+ njs_bool_t closure;
+ njs_variable_t *var;
+
+ if (node == NULL) {
+ return NJS_OK;
+ }
+
+ if (node->token_type == NJS_TOKEN_NAME) {
+ var = njs_variable_resolve(vm, node);
+
+ if (njs_fast_path(var != NULL)) {
+ closure = njs_variable_closure_test(node->scope, var->scope);
+
+ if (closure) {
+ var->closure = 1;
+ }
+ }
+ }
+
+ if (depth >= NJS_GENERATE_MAX_DEPTH) {
+ njs_range_error(vm, "Maximum call stack size exceeded");
+ return NJS_ERROR;
+ }
+
+ ret = njs_generate_for_resolve_closure(vm, node->left, depth + 1);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ ret = njs_generate_for_resolve_closure(vm, node->right, depth + 1);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ return NJS_OK;
+}
+
+
+static njs_int_t
njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator,
njs_parser_node_t *node)
{
njs_int_t ret;
njs_index_t index;
+ njs_variable_t *var;
njs_jump_off_t loop_offset, prop_offset;
- njs_parser_node_t *foreach;
+ njs_parser_node_t *foreach, *name;
njs_vmcode_prop_next_t *prop_next;
njs_vmcode_prop_foreach_t *prop_foreach;
@@ -1217,15 +1385,38 @@ njs_generate_for_in_statement(njs_vm_t *
/* The object. */
foreach = node->left;
-
- ret = njs_generator(vm, generator, foreach->left);
- if (njs_slow_path(ret != NJS_OK)) {
- return ret;
- }
-
- ret = njs_generator(vm, generator, foreach->right);
- if (njs_slow_path(ret != NJS_OK)) {
- return ret;
+ name = foreach->left->right;
+
+ if (name != NULL) {
+ name = name->left;
+
+ ret = njs_generate_variable(vm, generator, name, NJS_DECLARATION, &var);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ foreach->left->index = name->index;
+
+ var->check = NJS_VARIABLE_CHECK_PROGRESS;
+
+ ret = njs_generator(vm, generator, foreach->right);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ var->init = 1;
+ var->check = NJS_VARIABLE_CHECK;
+
+ } else {
+ ret = njs_generator(vm, generator, foreach->left);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ ret = njs_generator(vm, generator, foreach->right);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
}
njs_generate_code(generator, njs_vmcode_prop_foreach_t, prop_foreach,
@@ -1251,6 +1442,14 @@ njs_generate_for_in_statement(njs_vm_t *
/* The loop iterator. */
+ if (name != NULL) {
+ ret = njs_generate_for_let_update(vm, generator, foreach->left,
+ generator->count);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+ }
+
njs_generate_patch_block(vm, generator, generator->block->continuation);
njs_code_set_jump_offset(generator, njs_vmcode_prop_foreach_t, prop_offset);
diff -r 8583f3bdaeb9 -r c77b6d6fdfee src/njs_parser.c
--- a/src/njs_parser.c Mon Apr 12 10:05:59 2021 +0300
+++ b/src/njs_parser.c Mon Apr 12 10:06:49 2021 +0300
@@ -238,6 +238,8 @@ static njs_int_t njs_parser_statement_li
static njs_int_t njs_parser_statement_list_item(njs_parser_t *parser,
njs_lexer_token_t *token, njs_queue_link_t *current);
+static njs_int_t njs_parser_lexical_declaration(njs_parser_t *parser,
+ njs_lexer_token_t *token, njs_queue_link_t *current);
static njs_int_t njs_parser_variable_statement(njs_parser_t *parser,
njs_lexer_token_t *token, njs_queue_link_t *current);
static njs_int_t njs_parser_variable_declaration_list(njs_parser_t *parser,
@@ -287,7 +289,8 @@ static njs_int_t njs_parser_iteration_st
static njs_int_t njs_parser_iteration_statement_for_map(njs_parser_t *parser,
njs_lexer_token_t *token, njs_queue_link_t *current);
static njs_int_t njs_parser_for_var_binding_or_var_list(njs_parser_t *parser,
- njs_lexer_token_t *token, njs_queue_link_t *current);
+ njs_lexer_token_t *token, njs_queue_link_t *current,
+ njs_token_type_t token_type);
static njs_int_t njs_parser_for_var_in_statement(njs_parser_t *parser,
njs_lexer_token_t *token, njs_queue_link_t *current);
static njs_int_t njs_parser_for_var_in_statement_after(njs_parser_t *parser,
@@ -506,8 +509,9 @@ njs_parser_reject(njs_parser_t *parser)
njs_int_t
njs_parser(njs_vm_t *vm, njs_parser_t *parser)
{
- njs_int_t ret;
- njs_lexer_token_t *token;
+ njs_int_t ret;
+ njs_lexer_token_t *token;
+ const njs_lexer_keyword_entry_t *keyword;
parser->vm = vm;
@@ -527,6 +531,15 @@ njs_parser(njs_vm_t *vm, njs_parser_t *p
parser->level = 1;
}
+ /* Add this as first variable. */
+ keyword = njs_lexer_keyword(njs_string_undefined.short_string.start,
+ njs_string_undefined.short_string.length);
+ if (njs_slow_path(keyword == NULL)) {
+ return NJS_ERROR;
+ }
+
+ parser->undefined_id = (uintptr_t) keyword->value;
+
njs_queue_init(&parser->stack);
parser->target = NULL;
@@ -746,14 +759,6 @@ njs_parser_class_declaration(njs_parser_
static njs_int_t
-njs_parser_lexical_declaration(njs_parser_t *parser, njs_lexer_token_t *token,
- njs_queue_link_t *current)
-{
- return njs_parser_not_supported(parser, token);
-}
-
-
-static njs_int_t
njs_parser_function_or_generator(njs_parser_t *parser,
njs_lexer_token_t *token, njs_queue_link_t *current)
{
@@ -899,12 +904,14 @@ njs_parser_expression_parenthesis(njs_pa
static njs_int_t
-njs_parser_set_line_state(njs_parser_t *parser,
+njs_parser_iteration_statement_for_end(njs_parser_t *parser,
njs_lexer_token_t *token, njs_queue_link_t *current)
{
parser->node->token_line = (uint32_t) (uintptr_t) parser->target;
parser->target = NULL;
+ njs_parser_scope_end(parser);
+
return njs_parser_stack_pop(parser);
}
@@ -4543,7 +4550,7 @@ njs_parser_declaration(njs_parser_t *par
switch (token->type) {
case NJS_TOKEN_CLASS:
njs_parser_next(parser, njs_parser_class_declaration);
- break;
+ return NJS_OK;
case NJS_TOKEN_LET:
case NJS_TOKEN_CONST:
@@ -4573,7 +4580,8 @@ njs_parser_declaration(njs_parser_t *par
return NJS_DECLINED;
}
- return NJS_OK;
+ return njs_parser_after(parser, current, parser->node, 1,
+ njs_parser_statement_after);
}
@@ -4736,12 +4744,32 @@ njs_parser_statement_list_item(njs_parse
/*
+ * 13.3.1 Let and Const Declarations
+ */
+static njs_int_t
+njs_parser_lexical_declaration(njs_parser_t *parser, njs_lexer_token_t *token,
+ njs_queue_link_t *current)
+{
+ parser->var_type = (token->type == NJS_TOKEN_LET) ? NJS_VARIABLE_LET
+ : NJS_VARIABLE_CONST;
+
+ njs_lexer_consume_token(parser->lexer, 1);
+
+ njs_parser_next(parser, njs_parser_variable_declaration_list);
+
+ return njs_parser_after(parser, current, NULL, 1, njs_parser_semicolon);
+}
+
+
+/*
* 13.3.2 Variable Statement
*/
static njs_int_t
njs_parser_variable_statement(njs_parser_t *parser, njs_lexer_token_t *token,
njs_queue_link_t *current)
{
+ parser->var_type = NJS_VARIABLE_VAR;
+
njs_parser_next(parser, njs_parser_variable_declaration_list);
return njs_parser_after(parser, current, NULL, 1, njs_parser_semicolon);
@@ -4792,6 +4820,7 @@ njs_parser_variable_declaration(njs_pars
{
njs_int_t ret;
njs_variable_t *var;
+ njs_token_type_t type;
njs_parser_node_t *name;
ret = njs_parser_binding_pattern(parser, token, current);
@@ -4810,14 +4839,14 @@ njs_parser_variable_declaration(njs_pars
return NJS_DONE;
}
- name = njs_parser_variable_node(parser, token->unique_id, NJS_VARIABLE_VAR,
+ name = njs_parser_variable_node(parser, token->unique_id, parser->var_type,
&var);
if (name == NULL) {
return NJS_ERROR;
}
if (var->self) {
- var->type = NJS_VARIABLE_VAR;
+ var->type = parser->var_type;
var->self = 0;
var->init = 0;
}
@@ -4833,7 +4862,21 @@ njs_parser_variable_declaration(njs_pars
return NJS_ERROR;
}
- ret = njs_parser_initializer_assign(parser, NJS_TOKEN_VAR);
+ switch (parser->var_type) {
+ case NJS_VARIABLE_LET:
+ type = NJS_TOKEN_LET;
+ break;
+
+ case NJS_VARIABLE_CONST:
+ type = NJS_TOKEN_CONST;
+ break;
+
+ default:
+ type = NJS_TOKEN_VAR;
+ break;
+ }
+
+ ret = njs_parser_initializer_assign(parser, type);
if (ret != NJS_OK) {
return ret;
}
@@ -4934,6 +4977,12 @@ njs_parser_expression_statement(njs_pars
return NJS_ERROR;
}
+ if (token->type == NJS_TOKEN_NAME) {
+ njs_parser_syntax_error(parser, "let declaration cannot appear "
+ "in a single-statement context");
+ return NJS_DONE;
+ }
+
if (token->type == NJS_TOKEN_OPEN_BRACKET) {
return njs_parser_failed(parser);
}
@@ -5176,14 +5225,21 @@ static njs_int_t
njs_parser_iteration_statement_for(njs_parser_t *parser,
njs_lexer_token_t *token, njs_queue_link_t *current)
{
+ njs_int_t ret;
+
if (token->type == NJS_TOKEN_OPEN_PARENTHESIS) {
njs_lexer_consume_token(parser->lexer, 1);
+ ret = njs_parser_scope_begin(parser, NJS_SCOPE_BLOCK, 0);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
njs_parser_next(parser, njs_parser_iteration_statement_for_map);
return njs_parser_after(parser, current,
(void *) (uintptr_t) parser->line, 1,
- njs_parser_set_line_state);
+ njs_parser_iteration_statement_for_end);
}
if (token->type == NJS_TOKEN_AWAIT) {
@@ -5198,8 +5254,9 @@ static njs_int_t
njs_parser_iteration_statement_for_map(njs_parser_t *parser,
njs_lexer_token_t *token, njs_queue_link_t *current)
{
- njs_int_t ret;
- njs_str_t *text;
+ njs_int_t ret;
+ njs_str_t *text;
+ njs_token_type_t token_type;
/*
* "var" <VariableDeclarationList> ";" <Expression>? ";" <Expression>? ")"
@@ -5246,6 +5303,9 @@ njs_parser_iteration_statement_for_map(n
return NJS_OK;
case NJS_TOKEN_VAR:
+ case NJS_TOKEN_LET:
+ token_type = token->type;
+
token = njs_lexer_peek_token(parser->lexer, token, 0);
if (token == NULL) {
return NJS_ERROR;
@@ -5253,7 +5313,8 @@ njs_parser_iteration_statement_for_map(n
njs_lexer_consume_token(parser->lexer, 1);
- ret = njs_parser_for_var_binding_or_var_list(parser, token, current);
+ ret = njs_parser_for_var_binding_or_var_list(parser, token,
+ current, token_type);
if (ret != NJS_OK) {
if (ret == NJS_DONE) {
return NJS_OK;
@@ -5264,7 +5325,6 @@ njs_parser_iteration_statement_for_map(n
break;
- case NJS_TOKEN_LET:
case NJS_TOKEN_CONST:
return njs_parser_not_supported(parser, token);
@@ -5292,11 +5352,23 @@ njs_parser_iteration_statement_for_map(n
static njs_int_t
njs_parser_for_var_binding_or_var_list(njs_parser_t *parser,
- njs_lexer_token_t *token, njs_queue_link_t *current)
-{
- njs_int_t ret;
- njs_lexer_token_t *next;
- njs_parser_node_t *node, *var;
+ njs_lexer_token_t *token, njs_queue_link_t *current,
+ njs_token_type_t token_type)
+{
+ njs_int_t ret;
+ njs_lexer_token_t *next;
+ njs_parser_node_t *node, *var, *node_type, *statement;
+ njs_variable_type_t type;
+
+ switch (token_type) {
+ case NJS_TOKEN_LET:
+ type = NJS_VARIABLE_LET;
+ break;
+
+ default:
+ type = NJS_VARIABLE_VAR;
+ break;
+ }
switch (token->type) {
/* BindingPattern */
@@ -5322,18 +5394,33 @@ njs_parser_for_var_binding_or_var_list(n
}
if (next->type != NJS_TOKEN_IN) {
+ parser->var_type = type;
+
njs_parser_next(parser, njs_parser_variable_declaration_list);
return NJS_OK;
}
+ statement = njs_parser_node_new(parser, NJS_TOKEN_STATEMENT);
+ if (njs_slow_path(statement == NULL)) {
+ return NJS_ERROR;
+ }
+
+ node_type = njs_parser_node_new(parser, token_type);
+ if (njs_slow_path(node_type == NULL)) {
+ return NJS_ERROR;
+ }
+
var = njs_parser_variable_node(parser, token->unique_id,
- NJS_VARIABLE_VAR, NULL);
+ type, NULL);
if (var == NULL) {
return NJS_ERROR;
}
+ node_type->token_line = token->line;
var->token_line = token->line;
+ statement->right = node_type;
+ node_type->left = var;
parser->node = NULL;
node = njs_parser_node_new(parser, NJS_TOKEN_IN);
@@ -5342,7 +5429,7 @@ njs_parser_for_var_binding_or_var_list(n
}
node->token_line = next->line;
- node->left = var;
+ node->left = statement;
njs_parser_next(parser, njs_parser_expression);
@@ -8414,6 +8501,7 @@ njs_parser_variable_reference(njs_parser
rb_parse_node->key = unique_id;
rb_parse_node->index = NJS_INDEX_NONE;
+ rb_parse_node->check = 0;
njs_rbtree_insert(&scope->references, &rb_parse_node->node);
diff -r 8583f3bdaeb9 -r c77b6d6fdfee src/njs_parser.h
--- a/src/njs_parser.h Mon Apr 12 10:05:59 2021 +0300
+++ b/src/njs_parser.h Mon Apr 12 10:06:49 2021 +0300
@@ -76,7 +76,9 @@ struct njs_parser_s {
njs_parser_node_t *node;
njs_parser_node_t *target;
njs_parser_scope_t *scope;
+ njs_variable_type_t var_type;
njs_int_t ret;
+ uintptr_t undefined_id;
njs_bool_t strict_semicolon;
uint32_t line;
uint8_t level;
@@ -97,6 +99,7 @@ typedef struct {
NJS_RBTREE_NODE (node);
uintptr_t key;
njs_index_t index;
+ njs_bool_t check;
} njs_parser_rbtree_node_t;
@@ -109,6 +112,8 @@ njs_int_t njs_parser(njs_vm_t *vm, njs_p
njs_int_t njs_parser_module_lambda(njs_parser_t *parser,
njs_lexer_token_t *token, njs_queue_link_t *current);
+njs_bool_t njs_variable_closure_test(njs_parser_scope_t *root,
+ njs_parser_scope_t *scope);
njs_variable_t *njs_variable_resolve(njs_vm_t *vm, njs_parser_node_t *node);
njs_index_t njs_variable_index(njs_vm_t *vm, njs_parser_node_t *node);
njs_bool_t njs_parser_has_side_effect(njs_parser_node_t *node);
diff -r 8583f3bdaeb9 -r c77b6d6fdfee src/njs_variable.c
--- a/src/njs_variable.c Mon Apr 12 10:05:59 2021 +0300
+++ b/src/njs_variable.c Mon Apr 12 10:06:49 2021 +0300
@@ -79,9 +79,7 @@ njs_variable_scope(njs_parser_scope_t *s
if (node != NULL) {
var = ((njs_variable_node_t *) node)->variable;
- if (var->type == NJS_VARIABLE_VAR
- || var->type == NJS_VARIABLE_FUNCTION)
- {
+ if (var->type <= NJS_VARIABLE_FUNCTION) {
*retvar = var;
return (type == NJS_VARIABLE_VAR) ? scope : prev;
@@ -112,15 +110,37 @@ njs_variable_scope_find(njs_parser_t *pa
njs_parser_scope_t *root;
const njs_lexer_entry_t *entry;
- if (type != NJS_VARIABLE_VAR && type != NJS_VARIABLE_FUNCTION) {
- return scope;
- }
-
root = njs_variable_scope(scope, unique_id, &var, type);
if (njs_slow_path(root == NULL)) {
return NULL;
}
+ switch (type) {
+ case NJS_VARIABLE_LET:
+ if (scope->type == NJS_SCOPE_GLOBAL
+ && parser->undefined_id == unique_id)
+ {
+ goto failed;
+ }
+
+ if (root != scope) {
+ return scope;
+ }
+
+ if (var != NULL && var->scope == root) {
+ goto failed;
+ }
+
+ return scope;
+
+ case NJS_VARIABLE_VAR:
+ case NJS_VARIABLE_FUNCTION:
+ break;
+
+ default:
+ return scope;
+ }
+
if (type == NJS_VARIABLE_FUNCTION) {
root = scope;
}
@@ -129,6 +149,10 @@ njs_variable_scope_find(njs_parser_t *pa
return root;
}
+ if (var->type == NJS_VARIABLE_LET) {
+ goto failed;
+ }
+
if (var->original->type == NJS_SCOPE_BLOCK) {
if (type == NJS_VARIABLE_FUNCTION
|| var->type == NJS_VARIABLE_FUNCTION)
@@ -284,7 +308,7 @@ njs_label_remove(njs_vm_t *vm, njs_parse
}
-static njs_bool_t
+njs_bool_t
njs_variable_closure_test(njs_parser_scope_t *root, njs_parser_scope_t *scope)
{
if (root == scope) {
@@ -397,6 +421,7 @@ njs_variable_closure(njs_vm_t *vm, njs_v
}
parse_node->key = var->unique_id;
+ parse_node->check = 0;
njs_rbtree_insert(&scope->references, &parse_node->node);
}
@@ -413,6 +438,7 @@ njs_variable_closure(njs_vm_t *vm, njs_v
njs_variable_t *
njs_variable_reference(njs_vm_t *vm, njs_parser_node_t *node)
{
+ njs_bool_t closure;
njs_rbtree_node_t *rb_node;
njs_parser_scope_t *scope;
njs_parser_rbtree_node_t *parse_node, ref_node;
@@ -430,7 +456,7 @@ njs_variable_reference(njs_vm_t *vm, njs
}
}
- ref->closure = njs_variable_closure_test(node->scope, ref->variable->scope);
+ closure = njs_variable_closure_test(node->scope, ref->variable->scope);
ref->scope = node->scope;
ref_node.key = ref->unique_id;
@@ -448,12 +474,14 @@ njs_variable_reference(njs_vm_t *vm, njs
return ref->variable;
}
- if (!ref->closure) {
+ if (!closure) {
node->index = ref->variable->index;
return ref->variable;
}
+ ref->variable->closure = closure;
+
node->index = njs_variable_closure(vm, ref->variable, scope);
if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
return NULL;
diff -r 8583f3bdaeb9 -r c77b6d6fdfee src/njs_variable.h
--- a/src/njs_variable.h Mon Apr 12 10:05:59 2021 +0300
+++ b/src/njs_variable.h Mon Apr 12 10:06:49 2021 +0300
@@ -12,11 +12,17 @@ typedef enum {
NJS_VARIABLE_CONST = 0,
NJS_VARIABLE_LET,
NJS_VARIABLE_VAR,
+ NJS_VARIABLE_FUNCTION,
NJS_VARIABLE_CATCH,
- NJS_VARIABLE_FUNCTION,
} njs_variable_type_t;
+typedef enum {
+ NJS_VARIABLE_NOT_CHECK = 0,
+ NJS_VARIABLE_CHECK,
+ NJS_VARIABLE_CHECK_PROGRESS
+} njs_variable_check_t;
+
typedef struct {
uintptr_t unique_id;
@@ -24,8 +30,10 @@ typedef struct {
njs_bool_t argument;
njs_bool_t arguments_object;
njs_bool_t init;
+ njs_variable_check_t check;
njs_bool_t have_lambda;
njs_bool_t self;
+ njs_bool_t closure;
njs_parser_scope_t *scope;
njs_parser_scope_t *original;
@@ -48,7 +56,6 @@ typedef struct {
njs_variable_t *variable;
njs_parser_scope_t *scope;
njs_bool_t not_defined;
- njs_bool_t closure;
} njs_variable_reference_t;
diff -r 8583f3bdaeb9 -r c77b6d6fdfee src/njs_vm.h
--- a/src/njs_vm.h Mon Apr 12 10:05:59 2021 +0300
+++ b/src/njs_vm.h Mon Apr 12 10:06:49 2021 +0300
@@ -112,6 +112,7 @@ enum njs_object_e {
};
+#define NJS_SCOPE_VAR_MAX ((1 << NJS_SCOPE_VAR) - 1)
#define NJS_SCOPE_VALUE_MAX ((1 << (32 - NJS_SCOPE_VALUE)) - 1)
diff -r 8583f3bdaeb9 -r c77b6d6fdfee src/njs_vmcode.c
--- a/src/njs_vmcode.c Mon Apr 12 10:05:59 2021 +0300
+++ b/src/njs_vmcode.c Mon Apr 12 10:06:49 2021 +0300
@@ -98,6 +98,7 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_c
njs_property_next_t *next;
njs_vmcode_finally_t *finally;
njs_vmcode_generic_t *vmcode;
+ njs_vmcode_variable_t *var;
njs_vmcode_move_arg_t *move_arg;
njs_vmcode_prop_get_t *get;
njs_vmcode_prop_set_t *set;
@@ -707,6 +708,7 @@ next:
case NJS_VMCODE_RETURN:
value2 = njs_vmcode_operand(vm, (njs_index_t) value2);
+
frame = (njs_frame_t *) vm->top_frame;
if (frame->native.ctor) {
@@ -913,6 +915,56 @@ next:
ret = sizeof(njs_vmcode_move_arg_t);
break;
+ case NJS_VMCODE_LET:
+ var = (njs_vmcode_variable_t *) pc;
+ value1 = njs_scope_value(vm, var->dst);
+
+ if (njs_is_valid(value1)) {
+ value1 = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t));
+ if (njs_slow_path(value1 == NULL)) {
+ return NJS_ERROR;
+ }
+
+ njs_scope_value_set(vm, var->dst, value1);
+ }
+
+ njs_set_undefined(value1);
+
+ ret = sizeof(njs_vmcode_variable_t);
+ break;
+
+ case NJS_VMCODE_LET_UPDATE:
+ var = (njs_vmcode_variable_t *) pc;
+ value2 = njs_scope_value(vm, var->dst);
+
+ value1 = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t));
+ if (njs_slow_path(value1 == NULL)) {
+ return NJS_ERROR;
+ }
+
+ *value1 = *value2;
+
+ njs_scope_value_set(vm, var->dst, value1);
+
+ ret = sizeof(njs_vmcode_variable_t);
+ break;
+
+ case NJS_VMCODE_REFERENCE:
+ var = (njs_vmcode_variable_t *) pc;
+ value1 = njs_scope_value(vm, var->dst);
+
+ if (njs_is_valid(value1)) {
+ ret = sizeof(njs_vmcode_variable_t);
+ break;
+ }
+
+ /* Fall through. */
+
+ case NJS_VMCODE_NOT_INITIALIZATION:
+ njs_reference_error(vm, "cannot access to variable "
+ "before initialization");
+ goto error;
+
default:
njs_internal_error(vm, "%d has NO retval", op);
goto error;
@@ -1080,7 +1132,11 @@ njs_vmcode_arguments(njs_vm_t *vm, u_cha
}
code = (njs_vmcode_arguments_t *) pc;
- value = njs_vmcode_operand(vm, code->dst);
+
+ value = njs_scope_valid_value(vm, code->dst);
+ if (njs_slow_path(value == NULL)) {
+ return NJS_ERROR;
+ }
njs_set_object(value, frame->native.arguments_object);
@@ -1122,7 +1178,10 @@ njs_vmcode_template_literal(njs_vm_t *vm
.u.native = njs_string_prototype_concat
};
- value = njs_vmcode_operand(vm, (njs_index_t) retval);
+ value = njs_scope_valid_value(vm, (njs_index_t) retval);
+ if (njs_slow_path(value == NULL)) {
+ return NJS_ERROR;
+ }
if (!njs_is_primitive(value)) {
array = njs_array(value);
diff -r 8583f3bdaeb9 -r c77b6d6fdfee src/njs_vmcode.h
--- a/src/njs_vmcode.h Mon Apr 12 10:05:59 2021 +0300
+++ b/src/njs_vmcode.h Mon Apr 12 10:06:49 2021 +0300
@@ -59,6 +59,11 @@ enum {
NJS_VMCODE_MOVE_ARG,
+ NJS_VMCODE_LET,
+ NJS_VMCODE_LET_UPDATE,
+ NJS_VMCODE_REFERENCE,
+ NJS_VMCODE_NOT_INITIALIZATION,
+
NJS_VMCODE_NORET = 127
};
@@ -402,6 +407,12 @@ typedef struct {
} njs_vmcode_move_arg_t;
+typedef struct {
+ njs_vmcode_t code;
+ njs_index_t dst;
+} njs_vmcode_variable_t;
+
+
njs_int_t njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc);
njs_object_t *njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor);
diff -r 8583f3bdaeb9 -r c77b6d6fdfee src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c Mon Apr 12 10:05:59 2021 +0300
+++ b/src/test/njs_unit_test.c Mon Apr 12 10:06:49 2021 +0300
@@ -19700,6 +19700,198 @@ static njs_unit_test_t njs_test[] =
{ njs_str("var buffer = require('buffer');"
"typeof buffer.constants.MAX_STRING_LENGTH === 'number' "),
njs_str("true") },
+
+ /* let */
+
+ { njs_str("let x"),
+ njs_str("undefined") },
+
+ { njs_str("let x = 123; x"),
+ njs_str("123") },
+
+ { njs_str("let x = [123]; x"),
+ njs_str("123") },
+
+ { njs_str("x; let x"),
+ njs_str("ReferenceError: cannot access to variable before initialization") },
+
+ { njs_str("x; let x = 123"),
+ njs_str("ReferenceError: cannot access to variable before initialization") },
+
+ { njs_str("let x = x + 123"),
+ njs_str("ReferenceError: cannot access to variable before initialization") },
+
+ { njs_str("let x; var x"),
+ njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+ { njs_str("var x; let x"),
+ njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+ { njs_str("let x; function x() {}"),
+ njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+ { njs_str("function x() {} let x"),
+ njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+ { njs_str("function x() {let x; var x}"),
+ njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+ { njs_str("function x() {var x; let x}"),
+ njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+ { njs_str("let a; let x = 1;"
+ "{let x = 2; a = x}"
+ "[x, a]"),
+ njs_str("1,2") },
+
+ { njs_str("let a; let x = 1;"
+ "if (true) {let x = 2; a = x}"
+ "[x, a]"),
+ njs_str("1,2") },
+
+ { njs_str("var a = 5, b = 10, arr = [];"
+ "{let a = 4; var b = 1; arr.push(a); arr.push(b)}"
+ "arr.push(a); arr.push(b); arr"),
+ njs_str("4,1,5,1") },
+
+ { njs_str("function func() {return x}"
+ "let x = 123;"
+ "func()"),
+ njs_str("123") },
+
+ { njs_str("function func() {return x}"
+ "func();"
+ "let x = 123"),
+ njs_str("ReferenceError: cannot access to variable before initialization") },
+
+ { njs_str("function func() {return () => x}"
+ "let x = 123;"
+ "func()()"),
+ njs_str("123") },
+
+ { njs_str("function func() {x = x + 1; let x}"),
+ njs_str("undefined") },
+
+ { njs_str("function func() {return () => x}"
+ "func()();"
+ "let x = 123;"),
+ njs_str("ReferenceError: cannot access to variable before initialization") },
+
+ { njs_str("var arr = [];"
+ ""
+ "for (var i = 0; i < 10; i++) {"
+ " let x = i;"
+ ""
+ " arr.push( (n) => {x += n; return x} );"
+ "}"
+ ""
+ "["
+ " arr[0](2), arr[1](1), arr[2](4), arr[3](7), arr[4](0),"
+ " arr[5](1), arr[6](2), arr[7](5), arr[8](8), arr[9](10)"
+ "]"),
+ njs_str("2,2,6,10,4,6,8,12,16,19") },
+
+ { njs_str("var arr = [];"
+ ""
+ "for (let i = 0; i < 10; i++) {"
+ " arr.push( (n) => {i += n; return i} );"
+ "}"
+ ""
+ "["
+ " arr[0](2), arr[1](1), arr[2](4), arr[3](7), arr[4](0),"
+ " arr[5](1), arr[6](2), arr[7](5), arr[8](8), arr[9](10)"
+ "]"),
+ njs_str("2,2,6,10,4,6,8,12,16,19") },
+
+ { njs_str("for (let i = 0; i < 1; i++) {"
+ " let i = i + 2;"
+ "}"),
+ njs_str("ReferenceError: cannot access to variable before initialization") },
+
+ { njs_str("let arr = [], res = [];"
+ "for (let i = 0, f = function() { return i }; i < 5; i++) {"
+ " arr.push(f);"
+ "}"
+ "for (let i = 0; i < 5; i++) {"
+ " res.push(arr[i]());"
+ "} res"),
+ njs_str("0,0,0,0,0") },
+
+ { njs_str("let arr = [], res = [];"
+ "for (let i = 0; arr.push(() => i), i < 10; i++) {}"
+ "for (let k = 0; k < 10; k++) {res.push(arr[k]())}"
+ "res"),
+ njs_str("0,1,2,3,4,5,6,7,8,9") },
+
+ { njs_str("let res = [];"
+ "for (let n in [1,2,3]) {res.push(n)}"
+ "res"),
+ njs_str("0,1,2") },
+
+ { njs_str("let arr = [], res = [];"
+ ""
+ "for (let n in [1,2,3]) {"
+ " arr.push(() => n);"
+ "}"
+ ""
+ "for (let n in arr) {"
+ " res.push(arr[n]());"
+ "}"
+ "res"),
+ njs_str("0,1,2") },
+
+ { njs_str("let arr = [];"
+ ""
+ "for (let n in [1,2,3]) {"
+ " let n = 1;"
+ " arr.push(n);"
+ "}"
+ "arr"),
+ njs_str("1,1,1") },
+
+ { njs_str("for (let n in [1,2,3]) {"
+ " let n = n + 1;"
+ "}"),
+ njs_str("ReferenceError: cannot access to variable before initialization") },
+
+ { njs_str("for (let n in [1,2,3]) {}"
+ "n"),
+ njs_str("ReferenceError: \"n\" is not defined") },
+
+ { njs_str("for (let n in [1,n,3]) {}"),
+ njs_str("ReferenceError: cannot access to variable before initialization") },
+
+ { njs_str("(function() {"
+ "function f() {return x + 1}"
+ "function abc() {f()};"
+ "abc();"
+ "let x;"
+ "}())"),
+ njs_str("ReferenceError: cannot access to variable before initialization") },
+
+ { njs_str("function func() {var x = 1; {let x = x + 1} } func()"),
+ njs_str("ReferenceError: cannot access to variable before initialization") },
+
+ { njs_str("if (false) let x = 1"),
+ njs_str("SyntaxError: let declaration cannot appear in a single-statement context in 1") },
+
+ { njs_str("while (false) let x = 1"),
+ njs_str("SyntaxError: let declaration cannot appear in a single-statement context in 1") },
+
+ { njs_str("for (;;) let x = 1"),
+ njs_str("SyntaxError: let declaration cannot appear in a single-statement context in 1") },
+
+ { njs_str("let null"),
+ njs_str("SyntaxError: Unexpected token \"null\" in 1") },
+
+ { njs_str("let continue"),
+ njs_str("SyntaxError: Unexpected token \"continue\" in 1") },
+
+ { njs_str("let undefined"),
+ njs_str("SyntaxError: \"undefined\" has already been declared in 1") },
+
+ { njs_str("let a = 1; globalThis.a"),
+ njs_str("undefined") },
};
diff -r 8583f3bdaeb9 -r c77b6d6fdfee test/njs_expect_test.exp
--- a/test/njs_expect_test.exp Mon Apr 12 10:05:59 2021 +0300
+++ b/test/njs_expect_test.exp Mon Apr 12 10:06:49 2021 +0300
@@ -143,6 +143,12 @@ njs_test {
"'abc'."}
}
+# let
+njs_test {
+ {"x; {x} let x\r\n"
+ " 1 | 00000 REFERENCE*\r\n* 1 | 00016 LET*\r\n*"}
+} "-d"
+
# Global completions, global vars
njs_test {
{"var a = 1; var aa = 2\r\n"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment