]> git.kaiwu.me - njs.git/commitdiff
Support of "continue" and "break" statements without labels
authorIgor Sysoev <igor@sysoev.ru>
Tue, 15 Dec 2015 13:46:00 +0000 (16:46 +0300)
committerIgor Sysoev <igor@sysoev.ru>
Tue, 15 Dec 2015 13:46:00 +0000 (16:46 +0300)
in loops.

njs/njs_generator.c
njs/njs_parser.c
njs/njs_parser.h
njs/test/njs_unit_test.c

index 96343f63e1f03e93b72e211bed0136dfb49b5866..72a2aad3801eda59e754477885951c8087f4a167 100644 (file)
@@ -37,6 +37,16 @@ static nxt_int_t njs_generate_for_statement(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *node);
 static nxt_int_t njs_generate_for_in_statement(njs_vm_t *vm,
     njs_parser_t *parser, njs_parser_node_t *node);
+static nxt_noinline nxt_int_t njs_generate_start_block(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_block_type_t type, const nxt_str_t *label);
+static nxt_noinline void njs_generate_patch_loop_continuation(njs_vm_t *vm,
+    njs_parser_t *parser);
+static nxt_noinline void njs_generate_patch_block_exit(njs_vm_t *vm,
+    njs_parser_t *parser);
+static nxt_int_t njs_generate_continue_statement(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
+static nxt_int_t njs_generate_break_statement(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
 static nxt_int_t njs_generate_statement(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *node);
 static nxt_int_t njs_generate_children(njs_vm_t *vm, njs_parser_t *parser,
@@ -98,6 +108,9 @@ static nxt_noinline nxt_int_t njs_generator_index_release(njs_vm_t *vm,
 nxt_inline nxt_bool_t njs_generator_is_constant(njs_parser_node_t *node);
 
 
+static const nxt_str_t no_label = { 0, NULL };
+
+
 static nxt_int_t
 njs_generator(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node)
 {
@@ -127,6 +140,12 @@ njs_generator(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node)
     case NJS_TOKEN_FOR_IN:
         return njs_generate_for_in_statement(vm, parser, node);
 
+    case NJS_TOKEN_CONTINUE:
+        return njs_generate_continue_statement(vm, parser, node);
+
+    case NJS_TOKEN_BREAK:
+        return njs_generate_break_statement(vm, parser, node);
+
     case NJS_TOKEN_STATEMENT:
         return njs_generate_statement(vm, parser, node);
 
@@ -498,6 +517,11 @@ njs_generate_while_statement(njs_vm_t *vm, njs_parser_t *parser,
 
     /* The loop body. */
 
+    ret = njs_generate_start_block(vm, parser, NJS_PARSER_LOOP, &no_label);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
     loop = parser->code_end;
 
     ret = njs_generator(vm, parser, node->left);
@@ -507,6 +531,8 @@ njs_generate_while_statement(njs_vm_t *vm, njs_parser_t *parser,
 
     /* The loop condition. */
 
+    njs_generate_patch_loop_continuation(vm, parser);
+
     jump->offset = parser->code_end - (u_char *) jump;
 
     condition = node->right;
@@ -523,6 +549,8 @@ njs_generate_while_statement(njs_vm_t *vm, njs_parser_t *parser,
     cond_jump->offset = loop - (u_char *) cond_jump;
     cond_jump->cond = condition->index;
 
+    njs_generate_patch_block_exit(vm, parser);
+
     return njs_generator_node_index_release(vm, parser, condition);
 }
 
@@ -538,6 +566,11 @@ njs_generate_do_while_statement(njs_vm_t *vm, njs_parser_t *parser,
 
     /* The loop body. */
 
+    ret = njs_generate_start_block(vm, parser, NJS_PARSER_LOOP, &no_label);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
     loop = parser->code_end;
 
     ret = njs_generator(vm, parser, node->left);
@@ -547,6 +580,8 @@ njs_generate_do_while_statement(njs_vm_t *vm, njs_parser_t *parser,
 
     /* The loop condition. */
 
+    njs_generate_patch_loop_continuation(vm, parser);
+
     condition = node->right;
 
     ret = njs_generator(vm, parser, condition);
@@ -561,6 +596,8 @@ njs_generate_do_while_statement(njs_vm_t *vm, njs_parser_t *parser,
     cond_jump->offset = loop - (u_char *) cond_jump;
     cond_jump->cond = condition->index;
 
+    njs_generate_patch_block_exit(vm, parser);
+
     return njs_generator_node_index_release(vm, parser, condition);
 }
 
@@ -575,6 +612,11 @@ njs_generate_for_statement(njs_vm_t *vm, njs_parser_t *parser,
     njs_vmcode_jump_t       *jump;
     njs_vmcode_cond_jump_t  *cond_jump;
 
+    ret = njs_generate_start_block(vm, parser, NJS_PARSER_LOOP, &no_label);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
     jump = NULL;
 
     /* The loop initialization. */
@@ -617,6 +659,8 @@ njs_generate_for_statement(njs_vm_t *vm, njs_parser_t *parser,
 
     /* The loop update. */
 
+    njs_generate_patch_loop_continuation(vm, parser);
+
     update = node->right;
 
     ret = njs_generator(vm, parser, update);
@@ -646,6 +690,8 @@ njs_generate_for_statement(njs_vm_t *vm, njs_parser_t *parser,
         cond_jump->offset = loop - (u_char *) cond_jump;
         cond_jump->cond = condition->index;
 
+        njs_generate_patch_block_exit(vm, parser);
+
         return njs_generator_node_index_release(vm, parser, condition);
     }
 
@@ -655,6 +701,8 @@ njs_generate_for_statement(njs_vm_t *vm, njs_parser_t *parser,
     jump->code.retval = NJS_VMCODE_NO_RETVAL;
     jump->offset = loop - (u_char *) jump;
 
+    njs_generate_patch_block_exit(vm, parser);
+
     return NXT_OK;
 }
 
@@ -670,6 +718,11 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_parser_t *parser,
     njs_vmcode_prop_next_t     *prop_next;
     njs_vmcode_prop_foreach_t  *prop_foreach;
 
+    ret = njs_generate_start_block(vm, parser, NJS_PARSER_LOOP, &no_label);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
     /* The object. */
 
     foreach = node->left;
@@ -699,6 +752,8 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_parser_t *parser,
 
     /* The loop iterator. */
 
+    njs_generate_patch_loop_continuation(vm, parser);
+
     prop_foreach->offset = parser->code_end - (u_char *) prop_foreach;
 
     ret = njs_generator(vm, parser, node->left->left);
@@ -715,6 +770,8 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_parser_t *parser,
     prop_next->next = index;
     prop_next->offset = loop - (u_char *) prop_next;
 
+    njs_generate_patch_block_exit(vm, parser);
+
     /*
      * Release object and iterator indexes: an object can be a function result
      * or a property of another object and an iterator can be given with "let".
@@ -728,6 +785,138 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_parser_t *parser,
 }
 
 
+static nxt_noinline nxt_int_t
+njs_generate_start_block(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_block_type_t type, const nxt_str_t *label)
+{
+    njs_parser_block_t  *block;
+
+    block = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_parser_block_t));
+
+    if (nxt_fast_path(block != NULL)) {
+        block->next = parser->block;
+        parser->block = block;
+
+        block->type = type;
+        block->label = *label;
+        block->continuation = NULL;
+        block->exit = NULL;
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_noinline void
+njs_generate_patch_loop_continuation(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_parser_block_t  *block;
+    njs_parser_patch_t  *patch, *next;
+
+    block = parser->block;
+
+    for (patch = block->continuation; patch != NULL; patch = next) {
+        *patch->address += parser->code_end - patch->address;
+        next = patch->next;
+
+        nxt_mem_cache_free(vm->mem_cache_pool, patch);
+    }
+}
+
+
+static nxt_noinline void
+njs_generate_patch_block_exit(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_parser_block_t  *block;
+    njs_parser_patch_t  *patch, *next;
+
+    block = parser->block;
+    parser->block = block->next;
+
+    for (patch = block->exit; patch != NULL; patch = next) {
+        *patch->address += parser->code_end - patch->address;
+        next = patch->next;
+
+        nxt_mem_cache_free(vm->mem_cache_pool, patch);
+    }
+
+    nxt_mem_cache_free(vm->mem_cache_pool, block);
+}
+
+
+static nxt_int_t
+njs_generate_continue_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    njs_vmcode_jump_t   *jump;
+    njs_parser_patch_t  *patch;
+
+    if (parser->block == NULL) {
+        vm->exception = &njs_exception_syntax_error;
+        return NXT_ERROR;
+    }
+
+    /* TODO: LABEL */
+
+    if (parser->block->type != NJS_PARSER_LOOP) {
+        vm->exception = &njs_exception_syntax_error;
+        return NXT_ERROR;
+    }
+
+    patch = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_parser_patch_t));
+
+    if (nxt_fast_path(patch != NULL)) {
+        patch->next = parser->block->continuation;
+        parser->block->continuation = patch;
+
+        njs_generate_code(parser, 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->address = (u_char *) &jump->offset;
+    }
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_break_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    njs_vmcode_jump_t   *jump;
+    njs_parser_patch_t  *patch;
+
+    if (parser->block == NULL) {
+        vm->exception = &njs_exception_syntax_error;
+        return NXT_ERROR;
+    }
+
+    /* TODO: LABEL: loop and switch may have label, block must have label. */
+
+    patch = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_parser_patch_t));
+
+    if (nxt_fast_path(patch != NULL)) {
+        patch->next = parser->block->exit;
+        parser->block->exit = patch;
+
+        njs_generate_code(parser, 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->address = (u_char *) &jump->offset;
+    }
+
+    return NXT_OK;
+}
+
+
 static nxt_int_t
 njs_generate_statement(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *node)
index f2d9216a0f85810a19a6b382f076ebff39f7bec5..1eb20d5b832db0d2112773d73fbc0145cf8ddb8f 100644 (file)
@@ -34,8 +34,8 @@
  * is treated as a single expiression.
  */
 
-static njs_token_t njs_parser_statement_link(njs_vm_t *vm, njs_parser_t *parser,
-    njs_token_t token);
+static njs_token_t njs_parser_statement_chain(njs_vm_t *vm,
+    njs_parser_t *parser, njs_token_t token);
 static njs_token_t njs_parser_statement(njs_vm_t *vm, njs_parser_t *parser,
     njs_token_t token);
 static njs_token_t njs_parser_block(njs_vm_t *vm, njs_parser_t *parser);
@@ -56,6 +56,10 @@ static njs_token_t njs_parser_do_while_statement(njs_vm_t *vm,
 static njs_token_t njs_parser_for_statement(njs_vm_t *vm, njs_parser_t *parser);
 static njs_token_t njs_parser_for_in_statement(njs_vm_t *vm,
     njs_parser_t *parser, 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_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,
@@ -80,7 +84,7 @@ njs_parser(njs_vm_t *vm, njs_parser_t *parser)
 
     while (token != NJS_TOKEN_END) {
 
-        token = njs_parser_statement_link(vm, parser, token);
+        token = njs_parser_statement_chain(vm, parser, token);
         if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
             return NULL;
         }
@@ -110,7 +114,7 @@ njs_parser(njs_vm_t *vm, njs_parser_t *parser)
 
 
 static njs_token_t
-njs_parser_statement_link(njs_vm_t *vm, njs_parser_t *parser,
+njs_parser_statement_chain(njs_vm_t *vm, njs_parser_t *parser,
     njs_token_t token)
 {
     njs_parser_node_t  *node, *last;
@@ -172,6 +176,12 @@ njs_parser_statement(njs_vm_t *vm, njs_parser_t *parser,
     case NJS_TOKEN_FOR:
         return njs_parser_for_statement(vm, parser);
 
+    case NJS_TOKEN_CONTINUE:
+        return njs_parser_continue_statement(vm, parser);
+
+    case NJS_TOKEN_BREAK:
+        return njs_parser_break_statement(vm, parser);
+
     case NJS_TOKEN_TRY:
         return njs_parser_try_statement(vm, parser);
 
@@ -233,7 +243,7 @@ njs_parser_block(njs_vm_t *vm, njs_parser_t *parser)
     parser->node = NULL;
 
     while (token != NJS_TOKEN_CLOSE_BRACE) {
-        token = njs_parser_statement_link(vm, parser, token);
+        token = njs_parser_statement_chain(vm, parser, token);
         if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
             return token;
         }
@@ -964,6 +974,74 @@ njs_parser_for_in_statement(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_token_t        token;
+    njs_parser_node_t  *node;
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = NJS_TOKEN_CONTINUE;
+    parser->node = node;
+    parser->code_size += sizeof(njs_vmcode_jump_t);
+
+    token = njs_lexer_token(parser->lexer);
+
+    switch (token) {
+
+    case NJS_TOKEN_SEMICOLON:
+    case NJS_TOKEN_LINE_END:
+        return njs_parser_token(parser);
+
+    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_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = NJS_TOKEN_BREAK;
+    parser->node = node;
+    parser->code_size += sizeof(njs_vmcode_jump_t);
+
+    token = njs_lexer_token(parser->lexer);
+
+    switch (token) {
+
+    case NJS_TOKEN_SEMICOLON:
+    case NJS_TOKEN_LINE_END:
+        return njs_parser_token(parser);
+
+    case NJS_TOKEN_CLOSE_BRACE:
+    case NJS_TOKEN_END:
+        return token;
+
+    default:
+        /* TODO: LABEL */
+        return NJS_TOKEN_ILLEGAL;
+    }
+}
+
+
 static njs_token_t
 njs_parser_try_statement(njs_vm_t *vm, njs_parser_t *parser)
 {
index ebb3270c9679a2e09218f8661f298d28e7e01268..01a0c895a3b95e40ec5bcfddd6f1f8b9bac150ad 100644 (file)
@@ -231,12 +231,38 @@ struct njs_parser_node_s {
     nxt_mem_cache_zalloc((vm)->mem_cache_pool, sizeof(njs_parser_node_t))
 
 
+typedef struct njs_parser_patch_s   njs_parser_patch_t;
+
+struct njs_parser_patch_s {
+    u_char                          *address;
+    njs_parser_patch_t              *next;
+};
+
+
+typedef enum {
+    NJS_PARSER_BLOCK = 0,
+    NJS_PARSER_LOOP,
+    NJS_PARSER_SWITCH,
+} njs_parser_block_type_t;
+
+typedef struct njs_parser_block_s   njs_parser_block_t;
+
+struct njs_parser_block_s {
+    njs_parser_block_type_t         type;    /* 2 bits */
+    nxt_str_t                       label;
+    njs_parser_patch_t              *continuation;
+    njs_parser_patch_t              *exit;
+    njs_parser_block_t              *next;
+};
+
+
 struct njs_parser_s {
     njs_lexer_t                     *lexer;
     njs_parser_node_t               *node;
 
     /* Vector of njs_variable_t. */
     nxt_array_t                     *arguments;
+    njs_parser_block_t              *block;
 
     nxt_lvlhsh_t                    variables_hash;
 
index 73b7eb8af9699b1963a18d6b78735d697017cf51..4a1fc500e904280c597fa99047b0eecfd7a665e4 100644 (file)
@@ -1374,6 +1374,130 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("a = 3; if (true) if (false); else; a = 2; a"),
       nxt_string("2") },
 
+    /* continue. */
+
+    { nxt_string("continue"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("do continue while (false)"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("do continue; while (false)"),
+      nxt_string("undefined") },
+
+    { nxt_string("do { continue } while (false)"),
+      nxt_string("undefined") },
+
+    { nxt_string("var i = 0; do if (i++ > 9) continue; while (i < 100); i"),
+      nxt_string("100") },
+
+    { nxt_string("while (false) continue"),
+      nxt_string("undefined") },
+
+    { nxt_string("while (false) continue;"),
+      nxt_string("undefined") },
+
+    { nxt_string("while (false) { continue }"),
+      nxt_string("undefined") },
+
+    { nxt_string("var i = 0; while (i < 100) if (i++ > 9) continue; i"),
+      nxt_string("100") },
+
+    { nxt_string("for ( ;null; ) continue"),
+      nxt_string("undefined") },
+
+    { nxt_string("for ( ;null; ) continue;"),
+      nxt_string("undefined") },
+
+    { nxt_string("for ( ;null; ) { continue }"),
+      nxt_string("undefined") },
+
+    { nxt_string("for (i = 0; i < 100; i++) if (i > 9) continue; i"),
+      nxt_string("100") },
+
+    { nxt_string("var a = []; for (i in a) continue"),
+      nxt_string("undefined") },
+
+    { nxt_string("var a = []; for (i in a) continue;"),
+      nxt_string("undefined") },
+
+    { nxt_string("var a = []; for (i in a) { continue }"),
+      nxt_string("undefined") },
+
+    { nxt_string("var a = [1,2,3,4,5]; var s = 0;"
+                 "for (i in a) { if (a[i] > 4) continue; else s += a[i] } s"),
+      nxt_string("10") },
+
+    { nxt_string("var a = [1,2,3,4,5]; var s = 0;"
+                 "for (i in a) { if (a[i] > 4) continue; s += a[i] } s"),
+      nxt_string("10") },
+
+    /* break. */
+
+    { nxt_string("break"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("do break while (true)"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("do break; while (true)"),
+      nxt_string("undefined") },
+
+    { nxt_string("do { break } while (true)"),
+      nxt_string("undefined") },
+
+    { nxt_string("var i = 0; do if (i++ > 9) break; while (i < 100); i"),
+      nxt_string("11") },
+
+    { nxt_string("while (true) break"),
+      nxt_string("undefined") },
+
+    { nxt_string("while (true) break;"),
+      nxt_string("undefined") },
+
+    { nxt_string("while (true) { break }"),
+      nxt_string("undefined") },
+
+    { nxt_string("var i = 0; while (i < 100) if (i++ > 9) break; i"),
+      nxt_string("11") },
+
+    { nxt_string("for ( ;; ) break"),
+      nxt_string("undefined") },
+
+    { nxt_string("for ( ;; ) break;"),
+      nxt_string("undefined") },
+
+    { nxt_string("for ( ;; ) { break }"),
+      nxt_string("undefined") },
+
+    { nxt_string("for (i = 0; i < 100; i++) if (i > 9) break; i"),
+      nxt_string("10") },
+
+    { nxt_string("var a = []; for (i in a) break"),
+      nxt_string("undefined") },
+
+    { nxt_string("var a = []; for (i in a) break;"),
+      nxt_string("undefined") },
+
+    { nxt_string("var a = []; for (i in a) { break }"),
+      nxt_string("undefined") },
+
+    { nxt_string("var a = [1,2,3,4,5]; var s = 0;"
+                 "for (i in a) { if (a[i] > 4) break; else s += a[i] } s"),
+      nxt_string("10") },
+
+    { nxt_string("var a = [1,2,3,4,5]; var s = 0;"
+                 "for (i in a) { if (a[i] > 4) break; s += a[i] } s"),
+      nxt_string("10") },
+
+#if 0
+    { nxt_string("var a = [1,2,3,4,5]; var s = 0;"
+                 "for (i in a) if (a[i] > 4) break; s += a[i] } s"),
+      nxt_string("segfault") },
+#endif
+
+    /**/
+
     { nxt_string("for (i = 0; i < 10; i++) { i += 1 } i"),
       nxt_string("10") },