feat(php): 添加对 match 表达式的支持

- 实现了 match 表达式的解析和编译功能
- 添加了多个测试用例验证 match 表达式的基本功能
- 包括基本匹配、默认情况、多条件匹配等功能测试
- 实现了 match 表达式的错误处理机制
- 添加了对重复条件和尾随逗号的支持测试
- 更新了 AST 节点类型检查以支持 Match_ 表达式
- 在常量列表中添加 bool 类型支持
pull/1/head
韩天峰 7 months ago
parent 46e15ca247
commit a72752828f
  1. 5
      src/Php/AstNodeType.php
  2. 35
      src/Php/CompilerBase.php
  3. 1
      src/Php/Constants.php
  4. 37
      tests/zend/match/001.phpt
  5. 20
      tests/zend/match/002.phpt
  6. 24
      tests/zend/match/003.phpt
  7. 32
      tests/zend/match/004.phpt
  8. 26
      tests/zend/match/008.phpt
  9. 20
      tests/zend/match/011.phpt
  10. 61
      tests/zend/match/039.phpt
  11. 34
      tests/zend/match/041.phpt

@ -63,4 +63,9 @@ trait AstNodeType
{
return $expr instanceof Node\Scalar;
}
protected function isMatchExpr(NodeAbstract $expr): bool
{
return $expr instanceof Expr\Match_;
}
}

@ -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

@ -31,6 +31,7 @@ class Constants
'function',
'global',
'if',
'bool',
'int',
'double',
'float',

@ -0,0 +1,37 @@
--TEST--
Basic match expression functionality test
--FILE--
<?php
function wordify($x) {
return match ($x) {
0 => '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

@ -0,0 +1,20 @@
--TEST--
Match expression omit trailing comma
--FILE--
<?php
function print_bool($bool) {
echo match ($bool) {
true => "true\n",
false => "false\n"
};
}
function main() {
print_bool(true);
print_bool(false);
}
?>
--EXPECT--
true
false

@ -0,0 +1,24 @@
--TEST--
Match expression default case
--FILE--
<?php
function get_value($i) {
return match ($i) {
1 => 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

@ -0,0 +1,32 @@
--TEST--
Match expression with true as expression
--FILE--
<?php
function get_range($i) {
return match (true) {
$i >= 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

@ -0,0 +1,26 @@
--TEST--
Match expression multiple conditions per case
--FILE--
<?php
function is_working_day($day) {
return match ($day) {
1, 7 => 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)

@ -0,0 +1,20 @@
--TEST--
Implicit break in match expression
--FILE--
<?php
function dump_and_return($string) {
var_dump($string);
return $string;
}
function main() {
var_dump(match ('foo') {
'foo' => dump_and_return('foo'),
'bar' => dump_and_return('bar'),
});
}
?>
--EXPECT--
string(3) "foo"
string(3) "foo"

@ -0,0 +1,61 @@
--TEST--
Test match with duplicate conditions
--FILE--
<?php
$value = 1;
echo match ($value) {
1 => 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

@ -0,0 +1,34 @@
--TEST--
Match expression with trailing comma in condition list
--FILE--
<?php
function print_bool($bool) {
echo match ($bool) {
false,
0,
=> "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
Loading…
Cancel
Save