diff --git a/.gitignore b/.gitignore index b5f25c9c..00a267f1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,6 @@ /build /vendor /tmp -/examples/wordpress +/projects/wordpress /.php-cs-fixer.cache *.o \ No newline at end of file diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 843b9277..b2f56923 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -59,6 +59,8 @@ class CompilerBase extends \PhpAot\Core\Translator public const string NAMESPACE_SEPARATOR = '__'; public const string PREFIX = 'php_'; + public const string OP_ISSET = 'isset'; + public const string OP_EMPTY = 'empty'; protected string $phpxDir = '~/workspace/projects/phpx'; protected string $lang = 'PHP'; protected string $cppCompiler = 'g++'; @@ -311,6 +313,8 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->parseAssignOpBitwiseXor($expr); case 'Expr_AssignOp_Pow': return $this->parseAssignOpPow($expr); + case 'Expr_AssignOp_Coalesce': + return $this->parseAssignOpCoalesce($expr); case 'Expr_BinaryOp_Mul': return $this->parseBinaryOpMul($expr); case 'Expr_BinaryOp_Concat': @@ -1713,6 +1717,11 @@ class CompilerBase extends \PhpAot\Core\Translator $this->error("{$msg} in {$this->file}:{$node->getStartLine()}"); } + protected function errorUndefinedVariable(Variable $node): never + { + $this->fatalError($node, "The variable `{$node->name}` is undefined"); + } + protected function dump(NodeAbstract $v): void { if ($this->debugLine == $v->getStartLine()) { @@ -1734,7 +1743,7 @@ class CompilerBase extends \PhpAot\Core\Translator if ($write) { $this->addLocalVar($var, self::TYPE_ARRAY); } else { - $this->fatalError($node->var, "The variable `{$node->var->name}` is undefined"); + $this->errorUndefinedVariable($node->var); } } else { $type = $this->getVarType($var); @@ -1835,9 +1844,9 @@ class CompilerBase extends \PhpAot\Core\Translator return false; } - protected function foundStrayCode(): never + protected function foundStrayCode(Node $node): never { - $this->error("All execution code must be within a function; there is no allowance for stray code."); + $this->fatalError($node, "All execution code must be within a function, found stray code"); } protected function parseFuncCall(Node\Expr\FuncCall $expr, bool $silent = false): string @@ -2825,11 +2834,24 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->parseVarCheckExpr($expr->expr, 'empty'); } + /** + * 左值只能为变量、数组、对象属性、对象静态属性 + * @param NodeAbstract $expr + * @return void + */ + protected function checkLeftValue(NodeAbstract $expr): void + { + if (!$this->isVarExpr($expr) && !$this->isArrayDimFetch($expr) && !$this->isPropertyFetch($expr) && !$this->isStaticPropertyFetch($expr)) { + $this->fatalError($expr, 'The left value of assignment operation can only be variable, array item, object property, class static property'); + } + } + protected function parseVarCheckExpr(NodeAbstract $expr, string $op): string { if ($this->isVarExpr($expr)) { if ($op === 'isset') { - return $this->hasVar($this->parseIdentifier($expr)) ? 'true' : 'false'; + $var = $this->parseIdentifier($expr); + return $this->hasVar($var) ? 'php::exists(' . $var . ')' : 'false'; } return 'php::' . $op . '(' . $this->parseExpr($expr) . ')'; } @@ -3466,4 +3488,26 @@ class CompilerBase extends \PhpAot\Core\Translator return 'php::newClosure(' . $tmpVar . ', { ' . implode(', ', $useVars) . ' })'; } + + protected function parseAssignOpCoalesce(Node\Expr\AssignOp\Coalesce $expr): string + { + $this->checkLeftValue($expr->var); + $isset = $this->parseVarCheckExpr($expr->var, self::OP_ISSET); + + $inAssignExpr = $this->inAssignExpr; + $this->inAssignExpr = true; + $var = $this->parseIdentifier($expr->var); + $this->inAssignExpr = $inAssignExpr; + + $right = $this->parseExpr($expr->expr); + if ($this->isVarExpr($expr->expr) and !$this->hasVar($right)) { + $this->errorUndefinedVariable($expr->expr); + } + if ($this->isVarExpr($expr->var) and !$this->hasVar($var)) { + $this->addLocalVar($var, $this->detectExprType($expr->expr)); + } + return 'if (!' . $isset . ') {' . PHP_EOL . + $this->getIndent() . $var . ' = ' . $right . ';' . PHP_EOL . + '}' . PHP_EOL; + } } diff --git a/src/Php/Preprocessor.php b/src/Php/Preprocessor.php index cce1fb40..7b777f60 100644 --- a/src/Php/Preprocessor.php +++ b/src/Php/Preprocessor.php @@ -106,7 +106,7 @@ class Preprocessor extends CompilerBase $this->parseConstDef($v); break; case 'Stmt_Expression': - $this->foundStrayCode(); + $this->foundStrayCode($v); default: $this->fatalError($v, 'Unsupported statement: ' . $type); } @@ -144,7 +144,7 @@ class Preprocessor extends CompilerBase case 'Stmt_Const': break; case 'Stmt_Expression': - $this->foundStrayCode(); + $this->foundStrayCode($v2); default: abort($v2); } @@ -178,7 +178,7 @@ class Preprocessor extends CompilerBase $code .= $this->prepareFunction($v) . PHP_EOL; break; case 'Stmt_Expression': - $this->foundStrayCode(); + $this->foundStrayCode($v); default: abort($v); } diff --git a/tests/aot/assign_coalesce_001.phpt b/tests/aot/assign_coalesce_001.phpt new file mode 100644 index 00000000..4dcdd2b8 --- /dev/null +++ b/tests/aot/assign_coalesce_001.phpt @@ -0,0 +1,15 @@ +--TEST-- +assign coalesce +--FILE-- + +--EXPECT-- +int(123) +string(3) "foo" diff --git a/tests/zend/coalesce/assign_coalesce_001.phpt b/tests/zend/coalesce/assign_coalesce_001.phpt new file mode 100644 index 00000000..fce688f0 --- /dev/null +++ b/tests/zend/coalesce/assign_coalesce_001.phpt @@ -0,0 +1,119 @@ +--TEST-- +Coalesce assign (??=): Basic behavior +--FILE-- +foo ??= 123; + $obj->$foo ??= $bar; + $obj->$bar ??= $foo; + var_dump($obj); + + $obj = new stdClass; + $obj->{id($foo)} ??= 123; + $obj->{id($foo)} ??= $bar; + $obj->{id($bar)} ??= $foo; + var_dump($obj); + + echo "\nStatic props:\n"; + Test::$foo ??= 123; + Test::$$foo ??= $bar; + Test::$$bar ??= $foo; + var_dump(Test::$foo, Test::$bar); + + Test::$foo = null; + Test::$bar = null; + Test::${id($foo)} ??= 123; + Test::${id($foo)} ??= $bar; + Test::${id($bar)} ??= $foo; + var_dump(Test::$foo, Test::$bar); +} +?> +--EXPECT-- +Simple variables: +int(123) +string(3) "foo" +string(3) "foo" +string(3) "foo" + +Arrays: +array(2) { + ["foo"]=> + int(123) + ["bar"]=> + string(3) "foo" +} + +Arrays (identity): +array(2) { + ["foo"]=> + int(123) + ["bar"]=> + string(3) "foo" +} + +Objects: +object(stdClass)#1 (2) { + ["foo"]=> + int(123) + ["bar"]=> + string(3) "foo" +} +object(stdClass)#2 (2) { + ["foo"]=> + int(123) + ["bar"]=> + string(3) "foo" +} + +Static props: +int(123) +string(3) "foo" +int(123) +string(3) "foo" diff --git a/tests/zend/use_function/basic.phpt b/tests/zend/use_function/basic.phpt new file mode 100644 index 00000000..57370716 --- /dev/null +++ b/tests/zend/use_function/basic.phpt @@ -0,0 +1,28 @@ +--TEST-- +import namespaced function +--FILE-- + +--EXPECT-- +string(11) "foo.bar.baz" +string(11) "foo.bar.baz" +Done