]> granicus.if.org Git - php/commitdiff
Added support for "continue" and "break" operators with labels. Each loop or switch...
authorDmitry Stogov <dmitry@php.net>
Fri, 3 Mar 2006 13:09:13 +0000 (13:09 +0000)
committerDmitry Stogov <dmitry@php.net>
Fri, 3 Mar 2006 13:09:13 +0000 (13:09 +0000)
15 files changed:
NEWS
Zend/tests/break_label01.phpt [new file with mode: 0755]
Zend/tests/break_label02.phpt [new file with mode: 0755]
Zend/tests/break_label03.phpt [new file with mode: 0755]
Zend/tests/break_label04.phpt [new file with mode: 0755]
Zend/tests/break_label05.phpt [new file with mode: 0755]
Zend/tests/break_label06.phpt [new file with mode: 0755]
Zend/tests/break_label07.phpt [new file with mode: 0755]
Zend/tests/break_label08.inc [new file with mode: 0755]
Zend/tests/break_label08.phpt [new file with mode: 0755]
Zend/tests/break_label09.phpt [new file with mode: 0755]
Zend/zend_compile.c
Zend/zend_compile.h
Zend/zend_globals.h
Zend/zend_language_parser.y

diff --git a/NEWS b/NEWS
index 4b8bcbb44d5f8c3886e98a11365955e34b4c178a..b20bac0b772fb55a41507ea8dd847221c7e93142 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,9 @@ PHP                                                                        NEWS
   for more details. (Dmitry)
 - Removed support for "continue" and "break" operators with non-constant
   operands. (Dmitry)
+- Added support for "continue" and "break" operators with labels. Each loop or
+  switch statement can be marked by label and then it is possible to write
+  "break <label>" instead of "break <number>". (Dmitry, Sara)
 - Changed __toString() behavior to call it in all necessary places
   (Marcus, Dmitry)
 - Changed "instanceof" and "catch" operators, is_a() and is_subclass_of()
diff --git a/Zend/tests/break_label01.phpt b/Zend/tests/break_label01.phpt
new file mode 100755 (executable)
index 0000000..20e1a5c
--- /dev/null
@@ -0,0 +1,56 @@
+--TEST--
+labeled break 01: Old style "break <number>"
+--FILE--
+<?php
+while (1) {
+       echo "1: ";
+       break 1;
+       echo "bug\n";
+}
+echo "ok\n";
+
+while (1) {
+       echo "2: ";
+       while (1) {
+               break 2;
+               echo "bug\n";
+       }
+       echo "bug\n";
+}
+echo "ok\n";
+
+while (1) {
+       echo "3: ";
+       while (1) {
+               break 1;
+               echo "bug\n";
+       }
+       break 1;
+       echo "bug\n";
+}
+echo "ok\n";
+
+while (1) {
+       echo "4: ";
+       switch (1) {
+               case 1:
+                       break 2;
+                       echo "bug\n";
+       }
+       echo "bug\n";
+}
+echo "ok\n";
+
+while (1) {
+       while (1) {
+               break 3;
+       }
+}
+?>
+--EXPECTF--
+1: ok
+2: ok
+3: ok
+4: ok
+
+Fatal error: Cannot break/continue 3 levels in %sbreak_label01.php on line %d
diff --git a/Zend/tests/break_label02.phpt b/Zend/tests/break_label02.phpt
new file mode 100755 (executable)
index 0000000..7c9a673
--- /dev/null
@@ -0,0 +1,66 @@
+--TEST--
+labeled break 02: New style "break <label>"
+--FILE--
+<?php
+L1: while (1) {
+       echo "1: ";
+       break L1;
+       echo "bug\n";
+}
+
+echo "ok\n";
+
+L2: while (1) {
+       echo "2: ";
+       while (1) {
+               break L2;
+               echo "bug\n";
+       }
+       echo "bug\n";
+}
+
+echo "ok\n";
+
+L3: while (1) {
+       echo "3: ";
+       L4: while (1) {
+               break L4;
+               echo "bug\n";
+       }
+       break L3;
+       echo "bug\n";
+}
+
+echo "ok\n";
+
+L5: while (1) {
+       echo "4: ";
+       switch (1) {
+               case 1:
+                       break L5;
+                       echo "bug\n";
+       }
+       echo "bug\n";
+}
+
+echo "ok\n";
+
+L6: while (1) {
+       echo "5: ";
+       L7: switch (1) {
+               case 1:
+                       break L7;
+                       echo "bug\n";
+       }
+       break L6;
+       echo "bug\n";
+}
+
+echo "ok\n";
+?>
+--EXPECT--
+1: ok
+2: ok
+3: ok
+4: ok
+5: ok
diff --git a/Zend/tests/break_label03.phpt b/Zend/tests/break_label03.phpt
new file mode 100755 (executable)
index 0000000..b3de549
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+labeled break 03: Undefined label
+--FILE--
+<?php
+echo "bug\n";
+L1: while (1) {
+       break L2;
+}
+?>
+--EXPECTF--
+Fatal error: break to undefined label 'L2' in %sbreak_label03.php on line %d
diff --git a/Zend/tests/break_label04.phpt b/Zend/tests/break_label04.phpt
new file mode 100755 (executable)
index 0000000..b947d0f
--- /dev/null
@@ -0,0 +1,14 @@
+--TEST--
+labeled break 04: Invalid label (another loop)
+--FILE--
+<?php
+echo "bug\n";
+L1: while (1) {
+       break;
+}
+L2: while (1) {
+       break L1;
+}
+?>
+--EXPECTF--
+Fatal error: break to label 'L1', that doesn't mark outer loop in %sbreak_label04.php on line %d
diff --git a/Zend/tests/break_label05.phpt b/Zend/tests/break_label05.phpt
new file mode 100755 (executable)
index 0000000..98e7209
--- /dev/null
@@ -0,0 +1,12 @@
+--TEST--
+labeled break 05: Invalid label (non loop)
+--FILE--
+<?php
+L1:
+echo "bug\n";
+while (1) {
+       break L1;
+}
+?>
+--EXPECTF--
+Fatal error: break to label 'L1', that doesn't mark outer loop in %sbreak_label05.php on line %d
diff --git a/Zend/tests/break_label06.phpt b/Zend/tests/break_label06.phpt
new file mode 100755 (executable)
index 0000000..d2b3759
--- /dev/null
@@ -0,0 +1,12 @@
+--TEST--
+labeled break 06: Invalid label (non declared yet)
+--FILE--
+<?php
+echo "bug\n";
+while (1) {    
+       break L1;
+}
+L1:
+?>
+--EXPECTF--
+Fatal error: break to undefined label 'L1' in %sbreak_label06.php on line %d
diff --git a/Zend/tests/break_label07.phpt b/Zend/tests/break_label07.phpt
new file mode 100755 (executable)
index 0000000..eb7d40a
--- /dev/null
@@ -0,0 +1,17 @@
+--TEST--
+labeled break 07: Label redefinition 
+--FILE--
+<?php
+echo "bug\n";
+L1: while (1) {        
+       L2: while (1) { 
+               break L1;
+       }
+}
+L3: while (1) {        
+       L1: while (1) { 
+               break L3;
+       }
+}
+--EXPECTF--
+Fatal error: Label 'L1' already defined in %sbreak_label07.php on line %d
diff --git a/Zend/tests/break_label08.inc b/Zend/tests/break_label08.inc
new file mode 100755 (executable)
index 0000000..f424fb0
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+L1: while (1) {        
+       echo "2: ";
+       L2: while (1) { 
+               break L1;
+               echo "bug\n";
+       }
+       echo "bug\n";
+}
+echo "ok\n";
+?>
diff --git a/Zend/tests/break_label08.phpt b/Zend/tests/break_label08.phpt
new file mode 100755 (executable)
index 0000000..2aceb89
--- /dev/null
@@ -0,0 +1,18 @@
+--TEST--
+labeled break 08: Use the same <label> in different files
+--FILE--
+<?php
+L1: while (1) {        
+       echo "1: ";
+       L2: while (1) { 
+               break L1;
+               echo "bug\n";
+       }
+       echo "bug\n";
+}
+echo "ok\n";
+include(dirname(__FILE__)."/break_label08.inc");
+?>
+--EXPECT--
+1: ok
+2: ok
diff --git a/Zend/tests/break_label09.phpt b/Zend/tests/break_label09.phpt
new file mode 100755 (executable)
index 0000000..f5b4758
--- /dev/null
@@ -0,0 +1,26 @@
+--TEST--
+labeled break 09: Use the same <label> name in several copies of one file
+--FILE--
+<?php
+if (isset($n)) {
+       ++$n;
+} else {
+       $n = 1;
+}
+L1: while (1) {        
+       echo "$n: ";
+       L2: while (1) { 
+               break L1;
+               echo "bug\n";
+       }
+       echo "bug\n";
+}
+echo "ok\n";
+if ($n < 3) {
+       include(__FILE__);
+}
+?>
+--EXPECTF--
+1: ok
+2: ok
+3: ok
index 164599b94abc5eb28091f04ec3865d9e8a1be601..08a90cc85a3f7d2cb7632a53cc44c76c5b71a825 100644 (file)
@@ -148,6 +148,9 @@ void zend_init_compiler_data_structures(TSRMLS_D)
        CG(start_lineno) = 0;
        init_compiler_declarables(TSRMLS_C);
        zend_hash_apply(CG(auto_globals), (apply_func_t) zend_auto_global_arm TSRMLS_CC);
+       zend_stack_init(&CG(labels_stack));
+       CG(labels) = NULL;
+       CG(last_label) = NULL;
 }
 
 
@@ -174,6 +177,7 @@ void shutdown_compiler(TSRMLS_D)
        zend_hash_destroy(&CG(script_encodings_table));
        zend_hash_destroy(&CG(filenames_table));
        zend_llist_destroy(&CG(open_files));
+       zend_stack_destroy(&CG(labels_stack));
 }
 
 
@@ -675,6 +679,10 @@ static inline void do_begin_loop(TSRMLS_D)
        CG(active_op_array)->current_brk_cont = CG(active_op_array)->last_brk_cont;
        brk_cont_element = get_next_brk_cont_element(CG(active_op_array));
        brk_cont_element->parent = parent;
+       if (CG(last_label)) {
+               CG(last_label)->loop = CG(active_op_array)->current_brk_cont;
+               CG(last_label) = NULL;
+       }
 }
 
 
@@ -1241,6 +1249,10 @@ void zend_do_begin_function_declaration(znode *function_token, znode *function_n
                CG(doc_comment) = NULL;
                CG(doc_comment_len) = 0;
        }
+
+       zend_stack_push(&CG(labels_stack), (void *) &CG(labels), sizeof(HashTable*));
+       CG(labels) = NULL;
+       CG(last_label) = NULL;
 }
 
 void zend_do_handle_exception(TSRMLS_D)
@@ -1250,6 +1262,21 @@ void zend_do_handle_exception(TSRMLS_D)
        opline->opcode = ZEND_HANDLE_EXCEPTION;
        SET_UNUSED(opline->op1);
        SET_UNUSED(opline->op2);
+
+       if (CG(labels)) {
+               zend_hash_destroy(CG(labels));
+               FREE_HASHTABLE(CG(labels));
+       }
+       if (!zend_stack_is_empty(&CG(labels_stack))) {
+               HashTable **pht;
+
+               zend_stack_top(&CG(labels_stack), (void**)&pht);
+               CG(labels) = *pht;
+               zend_stack_del_top(&CG(labels_stack));
+       } else {
+               CG(labels) = NULL;
+       }
+       CG(last_label) = NULL;
 }
 
 
@@ -2665,8 +2692,37 @@ void zend_do_brk_cont(zend_uchar op, znode *expr TSRMLS_DC)
        if (expr) {
                if (expr->op_type != IS_CONST) {
                        zend_error(E_COMPILE_ERROR, "'%s' operator with non-constant operand is no longer supported", op == ZEND_BRK ? "break" : "continue");
-               } else if (Z_TYPE(expr->u.constant) != IS_LONG || Z_LVAL(expr->u.constant) < 1) {
-                       zend_error(E_COMPILE_ERROR, "'%s' operator accepts only positive numbers", op == ZEND_BRK ? "break" : "continue");
+               } else {
+                       if (Z_TYPE(expr->u.constant) == IS_STRING ||
+                           Z_TYPE(expr->u.constant) == IS_UNICODE) {
+                               zend_label *label;
+
+                               if (CG(labels) == NULL ||
+                                   zend_u_hash_find(CG(labels), Z_TYPE(expr->u.constant), Z_UNIVAL(expr->u.constant), Z_UNILEN(expr->u.constant)+1, (void**)&label) == FAILURE) {
+                                       zend_error(E_COMPILE_ERROR, "%s to undefined label '%R'", op == ZEND_BRK ? "break" : "continue", Z_TYPE(expr->u.constant), Z_UNIVAL(expr->u.constant));
+                               }
+                               
+                               if (label->loop != -1) {
+                                       long distance = 1;
+                                       long current = CG(active_op_array)->current_brk_cont;
+
+                                       while (current != -1) {
+                                               if (label->loop == current) {
+                                                       zval_dtor(&expr->u.constant);
+                                                       Z_TYPE(expr->u.constant) = IS_LONG;
+                                                       Z_LVAL(expr->u.constant) = distance;
+                                                       break;
+                                               }
+                                               distance++;
+                                               current = CG(active_op_array)->brk_cont_array[current].parent;
+                                       }
+                               }
+                               if (Z_TYPE(expr->u.constant) != IS_LONG) {
+                                       zend_error(E_COMPILE_ERROR, "%s to label '%R', that doesn't mark outer loop", op == ZEND_BRK ? "break" : "continue", Z_TYPE(expr->u.constant), Z_UNIVAL(expr->u.constant));
+                               }
+                       } else if (Z_TYPE(expr->u.constant) != IS_LONG || Z_LVAL(expr->u.constant) < 1) {
+                               zend_error(E_COMPILE_ERROR, "'%s' operator accepts only positive numbers and labels", op == ZEND_BRK ? "break" : "continue");
+                       }
                }
                opline->op2 = *expr;
        } else {
@@ -4315,6 +4371,30 @@ void zend_do_normalization(znode *result, znode *str TSRMLS_DC)
        *result = opline->result;
 }
 
+void zend_do_label(znode *label TSRMLS_DC)
+{
+       zend_op_array *oparray = CG(active_op_array);
+       zend_label dest;
+
+       if (!CG(labels)) {
+               ALLOC_HASHTABLE(CG(labels));
+               zend_hash_init(CG(labels), 4, NULL, NULL, 0);
+       }
+
+       dest.brk_cont = oparray->current_brk_cont;
+       dest.loop = -1;
+       dest.opline_num = get_next_op_number(oparray);
+
+       if (zend_u_hash_add(CG(labels), Z_TYPE(label->u.constant), Z_UNIVAL(label->u.constant),
+                                       Z_UNILEN(label->u.constant) + 1, (void**)&dest, sizeof(zend_label), (void**)&CG(last_label)) == FAILURE) {
+               CG(last_label) = NULL;
+               zend_error(E_COMPILE_ERROR, "Label '%R' already defined", Z_TYPE(label->u.constant), Z_UNIVAL(label->u.constant));
+       }
+
+       /* Done with label now */
+       zval_dtor(&label->u.constant);
+}
+
 /*
  * Local variables:
  * tab-width: 4
index cb5d5bf9c2577601158886a9f7f12a10d534bc57..51da2581c59b48296feb09fcb0ee1c269c89d4e8 100644 (file)
@@ -94,6 +94,11 @@ typedef struct _zend_brk_cont_element {
        int parent;
 } zend_brk_cont_element;
 
+typedef struct _zend_label {
+       int brk_cont;
+       int loop;
+       zend_uint opline_num;
+} zend_label;
 
 typedef struct _zend_try_catch_element {
        zend_uint try_op;
@@ -513,6 +518,8 @@ ZEND_API void function_add_ref(zend_function *function TSRMLS_DC);
 
 void zend_do_normalization(znode *result, znode *str TSRMLS_DC);
 
+void zend_do_label(znode *label TSRMLS_DC);
+
 #define INITIAL_OP_ARRAY_SIZE 64
 
 
index 6af32473b1cca3d39bb6528e06c823c978c727bb..a397c6597433430c82a786f5f0acf940c75f756c 100644 (file)
@@ -136,6 +136,10 @@ struct _zend_compiler_globals {
        HashTable script_encodings_table;
        char *script_encoding;
 
+       HashTable *labels;
+       zend_label *last_label;
+       zend_stack labels_stack;
+
 #ifdef ZTS
        HashTable **static_members;
        int last_static_member;
index a0addb86dc371059a1c6a6e8706c5147f152dae4..69ea6fb657c599a58c626d55827393d85b997f27 100644 (file)
@@ -183,11 +183,12 @@ inner_statement:
 
 
 statement:
-               unticked_statement { zend_do_ticks(TSRMLS_C); }
+               unticked_statement { CG(last_label) = NULL; zend_do_ticks(TSRMLS_C); }
+       |       T_STRING ':' { zend_do_label(&$1 TSRMLS_CC); }
 ;
 
 unticked_statement:
-               '{' inner_statement_list '}'
+               '{' { CG(last_label) = NULL; } inner_statement_list '}'
        |       T_IF '(' expr ')' { zend_do_if_cond(&$3, &$4 TSRMLS_CC); } statement { zend_do_if_after_statement(&$4, 1 TSRMLS_CC); } elseif_list else_single { zend_do_if_end(TSRMLS_C); }
        |       T_IF '(' expr ')' ':' { zend_do_if_cond(&$3, &$4 TSRMLS_CC); } inner_statement_list { zend_do_if_after_statement(&$4, 1 TSRMLS_CC); } new_elseif_list new_else_single T_ENDIF ';' { zend_do_if_end(TSRMLS_C); }
        |       T_WHILE '(' { $1.u.opline_num = get_next_op_number(CG(active_op_array));  } expr  ')' { zend_do_while_cond(&$4, &$5 TSRMLS_CC); } while_statement { zend_do_while_end(&$1, &$5 TSRMLS_CC); }
@@ -203,9 +204,11 @@ unticked_statement:
                        for_statement { zend_do_for_end(&$7 TSRMLS_CC); }
        |       T_SWITCH '(' expr ')'   { zend_do_switch_cond(&$3 TSRMLS_CC); } switch_case_list { zend_do_switch_end(&$6 TSRMLS_CC); }
        |       T_BREAK ';'                             { zend_do_brk_cont(ZEND_BRK, NULL TSRMLS_CC); }
-       |       T_BREAK expr ';'                { zend_do_brk_cont(ZEND_BRK, &$2 TSRMLS_CC); }
+       |       T_BREAK T_LNUMBER ';'           { zend_do_brk_cont(ZEND_BRK, &$2 TSRMLS_CC); }
+       |       T_BREAK T_STRING ';'            { zend_do_brk_cont(ZEND_BRK, &$2 TSRMLS_CC); }
        |       T_CONTINUE ';'                  { zend_do_brk_cont(ZEND_CONT, NULL TSRMLS_CC); }
-       |       T_CONTINUE expr ';'             { zend_do_brk_cont(ZEND_CONT, &$2 TSRMLS_CC); }
+       |       T_CONTINUE T_LNUMBER ';'                { zend_do_brk_cont(ZEND_CONT, &$2 TSRMLS_CC); }
+       |       T_CONTINUE T_STRING ';'         { zend_do_brk_cont(ZEND_CONT, &$2 TSRMLS_CC); }
        |       T_RETURN ';'                                            { zend_do_return(NULL, 0 TSRMLS_CC); }
        |       T_RETURN expr_without_variable ';'      { zend_do_return(&$2, 0 TSRMLS_CC); }
        |       T_RETURN variable ';'                           { zend_do_return(&$2, 1 TSRMLS_CC); }