*/
njs_ret_t jump_offset;
njs_generator_patch_t *next;
+
+ nxt_str_t label;
};
typedef enum {
- NJS_GENERATOR_BLOCK = 1,
- NJS_GENERATOR_LOOP = 2,
- NJS_GENERATOR_SWITCH = 4,
+ NJS_GENERATOR_LOOP = 1,
+ NJS_GENERATOR_SWITCH = 2,
+ NJS_GENERATOR_BLOCK = 4,
NJS_GENERATOR_TRY = 8,
-
-#define NJS_GENERATOR_ALL (NJS_GENERATOR_BLOCK \
- | NJS_GENERATOR_LOOP \
- | NJS_GENERATOR_SWITCH \
- | NJS_GENERATOR_TRY)
+#define NJS_GENERATOR_ALL (NJS_GENERATOR_LOOP | NJS_GENERATOR_SWITCH)
} njs_generator_block_type_t;
struct njs_generator_block_s {
- njs_generator_block_type_t type; /* 2 bits */
+ njs_generator_block_type_t type; /* 4 bits */
nxt_str_t label;
+
+ /* list of "continue" instruction offsets to be patched. */
njs_generator_patch_t *continuation;
+ /*
+ * list of "return" from try-catch block and "break"
+ * instruction offsets to be patched.
+ */
njs_generator_patch_t *exit;
+
njs_generator_block_t *next;
+ /* exit value index, used only for NJS_GENERATOR_TRY blocks. */
njs_index_t index;
};
static nxt_noinline nxt_int_t njs_generate_start_block(njs_vm_t *vm,
njs_generator_t *generator, njs_generator_block_type_t type,
const nxt_str_t *label);
+static njs_generator_block_t *njs_generate_lookup_block(
+ njs_generator_block_t *block, uint32_t mask, const nxt_str_t *label);
static njs_generator_block_t *njs_generate_find_block(
- njs_generator_block_t *block, uint32_t mask);
-static nxt_int_t njs_generate_make_continuation_patch(njs_vm_t *vm,
- njs_generator_t *generator, njs_generator_block_t *block, njs_ret_t offset);
+ njs_generator_block_t *block, uint32_t mask, const nxt_str_t *label);
+static njs_generator_patch_t *njs_generate_make_continuation_patch(njs_vm_t *vm,
+ njs_generator_block_t *block, const nxt_str_t *label, njs_ret_t offset);
static nxt_noinline void njs_generate_patch_block(njs_vm_t *vm,
njs_generator_t *generator, njs_generator_patch_t *list);
-static nxt_int_t njs_generate_make_exit_patch(njs_vm_t *vm,
- njs_generator_t *generator, njs_generator_block_t *block, njs_ret_t offset);
+static njs_generator_patch_t *njs_generate_make_exit_patch(njs_vm_t *vm,
+ njs_generator_block_t *block, const nxt_str_t *label, njs_ret_t offset);
static nxt_noinline void njs_generate_patch_block_exit(njs_vm_t *vm,
njs_generator_t *generator);
+static const nxt_str_t *njs_generate_jump_destination(njs_vm_t *vm,
+ njs_generator_block_t *block, const char *inst_type, uint32_t mask,
+ const nxt_str_t *label1, const nxt_str_t *label2);
static nxt_int_t njs_generate_continue_statement(njs_vm_t *vm,
njs_generator_t *generator, njs_parser_node_t *node);
static nxt_int_t njs_generate_break_statement(njs_vm_t *vm,
njs_parser_node_error(vm, node, NJS_OBJECT_SYNTAX_ERROR, fmt, ##__VA_ARGS__)
-static const nxt_str_t no_label = { 0, NULL };
+static const nxt_str_t no_label = nxt_string("");
+static const nxt_str_t return_label = nxt_string("@return");
+/* GCC and Clang complain about NULL argument passed to memcmp(). */
+static const nxt_str_t undef_label = { 0xffffffff, (u_char *) "" };
static nxt_int_t
default:
nxt_thread_log_debug("unknown token: %d", node->token);
- njs_syntax_error(vm, "unknown token");
+ njs_internal_error(vm, "Generator failed: unknown token");
return NXT_ERROR;
}
}
ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_SWITCH,
- &no_label);
+ &swtch->label);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
patch->jump_offset = njs_code_offset(generator, equal)
+ offsetof(njs_vmcode_equal_jump_t, offset);
+ patch->label = no_label;
*last = patch;
last = &patch->next;
/* The loop body. */
ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP,
- &no_label);
+ &node->label);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
/* The loop body. */
ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP,
- &no_label);
+ &node->label);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
njs_vmcode_cond_jump_t *cond_jump;
ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP,
- &no_label);
+ &node->label);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
njs_vmcode_prop_foreach_t *prop_foreach;
ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP,
- &no_label);
+ &node->label);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
static njs_generator_block_t *
-njs_generate_find_block(njs_generator_block_t *block, uint32_t mask)
+njs_generate_lookup_block(njs_generator_block_t *block, uint32_t mask,
+ const nxt_str_t *label)
{
+ if (nxt_strstr_eq(label, &return_label)) {
+ mask = NJS_GENERATOR_TRY;
+ label = &no_label;
+ }
+
while (block != NULL) {
- if (block->type & mask) {
+ if ((block->type & mask) != 0
+ && (label->length == 0 || nxt_strstr_eq(&block->label, label)))
+ {
return block;
}
}
-static nxt_int_t
-njs_generate_make_continuation_patch(njs_vm_t *vm, njs_generator_t *generator,
- njs_generator_block_t *block, njs_ret_t offset)
+static njs_generator_block_t *
+njs_generate_find_block(njs_generator_block_t *block, uint32_t mask,
+ const nxt_str_t *label)
+{
+ njs_generator_block_t *dest_block;
+
+ /*
+ * ES5.1: 12.8 The break Statement
+ * "break" without a label is valid only from within
+ * loop or switch statement.
+ */
+ if ((mask & NJS_GENERATOR_ALL) == NJS_GENERATOR_ALL
+ && !nxt_strstr_eq(label, &no_label))
+ {
+ mask |= NJS_GENERATOR_BLOCK;
+ }
+
+ dest_block = njs_generate_lookup_block(block, mask, label);
+
+ if (dest_block != NULL) {
+
+ /*
+ * Looking for intermediate try-catch blocks. Before jumping to
+ * the destination finally blocks have to be executed.
+ */
+
+ while (block != NULL) {
+ if (block->type & NJS_GENERATOR_TRY) {
+ return block;
+ }
+
+ if (block == dest_block) {
+ return block;
+ }
+
+ block = block->next;
+ }
+ }
+
+ return dest_block;
+}
+
+
+static njs_generator_patch_t *
+njs_generate_make_continuation_patch(njs_vm_t *vm, njs_generator_block_t *block,
+ const nxt_str_t *label, njs_ret_t offset)
{
njs_generator_patch_t *patch;
patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t));
if (nxt_slow_path(patch == NULL)) {
njs_memory_error(vm);
- return NXT_ERROR;
+ return NULL;
}
patch->next = block->continuation;
patch->jump_offset = offset;
- return NXT_OK;
+ patch->label = *label;
+
+ return patch;
}
}
-static nxt_int_t
-njs_generate_make_exit_patch(njs_vm_t *vm, njs_generator_t *generator,
- njs_generator_block_t *block, njs_ret_t offset)
+static njs_generator_patch_t *
+njs_generate_make_exit_patch(njs_vm_t *vm, njs_generator_block_t *block,
+ const nxt_str_t *label, njs_ret_t offset)
{
njs_generator_patch_t *patch;
patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t));
if (nxt_slow_path(patch == NULL)) {
njs_memory_error(vm);
- return NXT_ERROR;
+ return NULL;
}
patch->next = block->exit;
patch->jump_offset = offset;
- return NXT_OK;
+ patch->label = *label;
+
+ return patch;
}
}
+/*
+ * TODO: support multiple destination points from within try-catch block.
+ */
+static const nxt_str_t *
+njs_generate_jump_destination(njs_vm_t *vm, njs_generator_block_t *block,
+ const char *inst_type, uint32_t mask, const nxt_str_t *label1,
+ const nxt_str_t *label2)
+{
+ njs_generator_block_t *block1, *block2;
+
+ if (label1->length == undef_label.length) {
+ return label2;
+ }
+
+ if (label2->length == undef_label.length) {
+ return label1;
+ }
+
+ block1 = njs_generate_lookup_block(block, mask, label1);
+ block2 = njs_generate_lookup_block(block, mask, label2);
+
+ if (block1 != block2) {
+ njs_internal_error(vm, "%s instructions with different labels "
+ "(\"%V\" vs \"%V\") "
+ "from try-catch block are not supported", inst_type,
+ label1, label2);
+
+ return NULL;
+ }
+
+ return label1;
+}
+
+
static nxt_int_t
njs_generate_continue_statement(njs_vm_t *vm, njs_generator_t *generator,
njs_parser_node_t *node)
{
+ const nxt_str_t *label, *dest;
njs_vmcode_jump_t *jump;
njs_generator_patch_t *patch;
njs_generator_block_t *block;
- block = njs_generate_find_block(generator->block,
- NJS_GENERATOR_LOOP | NJS_GENERATOR_TRY);
+ label = &node->label;
+
+ block = njs_generate_find_block(generator->block, NJS_GENERATOR_LOOP,
+ label);
if (nxt_slow_path(block == NULL)) {
goto syntax_error;
}
- if (block->type == NJS_GENERATOR_TRY
- && njs_generate_find_block(block->next, NJS_GENERATOR_LOOP) == NULL)
- {
- goto syntax_error;
+ if (block->type == NJS_GENERATOR_TRY && block->continuation != NULL) {
+ dest = njs_generate_jump_destination(vm, block->next, "continue",
+ NJS_GENERATOR_LOOP,
+ &block->continuation->label,
+ label);
+ if (nxt_slow_path(dest == NULL)) {
+ return NXT_ERROR;
+ }
}
- /* TODO: LABEL */
-
- patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t));
-
- if (nxt_fast_path(patch != NULL)) {
- patch->next = block->continuation;
- block->continuation = patch;
-
- njs_generate_code(generator, njs_vmcode_jump_t, jump);
- jump->code.operation = njs_vmcode_jump;
- jump->code.operands = NJS_VMCODE_NO_OPERAND;
- jump->code.retval = NJS_VMCODE_NO_RETVAL;
- jump->offset = offsetof(njs_vmcode_jump_t, offset);
+ njs_generate_code(generator, njs_vmcode_jump_t, jump);
+ jump->code.operation = njs_vmcode_jump;
+ jump->code.operands = NJS_VMCODE_NO_OPERAND;
+ jump->code.retval = NJS_VMCODE_NO_RETVAL;
+ jump->offset = offsetof(njs_vmcode_jump_t, offset);
- patch->jump_offset = njs_code_offset(generator, jump)
- + offsetof(njs_vmcode_jump_t, offset);
+ patch = njs_generate_make_continuation_patch(vm, block, label,
+ njs_code_offset(generator, jump)
+ + offsetof(njs_vmcode_jump_t, offset));
+ if (nxt_slow_path(patch == NULL)) {
+ return NXT_ERROR;
}
return NXT_OK;
njs_generate_break_statement(njs_vm_t *vm, njs_generator_t *generator,
njs_parser_node_t *node)
{
+ const nxt_str_t *label, *dest;
njs_vmcode_jump_t *jump;
njs_generator_patch_t *patch;
njs_generator_block_t *block;
- block = njs_generate_find_block(generator->block, NJS_GENERATOR_ALL);
+ label = &node->label;
+ block = njs_generate_find_block(generator->block, NJS_GENERATOR_ALL, label);
if (nxt_slow_path(block == NULL)) {
goto syntax_error;
}
- if (block->type == NJS_GENERATOR_TRY
- && njs_generate_find_block(block->next, NJS_GENERATOR_ALL) == NULL)
- {
- goto syntax_error;
+ if (block->type == NJS_GENERATOR_TRY && block->exit != NULL) {
+ dest = njs_generate_jump_destination(vm, block->next, "break/return",
+ NJS_GENERATOR_ALL,
+ &block->exit->label, label);
+ if (nxt_slow_path(dest == NULL)) {
+ return NXT_ERROR;
+ }
}
- /* TODO: LABEL: loop and switch may have label, block must have label. */
-
- patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t));
-
- if (nxt_fast_path(patch != NULL)) {
- patch->next = block->exit;
- block->exit = patch;
-
- njs_generate_code(generator, njs_vmcode_jump_t, jump);
- jump->code.operation = njs_vmcode_jump;
- jump->code.operands = NJS_VMCODE_NO_OPERAND;
- jump->code.retval = NJS_VMCODE_NO_RETVAL;
- jump->offset = offsetof(njs_vmcode_jump_t, offset);
+ njs_generate_code(generator, njs_vmcode_jump_t, jump);
+ jump->code.operation = njs_vmcode_jump;
+ jump->code.operands = NJS_VMCODE_NO_OPERAND;
+ jump->code.retval = NJS_VMCODE_NO_RETVAL;
+ jump->offset = offsetof(njs_vmcode_jump_t, offset);
- patch->jump_offset = njs_code_offset(generator, jump)
- + offsetof(njs_vmcode_jump_t, offset);
+ patch = njs_generate_make_exit_patch(vm, block, label,
+ njs_code_offset(generator, jump)
+ + offsetof(njs_vmcode_jump_t, offset));
+ if (nxt_slow_path(patch == NULL)) {
+ return NXT_ERROR;
}
return NXT_OK;
nxt_int_t ret;
ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_BLOCK,
- &no_label);
+ &node->label);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
{
nxt_int_t ret;
njs_index_t index;
+ const nxt_str_t *dest;
njs_vmcode_return_t *code;
njs_generator_patch_t *patch;
- njs_generator_block_t *block;
+ njs_generator_block_t *block, *immediate, *top;
njs_vmcode_try_return_t *try_return;
ret = njs_generator(vm, generator, node->right);
index = node->right->index;
} else {
- index = njs_value_index(vm, &njs_value_void,
- generator->runtime);
+ index = njs_value_index(vm, &njs_value_void, generator->runtime);
}
- block = njs_generate_find_block(generator->block, NJS_GENERATOR_TRY);
+ immediate = njs_generate_lookup_block(generator->block, NJS_GENERATOR_TRY,
+ &no_label);
- if (nxt_fast_path(block == NULL)) {
+ if (nxt_fast_path(immediate == NULL)) {
njs_generate_code(generator, njs_vmcode_return_t, code);
code->code.operation = njs_vmcode_return;
code->code.operands = NJS_VMCODE_1OPERAND;
return NXT_OK;
}
- patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t));
- if (nxt_slow_path(patch == NULL)) {
- return NXT_ERROR;
+ if (immediate->type == NJS_GENERATOR_TRY && immediate->exit != NULL) {
+ dest = njs_generate_jump_destination(vm, immediate->next,
+ "break/return",
+ NJS_GENERATOR_ALL,
+ &immediate->exit->label,
+ &return_label);
+ if (nxt_slow_path(dest == NULL)) {
+ return NXT_ERROR;
+ }
}
- patch->next = block->exit;
- block->exit = patch;
+ top = immediate;
+ block = immediate->next;
+
+ while (block != NULL) {
+ if (block->type & NJS_GENERATOR_TRY) {
+ top = block;
+ }
+
+ block = block->next;
+ }
njs_generate_code(generator, njs_vmcode_try_return_t, try_return);
try_return->code.operation = njs_vmcode_try_return;
try_return->code.operands = NJS_VMCODE_2OPERANDS;
try_return->code.retval = NJS_VMCODE_RETVAL;
try_return->retval = index;
- try_return->save = block->index;
-
+ try_return->save = top->index;
try_return->offset = offsetof(njs_vmcode_try_return_t, offset);
- patch->jump_offset = njs_code_offset(generator, try_return)
- + offsetof(njs_vmcode_try_return_t, offset);
+
+ patch = njs_generate_make_exit_patch(vm, immediate, &return_label,
+ njs_code_offset(generator, try_return)
+ + offsetof(njs_vmcode_try_return_t,
+ offset));
+ if (nxt_slow_path(patch == NULL)) {
+ return NXT_ERROR;
+ }
return NXT_OK;
}
catch_end_offset;
nxt_int_t ret;
njs_index_t exception_index, exit_index, catch_index;
+ nxt_str_t try_cont_label, try_exit_label,
+ catch_cont_label, catch_exit_label;
+ const nxt_str_t *dest_label;
njs_vmcode_catch_t *catch;
njs_vmcode_finally_t *finally;
njs_vmcode_try_end_t *try_end, *catch_end;
+ njs_generator_patch_t *patch;
njs_generator_block_t *block, *try_block, *catch_block;
njs_vmcode_try_start_t *try_start;
njs_vmcode_try_trampoline_t *try_break, *try_continue;
return ret;
}
+ try_exit_label = undef_label;
+ try_cont_label = undef_label;
+
njs_generate_code(generator, njs_vmcode_try_end_t, try_end);
try_end_offset = njs_code_offset(generator, try_end);
try_end->code.operation = njs_vmcode_try_end;
try_end->code.retval = NJS_VMCODE_NO_RETVAL;
if (try_block->exit != NULL) {
+ try_exit_label = try_block->exit->label;
+
njs_generate_patch_block(vm, generator, try_block->exit);
njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_break);
}
if (try_block->continuation != NULL) {
+ try_cont_label = try_block->continuation->label;
+
njs_generate_patch_block(vm, generator, try_block->continuation);
njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_continue);
node = node->right;
+ catch_exit_label = undef_label;
+ catch_cont_label = undef_label;
+
if (node->token == NJS_TOKEN_CATCH) {
/* A "try/catch" case. */
* by njs_generate_continue_statement()
*/
block = njs_generate_find_block(generator->block,
- NJS_GENERATOR_LOOP);
+ NJS_GENERATOR_LOOP,
+ &try_cont_label);
- njs_generate_make_continuation_patch(vm, generator, block,
+ patch = njs_generate_make_continuation_patch(vm, block,
+ &try_cont_label,
njs_code_offset(generator, finally)
+ offsetof(njs_vmcode_finally_t, continue_offset));
+ if (nxt_slow_path(patch == NULL)) {
+ return NXT_ERROR;
+ }
}
if (try_block->exit != NULL) {
block = njs_generate_find_block(generator->block,
- NJS_GENERATOR_ALL);
+ NJS_GENERATOR_ALL,
+ &try_exit_label);
if (block != NULL) {
- njs_generate_make_exit_patch(vm, generator, block,
+ patch = njs_generate_make_exit_patch(vm, block,
+ &try_exit_label,
njs_code_offset(generator, finally)
+ offsetof(njs_vmcode_finally_t, break_offset));
+ if (nxt_slow_path(patch == NULL)) {
+ return NXT_ERROR;
+ }
}
}
}
catch_end->code.retval = NJS_VMCODE_NO_RETVAL;
if (catch_block->exit != NULL) {
+ catch_exit_label = catch_block->exit->label;
+
njs_generate_patch_block(vm, generator, catch_block->exit);
njs_generate_code(generator, njs_vmcode_try_trampoline_t,
}
if (catch_block->continuation != NULL) {
+ catch_cont_label = catch_block->continuation->label;
+
njs_generate_patch_block(vm, generator,
catch_block->continuation);
if (try_block->continuation != NULL
|| (catch_block && catch_block->continuation != NULL))
{
+ dest_label = njs_generate_jump_destination(vm, generator->block,
+ "try continue",
+ NJS_GENERATOR_LOOP,
+ &try_cont_label,
+ &catch_cont_label);
+ if (nxt_slow_path(dest_label == NULL)) {
+ return NXT_ERROR;
+ }
+
/*
* block != NULL is checked
* by njs_generate_continue_statement()
*/
block = njs_generate_find_block(generator->block,
- NJS_GENERATOR_LOOP);
+ NJS_GENERATOR_LOOP, dest_label);
- njs_generate_make_continuation_patch(vm, generator, block,
- njs_code_offset(generator, finally)
- + offsetof(njs_vmcode_finally_t, continue_offset));
+ patch = njs_generate_make_continuation_patch(vm, block, dest_label,
+ njs_code_offset(generator, finally)
+ + offsetof(njs_vmcode_finally_t, continue_offset));
+ if (nxt_slow_path(patch == NULL)) {
+ return NXT_ERROR;
+ }
}
if (try_block->exit != NULL
|| (catch_block != NULL && catch_block->exit != NULL))
{
+ dest_label = njs_generate_jump_destination(vm, generator->block,
+ "try break/return",
+ NJS_GENERATOR_ALL
+ | NJS_GENERATOR_TRY,
+ &try_exit_label,
+ &catch_exit_label);
+ if (nxt_slow_path(dest_label == NULL)) {
+ return NXT_ERROR;
+ }
+
+ /*
+ * block can be NULL for "return" instruction in
+ * outermost try-catch block.
+ */
block = njs_generate_find_block(generator->block,
- NJS_GENERATOR_ALL);
+ NJS_GENERATOR_ALL
+ | NJS_GENERATOR_TRY, dest_label);
if (block != NULL) {
- njs_generate_make_exit_patch(vm, generator, block,
+ patch = njs_generate_make_exit_patch(vm, block, dest_label,
njs_code_offset(generator, finally)
+ offsetof(njs_vmcode_finally_t, break_offset));
+ if (nxt_slow_path(patch == NULL)) {
+ return NXT_ERROR;
+ }
}
}
}
njs_parser_t *parser);
static njs_token_t njs_parser_block(njs_vm_t *vm,
njs_parser_t *parser, njs_token_t token);
+static njs_token_t njs_parser_labelled_statement(njs_vm_t *vm,
+ njs_parser_t *parser);
static njs_token_t njs_parser_function_declaration(njs_vm_t *vm,
njs_parser_t *parser);
static njs_token_t njs_parser_function_lambda(njs_vm_t *vm,
njs_parser_t *parser, njs_parser_node_t *name);
static njs_token_t njs_parser_for_in_statement(njs_vm_t *vm,
njs_parser_t *parser, nxt_str_t *name, njs_token_t token);
-static njs_token_t njs_parser_continue_statement(njs_vm_t *vm,
- njs_parser_t *parser);
-static njs_token_t njs_parser_break_statement(njs_vm_t *vm,
- njs_parser_t *parser);
+static njs_token_t njs_parser_brk_statement(njs_vm_t *vm,
+ njs_parser_t *parser, njs_token_t token);
static njs_token_t njs_parser_try_statement(njs_vm_t *vm, njs_parser_t *parser);
static njs_token_t njs_parser_try_block(njs_vm_t *vm, njs_parser_t *parser);
static njs_token_t njs_parser_throw_statement(njs_vm_t *vm,
scope->argument_closures = 0;
nxt_queue_init(&scope->nested);
+ nxt_lvlhsh_init(&scope->labels);
nxt_lvlhsh_init(&scope->variables);
nxt_lvlhsh_init(&scope->references);
default:
+ if (token == NJS_TOKEN_NAME
+ && njs_lexer_peek_token(parser->lexer) == NJS_TOKEN_COLON)
+ {
+ return njs_parser_labelled_statement(vm, parser);
+ }
+
switch (token) {
case NJS_TOKEN_VAR:
token = njs_parser_var_statement(vm, parser);
break;
- case NJS_TOKEN_CONTINUE:
- token = njs_parser_continue_statement(vm, parser);
- break;
-
- case NJS_TOKEN_BREAK:
- token = njs_parser_break_statement(vm, parser);
- break;
-
case NJS_TOKEN_RETURN:
token = njs_parser_return_statement(vm, parser);
break;
token = njs_parser_throw_statement(vm, parser);
break;
+ case NJS_TOKEN_CONTINUE:
+ case NJS_TOKEN_BREAK:
+ token = njs_parser_brk_statement(vm, parser, token);
+ break;
+
default:
token = njs_parser_expression(vm, parser, token);
break;
}
+static njs_token_t
+njs_parser_labelled_statement(njs_vm_t *vm, njs_parser_t *parser)
+{
+ uint32_t hash;
+ njs_ret_t ret;
+ nxt_str_t name;
+ njs_token_t token;
+ njs_variable_t *label;
+
+ name = parser->lexer->text;
+ hash = parser->lexer->key_hash;
+
+ label = njs_label_find(vm, parser->scope, &name, hash);
+ if (nxt_slow_path(label != NULL)) {
+ njs_parser_syntax_error(vm, parser, "Label \"%V\" "
+ "has already been declared", &name);
+ return NJS_TOKEN_ILLEGAL;
+ }
+
+ label = njs_label_add(vm, parser->scope, &name, hash);
+ if (nxt_slow_path(label == NULL)) {
+ return NJS_TOKEN_ERROR;
+ }
+
+ token = njs_parser_token(parser);
+ if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+ return token;
+ }
+
+ token = njs_parser_match(vm, parser, token, NJS_TOKEN_COLON);
+ if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+ return token;
+ }
+
+ token = njs_parser_statement(vm, parser, token);
+
+ if (nxt_fast_path(token > NJS_TOKEN_ILLEGAL)) {
+
+ if (parser->node != NULL) {
+ /* The statement is not empty block or just semicolon. */
+
+ ret = njs_name_copy(vm, &parser->node->label, &name);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return NJS_TOKEN_ERROR;
+ }
+
+ ret = njs_label_remove(vm, parser->scope, &name, hash);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return NJS_TOKEN_ERROR;
+ }
+ }
+ }
+
+ return token;
+}
+
+
static njs_token_t
njs_parser_function_declaration(njs_vm_t *vm, njs_parser_t *parser)
{
static njs_token_t
-njs_parser_continue_statement(njs_vm_t *vm, njs_parser_t *parser)
+njs_parser_brk_statement(njs_vm_t *vm, njs_parser_t *parser,
+ njs_token_t token)
{
- njs_token_t token;
+ uint32_t hash;
+ njs_ret_t ret;
+ nxt_str_t name;
njs_parser_node_t *node;
- node = njs_parser_node_new(vm, parser, NJS_TOKEN_CONTINUE);
+ node = njs_parser_node_new(vm, parser, token);
if (nxt_slow_path(node == NULL)) {
return NJS_TOKEN_ERROR;
}
case NJS_TOKEN_LINE_END:
return njs_parser_token(parser);
- case NJS_TOKEN_SEMICOLON:
- case NJS_TOKEN_CLOSE_BRACE:
- case NJS_TOKEN_END:
- return token;
-
- default:
- /* TODO: LABEL */
- return NJS_TOKEN_ILLEGAL;
- }
-}
-
-
-static njs_token_t
-njs_parser_break_statement(njs_vm_t *vm, njs_parser_t *parser)
-{
- njs_token_t token;
- njs_parser_node_t *node;
-
- node = njs_parser_node_new(vm, parser, NJS_TOKEN_BREAK);
- if (nxt_slow_path(node == NULL)) {
- return NJS_TOKEN_ERROR;
- }
-
- node->token_line = parser->lexer->token_line;
- parser->node = node;
+ case NJS_TOKEN_NAME:
+ name = parser->lexer->text;
+ hash = parser->lexer->key_hash;
- token = njs_lexer_token(parser->lexer);
+ if (njs_label_find(vm, parser->scope, &name, hash) == NULL) {
+ njs_parser_syntax_error(vm, parser, "Undefined label \"%V\"",
+ &name);
+ return NJS_TOKEN_ILLEGAL;
+ }
- switch (token) {
+ ret = njs_name_copy(vm, &parser->node->label, &parser->lexer->text);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return NJS_TOKEN_ERROR;
+ }
- case NJS_TOKEN_LINE_END:
return njs_parser_token(parser);
case NJS_TOKEN_SEMICOLON:
{ nxt_string("break"),
nxt_string("SyntaxError: Illegal break statement in 1") },
+ { nxt_string("{break}"),
+ nxt_string("SyntaxError: Illegal break statement in 1") },
+
{ nxt_string("\nbreak"),
nxt_string("SyntaxError: Illegal break statement in 2") },
"for (i in a) if (a[i] > 4) break; s += a[i]; s"),
nxt_string("5") },
+ /* Labels. */
+
+ { nxt_string("var n = 0; a:{n++}; a:{n++}; n"),
+ nxt_string("2") },
+
+ { nxt_string("a: throw 'a'"),
+ nxt_string("a") },
+
+ { nxt_string("a : var n = 0; b :++n"),
+ nxt_string("1") },
+
+ { nxt_string("a:{a:1}"),
+ nxt_string("SyntaxError: Label \"a\" has already been declared in 1") },
+
+ { nxt_string("for (var i in [1]) {break b}"),
+ nxt_string("SyntaxError: Undefined label \"b\" in 1") },
+
+ { nxt_string("for (var i in [1]) {continue b}"),
+ nxt_string("SyntaxError: Undefined label \"b\" in 1") },
+
+ { nxt_string("a:{break b}"),
+ nxt_string("SyntaxError: Undefined label \"b\" in 1") },
+
+ { nxt_string("a:{continue b}"),
+ nxt_string("SyntaxError: Undefined label \"b\" in 1") },
+
+#if 0 /* TODO */
+ { nxt_string("a:{1; break a}"),
+ nxt_string("1") },
+#endif
+
+ { nxt_string("var a = 0; a:{a++}; a"),
+ nxt_string("1") },
+
+ { nxt_string("var a = 0; a:{break a; a++}; a"),
+ nxt_string("0") },
+
+ { nxt_string("var r = 0; "
+ "out: for (var i in [1,2,3]) { if (i == 2) {break out;}; r++}; r"),
+ nxt_string("2") },
+
+ { nxt_string("var r = 0; "
+ "out: for (var i = 0; i < 5; i++) { if (i == 2) {break out;}; r++}; r"),
+ nxt_string("2") },
+
+ { nxt_string("var l1 = 0, l2 = 0; "
+ "out: "
+ "for (var i in [1,2,3]) { "
+ " for (var j in [1,2,3]) { "
+ " if (i == 1 && j == 1) {break;}"
+ " l2++;"
+ " }"
+ " l1++;"
+ "}; [l1, l2]"),
+ nxt_string("3,7") },
+
+ { nxt_string("var l1 = 0, l2 = 0; "
+ "out: "
+ "for (var i in [1,2,3]) { "
+ " for (var j in [1,2,3]) { "
+ " if (i == 1 && j == 1) {break out;}"
+ " l2++;"
+ " }"
+ " l1++;"
+ "}; [l1, l2]"),
+ nxt_string("1,4") },
+
+ { nxt_string("var l1 = 0, l2 = 0; "
+ "out: "
+ "for (var i in [1,2,3]) { "
+ " for (var j in [1,2,3]) { "
+ " if (i == 1 && j == 1) {continue out;}"
+ " l2++;"
+ " }"
+ " l1++;"
+ "}; [l1, l2]"),
+ nxt_string("2,7") },
+
+ { nxt_string("var l1 = 0, l2 = 0; "
+ "out: "
+ "for (var i in [1,2,3]) { "
+ " l1++;"
+ " switch (i) { "
+ " case '1':"
+ " break out;"
+ " default:"
+ " }"
+ " l2++;"
+ "}; [l1, l2]"),
+ nxt_string("2,1") },
+
+ { nxt_string("var l1 = 0, l2 = 0; "
+ "out: "
+ "for (var i in [1,2,3]) { "
+ " l1++;"
+ " switch (i) { "
+ " case '1':"
+ " continue out;"
+ " default:"
+ " }"
+ " l2++;"
+ "}; [l1, l2]"),
+ nxt_string("3,2") },
+
+ { nxt_string("var l1 = 0, l2 = 0, i = 0, j; "
+ "out: "
+ "while (i < 3) { "
+ " j = 0;"
+ " while (j < 3) { "
+ " if (i == 1 && j == 1) {break out;}"
+ " l2++;"
+ " j++;"
+ " }"
+ " l1++;"
+ " i++;"
+ "}; [l1, l2]"),
+ nxt_string("1,4") },
+
+ { nxt_string("var l1 = 0, l2 = 0, i = 0, j; "
+ "out: "
+ "while (i < 3) { "
+ " j = 0;"
+ " while (j < 3) { "
+ " if (i == 1 && j == 1) {i++; continue out;}"
+ " l2++;"
+ " j++;"
+ " }"
+ " l1++;"
+ " i++;"
+ "}; [l1, l2]"),
+ nxt_string("2,7") },
+
+ { nxt_string("var l1 = 0, l2 = 0, i = 0, j; "
+ "out: "
+ "do { "
+ " j = 0;"
+ " do { "
+ " if (i == 1 && j == 1) {break out;}"
+ " l2++;"
+ " j++;"
+ " } while (j < 3)"
+ " l1++;"
+ " i++;"
+ "} while (i < 3); [l1, l2]"),
+ nxt_string("1,4") },
+
+ { nxt_string("var l1 = 0, l2 = 0, i = 0, j; "
+ "out: "
+ "do { "
+ " j = 0;"
+ " do { "
+ " if (i == 1 && j == 1) {i++; continue out;}"
+ " l2++;"
+ " j++;"
+ " } while (j < 3)"
+ " l1++;"
+ " i++;"
+ "} while (i < 3); [l1, l2]"),
+ nxt_string("2,7") },
+
+ { nxt_string("out1: while (1) { out2: while (1) { "
+ " try { break out1; break out2; } catch (e) {}"
+ "}}"),
+ nxt_string("InternalError: break/return instructions with different labels "
+ "(\"out1\" vs \"out2\") from try-catch block are not supported") },
+
+ { nxt_string("out1: while (1) { out2: while (1) { "
+ " try { } catch (e) {break out1; break out2;} finally {}"
+ "}}"),
+ nxt_string("InternalError: break/return instructions with different labels "
+ "(\"out1\" vs \"out2\") from try-catch block are not supported") },
+
+ { nxt_string("out1: while (1) { out2: while (1) { "
+ " try { break out1; } catch (e) {break out2;} finally {}"
+ "}}"),
+ nxt_string("InternalError: try break/return instructions with different labels "
+ "(\"out1\" vs \"out2\") from try-catch block are not supported") },
+
+ { nxt_string("out1: while (1) { out2: while (1) { "
+ " try { break out1; break out2; } finally {}"
+ "}}"),
+ nxt_string("InternalError: break/return instructions with different labels "
+ "(\"out1\" vs \"out2\") from try-catch block are not supported") },
+
+ { nxt_string("out1: while (1) { out2: while (1) { "
+ " try { continue out1; continue out2; } catch (e) {}"
+ "}}"),
+ nxt_string("InternalError: continue instructions with different labels "
+ "(\"out1\" vs \"out2\") from try-catch block are not supported") },
+
+ { nxt_string("out1: while (1) { out2: while (1) { "
+ " try { continue out1; } catch (e) {continue out2;} finally {}"
+ "}}"),
+ nxt_string("InternalError: try continue instructions with different labels "
+ "(\"out1\" vs \"out2\") from try-catch block are not supported") },
+
+ { nxt_string("function f() {"
+ " a:{ try { try { return 'a'; } catch (e) {break a;} finally {} } "
+ " catch (e) {} finally {}; }"
+ "}"),
+ nxt_string("InternalError: try break/return instructions with different labels "
+ "(\"@return\" vs \"a\") from try-catch block are not supported") },
+
+ { nxt_string("a:{ try { try { continue a; } catch (e) {} finally {} } "
+ " catch (e) {} finally {}; "
+ "}"),
+ nxt_string("SyntaxError: Illegal continue statement in 1") },
+
+ { nxt_string("var i = 0, j = 0, r = 0;"
+ "out1: while (i < 3) "
+ "{ "
+ " i++;"
+ " out2: while (j < 3) { "
+ " j++; try { break out1; } catch (e) {} finally {r++}"
+ " }"
+ "}; [i, j, r]"),
+ nxt_string("1,1,1") },
+
+ { nxt_string("var i = 0, j = 0, r = 0;"
+ "out1: while (i < 3) "
+ "{ "
+ " i++;"
+ " out2: while (j < 3) { "
+ " j++; try { continue out1; } catch (e) {} finally {r++}"
+ " }"
+ "}; [i, j, r]"),
+ nxt_string("3,3,3") },
+
+ { nxt_string("var c=0,fin=0;"
+ "try {"
+ " while (c < 2) {"
+ " try { c += 1; throw 'e';}"
+ " finally { fin = 1; break;}"
+ " fin = -1;"
+ " c += 2;"
+ " }"
+ "} catch(e) {c = 10;}; [c, fin]"),
+ nxt_string("1,1") },
+
+ /* jumping out of a nested try-catch block. */
+
+ { nxt_string("var r = 0; "
+ "function f () { try { try {return 'a';} finally { r++; }} "
+ " finally { r++; } }; "
+ "[f(), r]"),
+ nxt_string("a,2") },
+
+ { nxt_string("function f(n) { "
+ " var r1 = 0, r2 = 0, r3 = 0;"
+ " a:{ try { try { "
+ " if (n == 0) { break a; } "
+ " if (n == 1) { throw 'a'; } "
+ " } "
+ " catch (e) { break a; } finally { r1++; } } "
+ " catch (e) {} "
+ " finally { r2++; } "
+ " r3++; "
+ " }; "
+ "return [r1, r2, r3]"
+ "}; njs.dump([f(0), f(1), f(3)])"),
+ nxt_string("[[1,1,0],[1,1,0],[1,1,1]]") },
+
/**/
{ nxt_string("var i; for (i = 0; i < 10; i++) { i += 1 } i"),