diff --git a/src/Php/AstNodeType.php b/src/Php/AstNodeType.php index f5f4f57e..241db5db 100644 --- a/src/Php/AstNodeType.php +++ b/src/Php/AstNodeType.php @@ -63,4 +63,9 @@ trait AstNodeType { return $expr instanceof Node\Scalar; } + + protected function isMatchExpr(NodeAbstract $expr): bool + { + return $expr instanceof Expr\Match_; + } } diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index e56d632c..8e796772 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -2042,7 +2042,40 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseMatch(Node\Expr\Match_ $expr): string { - abort($expr); + $var = $this->parseIdentifier($expr->cond); + if ($this->isVarExpr($expr->cond) and !$this->hasVar($var)) { + $this->errorUndefinedVariable($expr->cond); + } + $code = '[&]() -> ' . self::TYPE_VAR . '{'; + $default = null; + foreach ($expr->arms as $i => $arm) { + if ($arm->conds === null) { + $default = $arm->body; + continue; + } + $prefix = $i === 0 ? 'if' : 'else if'; + $condList = []; + foreach ($arm->conds as $cond) { + if ($this->isMatchExpr($cond)) { + $this->fatalError($arm, 'Match expression cannot be used as a condition'); + } + $condList[] = 'php::same(' . $var . ', ' . $this->parseExpr($cond) . ')'; + } + $code .= $prefix . '(' . implode(' || ', $condList) . ') {'; + $code .= 'return ' . $this->parseExpr($arm->body) . ';'; + $code .= '}'; + } + + $else = count($expr->arms) === 0 ? '' : 'else '; + if ($default) { + $code .= $else . ' { return ' . $this->parseExpr($default) . '; }'; + } else { + $code .= $else . ' { php::throwException("UnhandledMatchError", "Unhandled match case");' . PHP_EOL . + 'return ' . self::VALUE_NULL . '; }'; + } + $code .= '}()'; + + return $code; } protected function parseBinaryOpGreater(mixed $expr): string diff --git a/src/Php/Constants.php b/src/Php/Constants.php index ff7006cd..0f8c4786 100644 --- a/src/Php/Constants.php +++ b/src/Php/Constants.php @@ -31,6 +31,7 @@ class Constants 'function', 'global', 'if', + 'bool', 'int', 'double', 'float', diff --git a/tests/zend/match/001.phpt b/tests/zend/match/001.phpt new file mode 100644 index 00000000..13c0b8ef --- /dev/null +++ b/tests/zend/match/001.phpt @@ -0,0 +1,37 @@ +--TEST-- +Basic match expression functionality test +--FILE-- + 'Zero', + 1 => 'One', + 2 => 'Two', + 3 => 'Three', + 4 => 'Four', + 5 => 'Five', + 6 => 'Six', + 7 => 'Seven', + 8 => 'Eight', + 9 => 'Nine', + }; +} + +function main() { + for ($i = 0; $i <= 9; $i++) { + print wordify($i) . "\n"; + } +} +?> +--EXPECT-- +Zero +One +Two +Three +Four +Five +Six +Seven +Eight +Nine diff --git a/tests/zend/match/002.phpt b/tests/zend/match/002.phpt new file mode 100644 index 00000000..0827b0de --- /dev/null +++ b/tests/zend/match/002.phpt @@ -0,0 +1,20 @@ +--TEST-- +Match expression omit trailing comma +--FILE-- + "true\n", + false => "false\n" + }; +} + +function main() { + print_bool(true); + print_bool(false); +} +?> +--EXPECT-- +true +false diff --git a/tests/zend/match/003.phpt b/tests/zend/match/003.phpt new file mode 100644 index 00000000..b02aac98 --- /dev/null +++ b/tests/zend/match/003.phpt @@ -0,0 +1,24 @@ +--TEST-- +Match expression default case +--FILE-- + 1, + 2 => 2, + default => 'default', + }; +} +function main() { + 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/tests/zend/match/004.phpt b/tests/zend/match/004.phpt new file mode 100644 index 00000000..4ab37645 --- /dev/null +++ b/tests/zend/match/004.phpt @@ -0,0 +1,32 @@ +--TEST-- +Match 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', + }; +} + +function main() { + 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/tests/zend/match/008.phpt b/tests/zend/match/008.phpt new file mode 100644 index 00000000..adf55b39 --- /dev/null +++ b/tests/zend/match/008.phpt @@ -0,0 +1,26 @@ +--TEST-- +Match expression multiple conditions per case +--FILE-- + false, + 2, 3, 4, 5, 6 => true, + }; +} + +function main() { + 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/tests/zend/match/011.phpt b/tests/zend/match/011.phpt new file mode 100644 index 00000000..6730ee01 --- /dev/null +++ b/tests/zend/match/011.phpt @@ -0,0 +1,20 @@ +--TEST-- +Implicit break in match expression +--FILE-- + dump_and_return('foo'), + 'bar' => dump_and_return('bar'), + }); +} +?> +--EXPECT-- +string(3) "foo" +string(3) "foo" diff --git a/tests/zend/match/039.phpt b/tests/zend/match/039.phpt new file mode 100644 index 00000000..d37aed59 --- /dev/null +++ b/tests/zend/match/039.phpt @@ -0,0 +1,61 @@ +--TEST-- +Test match with duplicate conditions +--FILE-- + 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + 1 => 6, +}; +echo "\n"; + +echo match ($value) { + 2, 1 => '2, 1', + 1 => 1, + 3 => 3, + 4 => 4, + 5 => 5, +}; +echo "\n"; + +echo match ($value) { + 1, 1 => '1, 1', + 2, 2 => '2, 2', + 3, 3 => '3, 3', + 4, 4 => '4, 4', + 5, 5 => '5, 5', +}; +echo "\n"; + +echo match ($value) { + 1 => 1, + 1 => 2, +}; +echo "\n"; + +echo match ($value) { + 2, 1 => '2, 1', + 1 => 1, +}; +echo "\n"; + +echo match ($value) { + 1, 1 => '1, 1', + 1 => 1, +}; +echo "\n"; + +?> +--EXPECT-- +1 +2, 1 +1, 1 +1 +2, 1 +1, 1 diff --git a/tests/zend/match/041.phpt b/tests/zend/match/041.phpt new file mode 100644 index 00000000..5726a357 --- /dev/null +++ b/tests/zend/match/041.phpt @@ -0,0 +1,34 @@ +--TEST-- +Match expression with trailing comma in condition list +--FILE-- + "false\n", + true, + 1, + => "true\n", + default, + => "not bool\n", + }; +} + +function main() { + print_bool(false); + print_bool(0); + print_bool(true); + print_bool(1); + print_bool(2); + print_bool('foo'); +} +?> +--EXPECT-- +false +false +true +true +not bool +not bool