From a1b2dc6f2c1bcd5d953022a592a82f38c14ccf7a Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 23 Mar 2020 17:18:47 +0100 Subject: [PATCH 1/5] Implement switch expression --- Zend/tests/switch/001.phpt | 36 ++++++++++++++++++ Zend/tests/switch/002.phpt | 19 ++++++++++ Zend/tests/switch/003.phpt | 24 ++++++++++++ Zend/tests/switch/004.phpt | 31 ++++++++++++++++ Zend/tests/switch/005.phpt | 12 ++++++ Zend/tests/switch/006.phpt | 13 +++++++ Zend/tests/switch/007.phpt | 26 +++++++++++++ Zend/tests/switch/008.phpt | 23 ++++++++++++ Zend/zend_compile.c | 47 ++++++++++++++++++++++-- Zend/zend_compile.h | 3 ++ Zend/zend_language_parser.y | 24 ++++++++++++ ext/tokenizer/tests/PhpToken_getAll.phpt | 12 +++--- ext/tokenizer/tokenizer_data.c | 4 +- 13 files changed, 263 insertions(+), 11 deletions(-) create mode 100644 Zend/tests/switch/001.phpt create mode 100644 Zend/tests/switch/002.phpt create mode 100644 Zend/tests/switch/003.phpt create mode 100644 Zend/tests/switch/004.phpt create mode 100644 Zend/tests/switch/005.phpt create mode 100644 Zend/tests/switch/006.phpt create mode 100644 Zend/tests/switch/007.phpt create mode 100644 Zend/tests/switch/008.phpt diff --git a/Zend/tests/switch/001.phpt b/Zend/tests/switch/001.phpt new file mode 100644 index 0000000000000..06be2d8610ed9 --- /dev/null +++ b/Zend/tests/switch/001.phpt @@ -0,0 +1,36 @@ +--TEST-- +Basic switch expression functionality test +--FILE-- + 'Zero', + 1 => 'One', + 2 => 'Two', + 3 => 'Three', + 4 => 'Four', + 5 => 'Five', + 6 => 'Six', + 7 => 'Seven', + 8 => 'Eight', + 9 => 'Nine', + }; +} + +for ($i = 0; $i <= 9; $i++) { + print wordify($i) . "\n"; +} + +?> +--EXPECT-- +Zero +One +Two +Three +Four +Five +Six +Seven +Eight +Nine diff --git a/Zend/tests/switch/002.phpt b/Zend/tests/switch/002.phpt new file mode 100644 index 0000000000000..62f80d7f4ff24 --- /dev/null +++ b/Zend/tests/switch/002.phpt @@ -0,0 +1,19 @@ +--TEST-- +Switch expression omit trailing comma +--FILE-- + "true\n", + false => "false\n" + }; +} + +print_bool(true); +print_bool(false); + +?> +--EXPECT-- +true +false diff --git a/Zend/tests/switch/003.phpt b/Zend/tests/switch/003.phpt new file mode 100644 index 0000000000000..42c39a9cd0ebe --- /dev/null +++ b/Zend/tests/switch/003.phpt @@ -0,0 +1,24 @@ +--TEST-- +Switch expression default case +--FILE-- + 1, + 2 => 2, + default => 'default', + }; +} + +echo get_value(0) . "\n"; +echo get_value(1) . "\n"; +echo get_value(2) . "\n"; +echo get_value(3) . "\n"; + +?> +--EXPECT-- +default +1 +2 +default diff --git a/Zend/tests/switch/004.phpt b/Zend/tests/switch/004.phpt new file mode 100644 index 0000000000000..411bbed7f0d63 --- /dev/null +++ b/Zend/tests/switch/004.phpt @@ -0,0 +1,31 @@ +--TEST-- +Switch expression with true as expression +--FILE-- += 50 => '50+', + $i >= 40 => '40-50', + $i >= 30 => '30-40', + $i >= 20 => '20-30', + $i >= 10 => '10-20', + default => '0-10', + }; +} + +echo get_range(22) . "\n"; +echo get_range(0) . "\n"; +echo get_range(59) . "\n"; +echo get_range(13) . "\n"; +echo get_range(39) . "\n"; +echo get_range(40) . "\n"; + +?> +--EXPECT-- +20-30 +0-10 +50+ +10-20 +30-40 +40-50 diff --git a/Zend/tests/switch/005.phpt b/Zend/tests/switch/005.phpt new file mode 100644 index 0000000000000..1f45794bbf050 --- /dev/null +++ b/Zend/tests/switch/005.phpt @@ -0,0 +1,12 @@ +--TEST-- +Switch expression discarding result +--FILE-- + print "Executed\n", +}; + +?> +--EXPECT-- +Executed diff --git a/Zend/tests/switch/006.phpt b/Zend/tests/switch/006.phpt new file mode 100644 index 0000000000000..3563261ef4dad --- /dev/null +++ b/Zend/tests/switch/006.phpt @@ -0,0 +1,13 @@ +--TEST-- +Switch expression with no cases +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught InvalidArgumentException in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/switch/007.phpt b/Zend/tests/switch/007.phpt new file mode 100644 index 0000000000000..ee344c6cbf8f8 --- /dev/null +++ b/Zend/tests/switch/007.phpt @@ -0,0 +1,26 @@ +--TEST-- +Switch expression exception on unhandled case +--FILE-- + 1, + 2 => 2, + }; +} + +echo get_value(1) . "\n"; +echo get_value(2) . "\n"; +echo get_value(3) . "\n"; + +?> +--EXPECTF-- +1 +2 + +Fatal error: Uncaught InvalidArgumentException in %s +Stack trace: +#0 %s: get_value(3) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/switch/008.phpt b/Zend/tests/switch/008.phpt new file mode 100644 index 0000000000000..85733ed5d3837 --- /dev/null +++ b/Zend/tests/switch/008.phpt @@ -0,0 +1,23 @@ +--TEST-- +Switch expression precedence +--FILE-- + "! has higher precedence\n" +}; + +$throwableInterface = Throwable::class; +print new RuntimeException() instanceof $throwableInterface switch { + true => "instanceof has higher precedence\n" +}; + +print 10 ** 2 switch { + 100 => "** has higher precedence\n" +}; + +?> +--EXPECT-- +! has higher precedence +instanceof has higher precedence +** has higher precedence diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ff466c5ef5740..8baaab66286cf 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5037,13 +5037,14 @@ static zend_bool should_use_jumptable(zend_ast_list *cases, zend_uchar jumptable } } -void zend_compile_switch(zend_ast *ast) /* {{{ */ +void zend_compile_switch(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *expr_ast = ast->child[0]; zend_ast_list *cases = zend_ast_get_list(ast->child[1]); uint32_t i; zend_bool has_default_case = 0; + zend_bool is_switch_expr = (ast->attr & ZEND_SWITCH_EXPRESSION) != 0; znode expr_node, case_node; zend_op *opline; @@ -5051,6 +5052,12 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */ zend_uchar jumptable_type; HashTable *jumptable = NULL; + // If the switch expression has no cases the result is never set + if (result != NULL) { + result->op_type = IS_CONST; + ZVAL_NULL(&result->u.constant); + } + zend_compile_expr(&expr_node, expr_ast); zend_begin_loop(ZEND_FREE, &expr_node, 1); @@ -5114,6 +5121,7 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */ } opnum_default_jmp = zend_emit_jump(0); + zend_bool is_first_case = 1; for (i = 0; i < cases->children; ++i) { zend_ast *case_ast = cases->child[i]; @@ -5146,7 +5154,23 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */ } } - zend_compile_stmt(stmt_ast); + if (is_switch_expr) { + znode cond_stmt_node; + zend_compile_expr(&cond_stmt_node, stmt_ast); + + if (is_first_case) { + zend_emit_op_tmp(result, ZEND_QM_ASSIGN, &cond_stmt_node, NULL); + is_first_case = 0; + } else { + zend_op *opline_qm_assign = zend_emit_op(NULL, ZEND_QM_ASSIGN, &cond_stmt_node, NULL); + SET_NODE(opline_qm_assign->result, result); + } + + zend_ast *break_ast = zend_ast_create(ZEND_AST_BREAK, NULL); + zend_compile_break_continue(break_ast); + } else { + zend_compile_stmt(stmt_ast); + } } if (!has_default_case) { @@ -5156,6 +5180,20 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */ opline = &CG(active_op_array)->opcodes[opnum_switch]; opline->extended_value = get_next_op_number(); } + + // Generate default case for switch expression + if (is_switch_expr) { + zval exception_name; + ZVAL_STRING(&exception_name, "InvalidArgumentException"); + zend_ast *exception_name_ast = zend_ast_create_zval(&exception_name); + + zend_ast *exception_args_ast = zend_ast_create_list(0, ZEND_AST_ARG_LIST); + zend_ast *new_exception_ast = zend_ast_create(ZEND_AST_NEW, exception_name_ast, exception_args_ast); + zend_ast *throw_ast = zend_ast_create(ZEND_AST_THROW, new_exception_ast); + zend_compile_throw(throw_ast); + + zval_ptr_dtor(&exception_name); + } } zend_end_loop(get_next_op_number(), &expr_node); @@ -8782,7 +8820,7 @@ void zend_compile_stmt(zend_ast *ast) /* {{{ */ zend_compile_if(ast); break; case ZEND_AST_SWITCH: - zend_compile_switch(ast); + zend_compile_switch(NULL, ast); break; case ZEND_AST_TRY: zend_compile_try(ast); @@ -8965,6 +9003,9 @@ void zend_compile_expr(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_ARROW_FUNC: zend_compile_func_decl(result, ast, 0); return; + case ZEND_AST_SWITCH: + zend_compile_switch(result, ast); + break; default: ZEND_ASSERT(0 /* not supported */); } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 8a64ed86c3d03..282537fbdc7ca 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1001,6 +1001,9 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf, /* Attribute for ternary inside parentheses */ #define ZEND_PARENTHESIZED_CONDITIONAL 1 +/* Attribute for switch expression */ +#define ZEND_SWITCH_EXPRESSION 1 + /* For "use" AST nodes and the seen symbol table */ #define ZEND_SYMBOL_CLASS (1<<0) #define ZEND_SYMBOL_FUNCTION (1<<1) diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index c1ced9a35aa21..871c1591e3c55 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -74,6 +74,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %left T_SL T_SR %left '+' '-' %left '*' '/' '%' +%precedence T_SWITCH %precedence '!' %precedence T_INSTANCEOF %precedence '~' T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@' @@ -257,6 +258,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type isset_variable type return_type type_expr type_without_static %type identifier type_expr_without_static union_type_without_static %type inline_function union_type +%type switch_expr_case_list non_empty_switch_expr_case_list switch_expr_case %type returns_ref function fn is_reference is_variadic variable_modifiers %type method_modifiers non_empty_member_modifiers member_modifier @@ -594,6 +596,24 @@ case_separator: | ';' ; +switch_expr_case_list: + %empty { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); } + | non_empty_switch_expr_case_list possible_comma { $$ = $1; } +; + +non_empty_switch_expr_case_list: + switch_expr_case + { $$ = zend_ast_create_list(1, ZEND_AST_SWITCH_LIST, $1); } + | non_empty_switch_expr_case_list ',' switch_expr_case + { $$ = zend_ast_list_add($1, $3); } +; + +switch_expr_case: + expr T_DOUBLE_ARROW expr + { $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, $1, $3); } + | T_DEFAULT T_DOUBLE_ARROW expr + { $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $3); } +; while_statement: statement { $$ = $1; } @@ -1021,6 +1041,10 @@ expr: | T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } | inline_function { $$ = $1; } | T_STATIC inline_function { $$ = $2; ((zend_ast_decl *) $$)->flags |= ZEND_ACC_STATIC; } + | expr T_SWITCH '{' switch_expr_case_list '}' { + $$ = zend_ast_create(ZEND_AST_SWITCH, $1, $4); + $$->attr = ZEND_SWITCH_EXPRESSION; + } ; diff --git a/ext/tokenizer/tests/PhpToken_getAll.phpt b/ext/tokenizer/tests/PhpToken_getAll.phpt index 604a979023ed7..add3f195aa7ee 100644 --- a/ext/tokenizer/tests/PhpToken_getAll.phpt +++ b/ext/tokenizer/tests/PhpToken_getAll.phpt @@ -54,7 +54,7 @@ array(15) { [3]=> object(PhpToken)#4 (4) { ["id"]=> - int(310) + int(311) ["text"]=> string(3) "foo" ["line"]=> @@ -121,7 +121,7 @@ array(15) { [9]=> object(PhpToken)#10 (4) { ["id"]=> - int(324) + int(325) ["text"]=> string(4) "echo" ["line"]=> @@ -143,7 +143,7 @@ array(15) { [11]=> object(PhpToken)#12 (4) { ["id"]=> - int(314) + int(315) ["text"]=> string(5) ""bar"" ["line"]=> @@ -224,7 +224,7 @@ array(15) { [3]=> object(PhpToken)#12 (4) { ["id"]=> - int(310) + int(311) ["text"]=> string(3) "foo" ["line"]=> @@ -291,7 +291,7 @@ array(15) { [9]=> object(PhpToken)#6 (4) { ["id"]=> - int(324) + int(325) ["text"]=> string(4) "echo" ["line"]=> @@ -313,7 +313,7 @@ array(15) { [11]=> object(PhpToken)#4 (4) { ["id"]=> - int(314) + int(315) ["text"]=> string(5) ""bar"" ["line"]=> diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 3ddf89521a8cd..fcf1975e345d9 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -61,6 +61,7 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_IS_GREATER_OR_EQUAL", T_IS_GREATER_OR_EQUAL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_SL", T_SL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_SR", T_SR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_SWITCH", T_SWITCH, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INSTANCEOF", T_INSTANCEOF, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INT_CAST", T_INT_CAST, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DOUBLE_CAST", T_DOUBLE_CAST, CONST_CS | CONST_PERSISTENT); @@ -100,7 +101,6 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_DECLARE", T_DECLARE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ENDDECLARE", T_ENDDECLARE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_AS", T_AS, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("T_SWITCH", T_SWITCH, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ENDSWITCH", T_ENDSWITCH, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_CASE", T_CASE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DEFAULT", T_DEFAULT, CONST_CS | CONST_PERSISTENT); @@ -204,6 +204,7 @@ char *get_token_type_name(int token_type) case T_IS_GREATER_OR_EQUAL: return "T_IS_GREATER_OR_EQUAL"; case T_SL: return "T_SL"; case T_SR: return "T_SR"; + case T_SWITCH: return "T_SWITCH"; case T_INSTANCEOF: return "T_INSTANCEOF"; case T_INT_CAST: return "T_INT_CAST"; case T_DOUBLE_CAST: return "T_DOUBLE_CAST"; @@ -243,7 +244,6 @@ char *get_token_type_name(int token_type) case T_DECLARE: return "T_DECLARE"; case T_ENDDECLARE: return "T_ENDDECLARE"; case T_AS: return "T_AS"; - case T_SWITCH: return "T_SWITCH"; case T_ENDSWITCH: return "T_ENDSWITCH"; case T_CASE: return "T_CASE"; case T_DEFAULT: return "T_DEFAULT"; From 77bd88756e441b458da3aaccc806671d3579b9a9 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 26 Mar 2020 23:47:04 +0100 Subject: [PATCH 2/5] Allow multiple conditions per switch case --- Zend/tests/switch/009.phpt | 25 +++++++ Zend/tests/switch/010.phpt | 31 ++++++++ Zend/zend_ast.c | 2 +- Zend/zend_ast.h | 1 + Zend/zend_compile.c | 139 ++++++++++++++++++++++-------------- Zend/zend_language_parser.y | 13 +++- 6 files changed, 155 insertions(+), 56 deletions(-) create mode 100644 Zend/tests/switch/009.phpt create mode 100644 Zend/tests/switch/010.phpt diff --git a/Zend/tests/switch/009.phpt b/Zend/tests/switch/009.phpt new file mode 100644 index 0000000000000..0d7beeb51c9a0 --- /dev/null +++ b/Zend/tests/switch/009.phpt @@ -0,0 +1,25 @@ +--TEST-- +Switch expression multiple conditions per case +--FILE-- + false, + 2, 3, 4, 5, 6 => true, + }; +} + +for ($i = 1; $i <= 7; $i++) { + var_dump(is_working_day($i)); +} + +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) diff --git a/Zend/tests/switch/010.phpt b/Zend/tests/switch/010.phpt new file mode 100644 index 0000000000000..2ef0b1067cdcd --- /dev/null +++ b/Zend/tests/switch/010.phpt @@ -0,0 +1,31 @@ +--TEST-- +Switch statement multiple conditions per case +--FILE-- + +--EXPECT-- +NULL +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +NULL diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 7cfc0450fd84e..2e7d4a3e94c3a 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1862,7 +1862,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio zend_ast_export_indent(str, indent); if (ast->child[0]) { smart_str_appends(str, "case "); - zend_ast_export_ex(str, ast->child[0], 0, indent); + zend_ast_export_list(str, (zend_ast_list*)ast->child[0], 1, 0, indent); smart_str_appends(str, ":\n"); } else { smart_str_appends(str, "default:\n"); diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 5b8aae6f96c25..ff012d888cc94 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -52,6 +52,7 @@ enum _zend_ast_kind { ZEND_AST_STMT_LIST, ZEND_AST_IF, ZEND_AST_SWITCH_LIST, + ZEND_AST_SWITCH_CASE_COND_LIST, ZEND_AST_CATCH_LIST, ZEND_AST_PARAM_LIST, ZEND_AST_CLOSURE_USES, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 8baaab66286cf..90dbcbcc9c9bc 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -4981,41 +4981,62 @@ void zend_compile_if(zend_ast *ast) /* {{{ */ } /* }}} */ +static uint32_t count_switch_conds(zend_ast_list *cases) +{ + uint32_t num_conds = 0; + + for (uint32_t i = 0; i < cases->children; i++) { + zend_ast *case_ast = cases->child[i]; + if (case_ast->child[0] == NULL) { + continue; + } + + zend_ast_list *conds = zend_ast_get_list(case_ast->child[0]); + num_conds += conds->children; + } + + return num_conds; +} + static zend_uchar determine_switch_jumptable_type(zend_ast_list *cases) { uint32_t i; zend_uchar common_type = IS_UNDEF; for (i = 0; i < cases->children; i++) { zend_ast *case_ast = cases->child[i]; - zend_ast **cond_ast = &case_ast->child[0]; zval *cond_zv; if (!case_ast->child[0]) { /* Skip default clause */ continue; } - zend_eval_const_expr(cond_ast); - if ((*cond_ast)->kind != ZEND_AST_ZVAL) { - /* Non-constant case */ - return IS_UNDEF; - } + zend_ast_list *conds = zend_ast_get_list(case_ast->child[0]); + for (uint32_t j = 0; j < conds->children; j++) { + zend_ast **cond_ast = &conds->child[j]; - cond_zv = zend_ast_get_zval(case_ast->child[0]); - if (Z_TYPE_P(cond_zv) != IS_LONG && Z_TYPE_P(cond_zv) != IS_STRING) { - /* We only optimize switched on integers and strings */ - return IS_UNDEF; - } + zend_eval_const_expr(cond_ast); + if ((*cond_ast)->kind != ZEND_AST_ZVAL) { + /* Non-constant case */ + return IS_UNDEF; + } - if (common_type == IS_UNDEF) { - common_type = Z_TYPE_P(cond_zv); - } else if (common_type != Z_TYPE_P(cond_zv)) { - /* Non-uniform case types */ - return IS_UNDEF; - } + cond_zv = zend_ast_get_zval(*cond_ast); + if (Z_TYPE_P(cond_zv) != IS_LONG && Z_TYPE_P(cond_zv) != IS_STRING) { + /* We only optimize switched on integers and strings */ + return IS_UNDEF; + } - if (Z_TYPE_P(cond_zv) == IS_STRING - && is_numeric_string(Z_STRVAL_P(cond_zv), Z_STRLEN_P(cond_zv), NULL, NULL, 0)) { - /* Numeric strings cannot be compared with a simple hash lookup */ - return IS_UNDEF; + if (common_type == IS_UNDEF) { + common_type = Z_TYPE_P(cond_zv); + } else if (common_type != Z_TYPE_P(cond_zv)) { + /* Non-uniform case types */ + return IS_UNDEF; + } + + if (Z_TYPE_P(cond_zv) == IS_STRING + && is_numeric_string(Z_STRVAL_P(cond_zv), Z_STRLEN_P(cond_zv), NULL, NULL, 0)) { + /* Numeric strings cannot be compared with a simple hash lookup */ + return IS_UNDEF; + } } } @@ -5083,13 +5104,14 @@ void zend_compile_switch(znode *result, zend_ast *ast) /* {{{ */ opnum_switch = opline - CG(active_op_array)->opcodes; } - jmpnz_opnums = safe_emalloc(sizeof(uint32_t), cases->children, 0); + uint32_t num_conds = count_switch_conds(cases); + uint32_t cond_count = 0; + jmpnz_opnums = safe_emalloc(sizeof(uint32_t), num_conds, 0); for (i = 0; i < cases->children; ++i) { zend_ast *case_ast = cases->child[i]; - zend_ast *cond_ast = case_ast->child[0]; znode cond_node; - if (!cond_ast) { + if (case_ast->child[0] == NULL) { if (has_default_case) { CG(zend_lineno) = case_ast->lineno; zend_error_noreturn(E_COMPILE_ERROR, @@ -5099,50 +5121,63 @@ void zend_compile_switch(znode *result, zend_ast *ast) /* {{{ */ continue; } - zend_compile_expr(&cond_node, cond_ast); + zend_ast_list *conds = zend_ast_get_list(case_ast->child[0]); + for (uint32_t j = 0; j < conds->children; j++) { + zend_ast *cond_ast = conds->child[j]; + zend_compile_expr(&cond_node, cond_ast); + + if (expr_node.op_type == IS_CONST + && Z_TYPE(expr_node.u.constant) == IS_FALSE) { + jmpnz_opnums[cond_count] = zend_emit_cond_jump(ZEND_JMPZ, &cond_node, 0); + } else if (expr_node.op_type == IS_CONST + && Z_TYPE(expr_node.u.constant) == IS_TRUE) { + jmpnz_opnums[cond_count] = zend_emit_cond_jump(ZEND_JMPNZ, &cond_node, 0); + } else { + opline = zend_emit_op(NULL, + (expr_node.op_type & (IS_VAR|IS_TMP_VAR)) ? ZEND_CASE : ZEND_IS_EQUAL, + &expr_node, &cond_node); + SET_NODE(opline->result, &case_node); + if (opline->op1_type == IS_CONST) { + Z_TRY_ADDREF_P(CT_CONSTANT(opline->op1)); + } - if (expr_node.op_type == IS_CONST - && Z_TYPE(expr_node.u.constant) == IS_FALSE) { - jmpnz_opnums[i] = zend_emit_cond_jump(ZEND_JMPZ, &cond_node, 0); - } else if (expr_node.op_type == IS_CONST - && Z_TYPE(expr_node.u.constant) == IS_TRUE) { - jmpnz_opnums[i] = zend_emit_cond_jump(ZEND_JMPNZ, &cond_node, 0); - } else { - opline = zend_emit_op(NULL, - (expr_node.op_type & (IS_VAR|IS_TMP_VAR)) ? ZEND_CASE : ZEND_IS_EQUAL, - &expr_node, &cond_node); - SET_NODE(opline->result, &case_node); - if (opline->op1_type == IS_CONST) { - Z_TRY_ADDREF_P(CT_CONSTANT(opline->op1)); + jmpnz_opnums[cond_count] = zend_emit_cond_jump(ZEND_JMPNZ, &case_node, 0); } - jmpnz_opnums[i] = zend_emit_cond_jump(ZEND_JMPNZ, &case_node, 0); + cond_count++; } } opnum_default_jmp = zend_emit_jump(0); zend_bool is_first_case = 1; + cond_count = 0; for (i = 0; i < cases->children; ++i) { zend_ast *case_ast = cases->child[i]; - zend_ast *cond_ast = case_ast->child[0]; zend_ast *stmt_ast = case_ast->child[1]; - if (cond_ast) { - zend_update_jump_target_to_next(jmpnz_opnums[i]); + if (case_ast->child[0] != NULL) { + zend_ast_list *conds = zend_ast_get_list(case_ast->child[0]); - if (jumptable) { - zval *cond_zv = zend_ast_get_zval(cond_ast); - zval jmp_target; - ZVAL_LONG(&jmp_target, get_next_op_number()); + for (uint32_t j = 0; j < conds->children; j++) { + zend_ast *cond_ast = conds->child[j]; + zend_update_jump_target_to_next(jmpnz_opnums[cond_count]); - ZEND_ASSERT(Z_TYPE_P(cond_zv) == jumptable_type); - if (Z_TYPE_P(cond_zv) == IS_LONG) { - zend_hash_index_add(jumptable, Z_LVAL_P(cond_zv), &jmp_target); - } else { - ZEND_ASSERT(Z_TYPE_P(cond_zv) == IS_STRING); - zend_hash_add(jumptable, Z_STR_P(cond_zv), &jmp_target); + if (jumptable) { + zval *cond_zv = zend_ast_get_zval(cond_ast); + zval jmp_target; + ZVAL_LONG(&jmp_target, get_next_op_number()); + + ZEND_ASSERT(Z_TYPE_P(cond_zv) == jumptable_type); + if (Z_TYPE_P(cond_zv) == IS_LONG) { + zend_hash_index_add(jumptable, Z_LVAL_P(cond_zv), &jmp_target); + } else { + ZEND_ASSERT(Z_TYPE_P(cond_zv) == IS_STRING); + zend_hash_add(jumptable, Z_STR_P(cond_zv), &jmp_target); + } } + + cond_count++; } } else { zend_update_jump_target_to_next(opnum_default_jmp); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 871c1591e3c55..ff7ce018d527b 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -258,7 +258,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type isset_variable type return_type type_expr type_without_static %type identifier type_expr_without_static union_type_without_static %type inline_function union_type -%type switch_expr_case_list non_empty_switch_expr_case_list switch_expr_case +%type case_cond_list switch_expr_case_list non_empty_switch_expr_case_list switch_expr_case %type returns_ref function fn is_reference is_variadic variable_modifiers %type method_modifiers non_empty_member_modifiers member_modifier @@ -585,12 +585,19 @@ switch_case_list: case_list: %empty { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); } - | case_list T_CASE expr case_separator inner_statement_list + | case_list T_CASE case_cond_list case_separator inner_statement_list { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, $3, $5)); } | case_list T_DEFAULT case_separator inner_statement_list { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $4)); } ; +case_cond_list: + expr + { $$ = zend_ast_create_list(1, ZEND_AST_SWITCH_CASE_COND_LIST, $1); } + | case_cond_list ',' expr + { $$ = zend_ast_list_add($1, $3); } +; + case_separator: ':' | ';' @@ -609,7 +616,7 @@ non_empty_switch_expr_case_list: ; switch_expr_case: - expr T_DOUBLE_ARROW expr + case_cond_list T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, $1, $3); } | T_DEFAULT T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $3); } From 0fdb273ffdd665bd4f7350b9efe766ae74168be0 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Sat, 28 Mar 2020 22:38:20 +0100 Subject: [PATCH 3/5] Add UnhandledSwitchCaseError --- Zend/tests/switch/006.phpt | 2 +- Zend/tests/switch/007.phpt | 2 +- Zend/zend_compile.c | 2 +- Zend/zend_exceptions.c | 5 +++++ Zend/zend_exceptions.h | 1 + 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Zend/tests/switch/006.phpt b/Zend/tests/switch/006.phpt index 3563261ef4dad..3f429119c7907 100644 --- a/Zend/tests/switch/006.phpt +++ b/Zend/tests/switch/006.phpt @@ -7,7 +7,7 @@ $x = true switch {}; ?> --EXPECTF-- -Fatal error: Uncaught InvalidArgumentException in %s:%d +Fatal error: Uncaught UnhandledSwitchCaseError in %s:%d Stack trace: #0 {main} thrown in %s on line %d diff --git a/Zend/tests/switch/007.phpt b/Zend/tests/switch/007.phpt index ee344c6cbf8f8..374f7d4a5d7b2 100644 --- a/Zend/tests/switch/007.phpt +++ b/Zend/tests/switch/007.phpt @@ -19,7 +19,7 @@ echo get_value(3) . "\n"; 1 2 -Fatal error: Uncaught InvalidArgumentException in %s +Fatal error: Uncaught UnhandledSwitchCaseError in %s Stack trace: #0 %s: get_value(3) #1 {main} diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 90dbcbcc9c9bc..c38cc829a3f6d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5219,7 +5219,7 @@ void zend_compile_switch(znode *result, zend_ast *ast) /* {{{ */ // Generate default case for switch expression if (is_switch_expr) { zval exception_name; - ZVAL_STRING(&exception_name, "InvalidArgumentException"); + ZVAL_STRING(&exception_name, "UnhandledSwitchCaseError"); zend_ast *exception_name_ast = zend_ast_create_zval(&exception_name); zend_ast *exception_args_ast = zend_ast_create_list(0, ZEND_AST_ARG_LIST); diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index fb48d34bdf61b..37ef72c943361 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -40,6 +40,7 @@ ZEND_API zend_class_entry *zend_ce_argument_count_error; ZEND_API zend_class_entry *zend_ce_value_error; ZEND_API zend_class_entry *zend_ce_arithmetic_error; ZEND_API zend_class_entry *zend_ce_division_by_zero_error; +ZEND_API zend_class_entry *zend_ce_unhandled_switch_case_error; ZEND_API void (*zend_throw_exception_hook)(zval *ex); @@ -867,6 +868,10 @@ void zend_register_default_exception(void) /* {{{ */ INIT_CLASS_ENTRY(ce, "DivisionByZeroError", NULL); zend_ce_division_by_zero_error = zend_register_internal_class_ex(&ce, zend_ce_arithmetic_error); zend_ce_division_by_zero_error->create_object = zend_default_exception_new; + + INIT_CLASS_ENTRY(ce, "UnhandledSwitchCaseError", NULL); + zend_ce_unhandled_switch_case_error = zend_register_internal_class_ex(&ce, zend_ce_exception); + zend_ce_unhandled_switch_case_error->create_object = zend_default_exception_new; } /* }}} */ diff --git a/Zend/zend_exceptions.h b/Zend/zend_exceptions.h index ec6f0a0201111..af80b45f0a5e3 100644 --- a/Zend/zend_exceptions.h +++ b/Zend/zend_exceptions.h @@ -35,6 +35,7 @@ extern ZEND_API zend_class_entry *zend_ce_argument_count_error; extern ZEND_API zend_class_entry *zend_ce_value_error; extern ZEND_API zend_class_entry *zend_ce_arithmetic_error; extern ZEND_API zend_class_entry *zend_ce_division_by_zero_error; +extern ZEND_API zend_class_entry *zend_ce_unhandled_switch_case_error; ZEND_API void zend_exception_set_previous(zend_object *exception, zend_object *add_previous); ZEND_API void zend_exception_save(void); From ff7bd2d222c8847bc408abc4350ad48aeaacacf5 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Sun, 29 Mar 2020 01:51:50 +0100 Subject: [PATCH 4/5] Implement pretty printing for switch expression --- Zend/tests/switch/011.phpt | 40 +++++++++++++++++++++++++++++++++++++ Zend/zend_ast.c | 34 ++++++++++++++++++++++--------- Zend/zend_language_parser.y | 12 +++++++---- 3 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 Zend/tests/switch/011.phpt diff --git a/Zend/tests/switch/011.phpt b/Zend/tests/switch/011.phpt new file mode 100644 index 0000000000000..39eb0e137aa5e --- /dev/null +++ b/Zend/tests/switch/011.phpt @@ -0,0 +1,40 @@ +--TEST-- +Pretty printing for switch expression +--FILE-- + false }); + +assert('foo' switch { + 'foo', 'bar' => false, + 'baz' => false, +}); + +assert((function () { + switch ('foo') { + case 'foo', 'bar': + return false; + case 'baz': + return false; + } +})()); + +?> +--EXPECTF-- +Warning: assert(): assert('foo' switch { + default => false, +}) failed in %s on line %d + +Warning: assert(): assert('foo' switch { + 'foo', 'bar' => false, + 'baz' => false, +}) failed in %s on line %d + +Warning: assert(): assert(function () { + switch ('foo') { + case 'foo', 'bar': + return false; + case 'baz': + return false; + } +}()) failed in %s on line %d diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 2e7d4a3e94c3a..47e7073416069 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1851,23 +1851,39 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio smart_str_appendc(str, '}'); break; case ZEND_AST_SWITCH: - smart_str_appends(str, "switch ("); - zend_ast_export_ex(str, ast->child[0], 0, indent); - smart_str_appends(str, ") {\n"); + if (ast->attr & ZEND_SWITCH_EXPRESSION) { + zend_ast_export_ex(str, ast->child[0], 0, indent); + smart_str_appends(str, " switch {\n"); + } else { + smart_str_appends(str, "switch ("); + zend_ast_export_ex(str, ast->child[0], 0, indent); + smart_str_appends(str, ") {\n"); + } zend_ast_export_ex(str, ast->child[1], 0, indent + 1); zend_ast_export_indent(str, indent); smart_str_appendc(str, '}'); break; case ZEND_AST_SWITCH_CASE: zend_ast_export_indent(str, indent); - if (ast->child[0]) { - smart_str_appends(str, "case "); - zend_ast_export_list(str, (zend_ast_list*)ast->child[0], 1, 0, indent); - smart_str_appends(str, ":\n"); + if (ast->attr & ZEND_SWITCH_EXPRESSION) { + if (ast->child[0]) { + zend_ast_export_list(str, (zend_ast_list*)ast->child[0], 1, 0, indent); + smart_str_appends(str, " => "); + } else { + smart_str_appends(str, "default => "); + } + zend_ast_export_ex(str, ast->child[1], 0, 0); + smart_str_appends(str, ",\n"); } else { - smart_str_appends(str, "default:\n"); + if (ast->child[0]) { + smart_str_appends(str, "case "); + zend_ast_export_list(str, (zend_ast_list*)ast->child[0], 1, 0, indent); + smart_str_appends(str, ":\n"); + } else { + smart_str_appends(str, "default:\n"); + } + zend_ast_export_stmt(str, ast->child[1], indent + 1); } - zend_ast_export_stmt(str, ast->child[1], indent + 1); break; case ZEND_AST_DECLARE: smart_str_appends(str, "declare("); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index ff7ce018d527b..09a68856d4a78 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -616,10 +616,14 @@ non_empty_switch_expr_case_list: ; switch_expr_case: - case_cond_list T_DOUBLE_ARROW expr - { $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, $1, $3); } - | T_DEFAULT T_DOUBLE_ARROW expr - { $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $3); } + case_cond_list T_DOUBLE_ARROW expr { + $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, $1, $3); + $$->attr = ZEND_SWITCH_EXPRESSION; + } + | T_DEFAULT T_DOUBLE_ARROW expr { + $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $3); + $$->attr = ZEND_SWITCH_EXPRESSION; + } ; while_statement: From 2f327a26caf191edb69164b08423eb48c8ff451a Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 31 Mar 2020 23:56:51 +0200 Subject: [PATCH 5/5] Switch to `switch ($x)` syntax --- Zend/tests/switch/001.phpt | 2 +- Zend/tests/switch/002.phpt | 2 +- Zend/tests/switch/003.phpt | 2 +- Zend/tests/switch/004.phpt | 2 +- Zend/tests/switch/005.phpt | 2 +- Zend/tests/switch/006.phpt | 7 ++--- Zend/tests/switch/007.phpt | 2 +- Zend/tests/switch/008.phpt | 6 ++-- Zend/tests/switch/009.phpt | 2 +- Zend/tests/switch/011.phpt | 4 +-- Zend/zend_language_parser.y | 35 +++++++++++++----------- ext/tokenizer/tests/PhpToken_getAll.phpt | 12 ++++---- ext/tokenizer/tokenizer_data.c | 4 +-- 13 files changed, 41 insertions(+), 41 deletions(-) diff --git a/Zend/tests/switch/001.phpt b/Zend/tests/switch/001.phpt index 06be2d8610ed9..bb2fb1616d25b 100644 --- a/Zend/tests/switch/001.phpt +++ b/Zend/tests/switch/001.phpt @@ -4,7 +4,7 @@ Basic switch expression functionality test 'Zero', 1 => 'One', 2 => 'Two', diff --git a/Zend/tests/switch/002.phpt b/Zend/tests/switch/002.phpt index 62f80d7f4ff24..873a9d67491c4 100644 --- a/Zend/tests/switch/002.phpt +++ b/Zend/tests/switch/002.phpt @@ -4,7 +4,7 @@ Switch expression omit trailing comma "true\n", false => "false\n" }; diff --git a/Zend/tests/switch/003.phpt b/Zend/tests/switch/003.phpt index 42c39a9cd0ebe..a6712e13c4ef3 100644 --- a/Zend/tests/switch/003.phpt +++ b/Zend/tests/switch/003.phpt @@ -4,7 +4,7 @@ Switch expression default case 1, 2 => 2, default => 'default', diff --git a/Zend/tests/switch/004.phpt b/Zend/tests/switch/004.phpt index 411bbed7f0d63..b29615f06237d 100644 --- a/Zend/tests/switch/004.phpt +++ b/Zend/tests/switch/004.phpt @@ -4,7 +4,7 @@ Switch expression with true as expression = 50 => '50+', $i >= 40 => '40-50', $i >= 30 => '30-40', diff --git a/Zend/tests/switch/005.phpt b/Zend/tests/switch/005.phpt index 1f45794bbf050..0503b6e46daa9 100644 --- a/Zend/tests/switch/005.phpt +++ b/Zend/tests/switch/005.phpt @@ -3,7 +3,7 @@ Switch expression discarding result --FILE-- print "Executed\n", }; diff --git a/Zend/tests/switch/006.phpt b/Zend/tests/switch/006.phpt index 3f429119c7907..427e6bd4d9375 100644 --- a/Zend/tests/switch/006.phpt +++ b/Zend/tests/switch/006.phpt @@ -3,11 +3,8 @@ Switch expression with no cases --FILE-- --EXPECTF-- -Fatal error: Uncaught UnhandledSwitchCaseError in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +Parse error: syntax error, unexpected '}' in %s on line %d diff --git a/Zend/tests/switch/007.phpt b/Zend/tests/switch/007.phpt index 374f7d4a5d7b2..761e4132defa8 100644 --- a/Zend/tests/switch/007.phpt +++ b/Zend/tests/switch/007.phpt @@ -4,7 +4,7 @@ Switch expression exception on unhandled case 1, 2 => 2, }; diff --git a/Zend/tests/switch/008.phpt b/Zend/tests/switch/008.phpt index 85733ed5d3837..7cfc8642630ad 100644 --- a/Zend/tests/switch/008.phpt +++ b/Zend/tests/switch/008.phpt @@ -3,16 +3,16 @@ Switch expression precedence --FILE-- "! has higher precedence\n" }; $throwableInterface = Throwable::class; -print new RuntimeException() instanceof $throwableInterface switch { +print switch (new RuntimeException() instanceof $throwableInterface) { true => "instanceof has higher precedence\n" }; -print 10 ** 2 switch { +print switch (10 ** 2) { 100 => "** has higher precedence\n" }; diff --git a/Zend/tests/switch/009.phpt b/Zend/tests/switch/009.phpt index 0d7beeb51c9a0..feeb2e2e0fdc8 100644 --- a/Zend/tests/switch/009.phpt +++ b/Zend/tests/switch/009.phpt @@ -4,7 +4,7 @@ Switch expression multiple conditions per case false, 2, 3, 4, 5, 6 => true, }; diff --git a/Zend/tests/switch/011.phpt b/Zend/tests/switch/011.phpt index 39eb0e137aa5e..d802fa9f0a265 100644 --- a/Zend/tests/switch/011.phpt +++ b/Zend/tests/switch/011.phpt @@ -3,9 +3,9 @@ Pretty printing for switch expression --FILE-- false }); +assert(switch ('foo') { default => false }); -assert('foo' switch { +assert(switch ('foo') { 'foo', 'bar' => false, 'baz' => false, }); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 09a68856d4a78..6d06658676390 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -74,7 +74,6 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %left T_SL T_SR %left '+' '-' %left '*' '/' '%' -%precedence T_SWITCH %precedence '!' %precedence T_INSTANCEOF %precedence '~' T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@' @@ -249,7 +248,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type top_statement_list use_declarations const_list inner_statement_list if_stmt %type alt_if_stmt for_exprs switch_case_list global_var_list static_var_list %type echo_expr_list unset_variables catch_name_list catch_list parameter_list class_statement_list -%type implements_list case_list if_stmt_without_else +%type implements_list case_list case if_stmt_without_else %type non_empty_parameter_list argument_list non_empty_argument_list property_list %type class_const_list class_const_decl class_name_list trait_adaptations method_body non_empty_for_exprs %type ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars @@ -258,7 +257,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type isset_variable type return_type type_expr type_without_static %type identifier type_expr_without_static union_type_without_static %type inline_function union_type -%type case_cond_list switch_expr_case_list non_empty_switch_expr_case_list switch_expr_case +%type case_cond_list switch_expr_case_list switch_expr_case %type returns_ref function fn is_reference is_variadic variable_modifiers %type method_modifiers non_empty_member_modifiers member_modifier @@ -578,17 +577,26 @@ declare_statement: switch_case_list: '{' case_list '}' { $$ = $2; } + | '{' '}' { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); } | '{' ';' case_list '}' { $$ = $3; } + | '{' ';' '}' { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); } | ':' case_list T_ENDSWITCH ';' { $$ = $2; } + | ':' T_ENDSWITCH ';' { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); } | ':' ';' case_list T_ENDSWITCH ';' { $$ = $3; } + | ':' ';' T_ENDSWITCH ';' { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); } ; case_list: - %empty { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); } - | case_list T_CASE case_cond_list case_separator inner_statement_list - { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, $3, $5)); } - | case_list T_DEFAULT case_separator inner_statement_list - { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $4)); } + case { $$ = zend_ast_create_list(1, ZEND_AST_SWITCH_LIST, $1); } + | case_list case + { $$ = zend_ast_list_add($1, $2); } +; + +case: + T_CASE case_cond_list case_separator inner_statement_list + { $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, $2, $4); } + | T_DEFAULT case_separator inner_statement_list + { $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $3); } ; case_cond_list: @@ -604,14 +612,9 @@ case_separator: ; switch_expr_case_list: - %empty { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); } - | non_empty_switch_expr_case_list possible_comma { $$ = $1; } -; - -non_empty_switch_expr_case_list: switch_expr_case { $$ = zend_ast_create_list(1, ZEND_AST_SWITCH_LIST, $1); } - | non_empty_switch_expr_case_list ',' switch_expr_case + | switch_expr_case_list ',' switch_expr_case { $$ = zend_ast_list_add($1, $3); } ; @@ -1052,8 +1055,8 @@ expr: | T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } | inline_function { $$ = $1; } | T_STATIC inline_function { $$ = $2; ((zend_ast_decl *) $$)->flags |= ZEND_ACC_STATIC; } - | expr T_SWITCH '{' switch_expr_case_list '}' { - $$ = zend_ast_create(ZEND_AST_SWITCH, $1, $4); + | T_SWITCH '(' expr ')' '{' switch_expr_case_list possible_comma '}' { + $$ = zend_ast_create(ZEND_AST_SWITCH, $3, $6); $$->attr = ZEND_SWITCH_EXPRESSION; } ; diff --git a/ext/tokenizer/tests/PhpToken_getAll.phpt b/ext/tokenizer/tests/PhpToken_getAll.phpt index add3f195aa7ee..604a979023ed7 100644 --- a/ext/tokenizer/tests/PhpToken_getAll.phpt +++ b/ext/tokenizer/tests/PhpToken_getAll.phpt @@ -54,7 +54,7 @@ array(15) { [3]=> object(PhpToken)#4 (4) { ["id"]=> - int(311) + int(310) ["text"]=> string(3) "foo" ["line"]=> @@ -121,7 +121,7 @@ array(15) { [9]=> object(PhpToken)#10 (4) { ["id"]=> - int(325) + int(324) ["text"]=> string(4) "echo" ["line"]=> @@ -143,7 +143,7 @@ array(15) { [11]=> object(PhpToken)#12 (4) { ["id"]=> - int(315) + int(314) ["text"]=> string(5) ""bar"" ["line"]=> @@ -224,7 +224,7 @@ array(15) { [3]=> object(PhpToken)#12 (4) { ["id"]=> - int(311) + int(310) ["text"]=> string(3) "foo" ["line"]=> @@ -291,7 +291,7 @@ array(15) { [9]=> object(PhpToken)#6 (4) { ["id"]=> - int(325) + int(324) ["text"]=> string(4) "echo" ["line"]=> @@ -313,7 +313,7 @@ array(15) { [11]=> object(PhpToken)#4 (4) { ["id"]=> - int(315) + int(314) ["text"]=> string(5) ""bar"" ["line"]=> diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index fcf1975e345d9..3ddf89521a8cd 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -61,7 +61,6 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_IS_GREATER_OR_EQUAL", T_IS_GREATER_OR_EQUAL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_SL", T_SL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_SR", T_SR, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("T_SWITCH", T_SWITCH, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INSTANCEOF", T_INSTANCEOF, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INT_CAST", T_INT_CAST, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DOUBLE_CAST", T_DOUBLE_CAST, CONST_CS | CONST_PERSISTENT); @@ -101,6 +100,7 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_DECLARE", T_DECLARE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ENDDECLARE", T_ENDDECLARE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_AS", T_AS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_SWITCH", T_SWITCH, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ENDSWITCH", T_ENDSWITCH, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_CASE", T_CASE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DEFAULT", T_DEFAULT, CONST_CS | CONST_PERSISTENT); @@ -204,7 +204,6 @@ char *get_token_type_name(int token_type) case T_IS_GREATER_OR_EQUAL: return "T_IS_GREATER_OR_EQUAL"; case T_SL: return "T_SL"; case T_SR: return "T_SR"; - case T_SWITCH: return "T_SWITCH"; case T_INSTANCEOF: return "T_INSTANCEOF"; case T_INT_CAST: return "T_INT_CAST"; case T_DOUBLE_CAST: return "T_DOUBLE_CAST"; @@ -244,6 +243,7 @@ char *get_token_type_name(int token_type) case T_DECLARE: return "T_DECLARE"; case T_ENDDECLARE: return "T_ENDDECLARE"; case T_AS: return "T_AS"; + case T_SWITCH: return "T_SWITCH"; case T_ENDSWITCH: return "T_ENDSWITCH"; case T_CASE: return "T_CASE"; case T_DEFAULT: return "T_DEFAULT";