try/catch 语法支持

pull/1/head
韩天峰 8 months ago
parent ec836bcb31
commit 4162b79113
  1. 8
      bin/compiler.php
  2. 2
      examples/nbody.php-3.php
  3. 15
      examples/throw-in-php.php
  4. 14
      main.cc
  5. 30
      src/Php/FileScanner.php
  6. 69
      src/Php/Translator.php
  7. 6
      src/functions.php
  8. 23
      tests/aot/throw-in-php.phpt
  9. 28
      tests/aot/try-catch.phpt

@ -20,7 +20,7 @@ if (is_dir($path)) {
$targetFile = basename($path); $targetFile = basename($path);
} else { } else {
$list = [$path]; $list = [$path];
$targetFile = basename($path, '.php'); $targetFile = FileScanner::getFileName($path);
} }
$sourceFiles = []; $sourceFiles = [];
@ -29,13 +29,15 @@ $objectFiles = [];
// 分析 PHP 文件,生成 C++ 文件 // 分析 PHP 文件,生成 C++ 文件
foreach ($list as $file) { foreach ($list as $file) {
try { try {
if (str_ends_with($file, '.php')) { if (FileScanner::isPhpFile($file)) {
$code = $translator->convert($file); $code = $translator->convert($file);
$info = pathinfo($file); $info = pathinfo($file);
$cppFile = $info['dirname'] . '/' . $info['filename'] . '.cc'; $cppFile = $info['dirname'] . '/' . $info['filename'] . '.cc';
$translator->save($code, $cppFile); $translator->save($code, $cppFile);
} else { } elseif (FileScanner::isCppFile($file)) {
$cppFile = $file; $cppFile = $file;
} else {
continue;
} }
$sourceFiles[] = $cppFile; $sourceFiles[] = $cppFile;
} catch (Unsupported $e) { } catch (Unsupported $e) {

@ -61,7 +61,7 @@ function main()
-9.51592254519715870E-05 * $days_per_year, -9.51592254519715870E-05 * $days_per_year,
5.15138902046611451E-05 * $solar_mass)); 5.15138902046611451E-05 * $solar_mass));
// offset_momentum // offset_momentum
$px = $py = $pz = 0.0; $px = $py = $pz = 0.0;
foreach ($bodies as $e) { foreach ($bodies as $e) {
$px += $e[3] * $e[6]; $px += $e[3] * $e[6];

@ -0,0 +1,15 @@
<?php
function inverse($x) {
return number_format(1.0 / $x, 2);
}
function main() {
try {
echo inverse(5.0) . "\n";
echo inverse(0) . "\n";
} catch (DivisionByZeroError $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
} finally {
echo "Finally\n";
}
}

@ -10,8 +10,22 @@ extern php::Var argc;
extern php::Var argv; extern php::Var argv;
extern void php_unset_all_global_vars(); extern void php_unset_all_global_vars();
static void throw_exception(zend_object *ex) {
zend_bailout();
}
int main(int cpp_argc, char **cpp_argv) { int main(int cpp_argc, char **cpp_argv) {
php_embed_init(cpp_argc, cpp_argv); php_embed_init(cpp_argc, cpp_argv);
zend_execute_data fake_execute_data;
memset(&fake_execute_data, 0, sizeof(zend_execute_data));
zend_function fake_func {};
fake_func.type = ZEND_INTERNAL_FUNCTION;
fake_execute_data.func = &fake_func;
EG(current_execute_data) = &fake_execute_data;
zend_throw_exception_hook = throw_exception;
int rc = 0; int rc = 0;
#if PPROF_ON #if PPROF_ON
ProfilerStart("myapp.prof"); ProfilerStart("myapp.prof");

@ -7,20 +7,41 @@ use FilesystemIterator;
class FileScanner class FileScanner
{ {
private string $directory; private string $directory;
private array $extensions;
private array $excludePatterns; private array $excludePatterns;
public function __construct(string $directory, array $extensions = ['.php', '.cc', '.cpp', '.cxx', '.c', '.h', '.hpp']) const array PHP_EXT = ['php'];
const array CPP_EXT = ['cpp', 'cxx', 'cc'];
public function __construct(string $directory)
{ {
if (!is_dir($directory)) { if (!is_dir($directory)) {
throw new \InvalidArgumentException("Directory does not exist: $directory"); throw new \InvalidArgumentException("Directory does not exist: $directory");
} }
$this->directory = rtrim($directory, DIRECTORY_SEPARATOR); $this->directory = rtrim($directory, DIRECTORY_SEPARATOR);
$this->extensions = $extensions;
$this->excludePatterns = []; $this->excludePatterns = [];
} }
public static function getFileName(string $path): string
{
return pathinfo($path, PATHINFO_FILENAME);
}
public static function getFileExt(string $path): string
{
return pathinfo($path, PATHINFO_EXTENSION);
}
static function isPhpFile(string $file): bool
{
return in_array(self::getFileExt($file), self::PHP_EXT);
}
static function isCppFile(string $file): bool
{
return in_array(self::getFileExt($file), self::CPP_EXT);
}
public function addExcludePattern(string $pattern): self public function addExcludePattern(string $pattern): self
{ {
$this->excludePatterns[] = $pattern; $this->excludePatterns[] = $pattern;
@ -36,8 +57,7 @@ class FileScanner
foreach ($iterator as $file) { foreach ($iterator as $file) {
if ($file->isFile()) { if ($file->isFile()) {
$extension = '.' . $file->getExtension(); if (self::isPhpFile($file)) {
if (in_array($extension, $this->extensions)) {
$filePath = $file->getPathname(); $filePath = $file->getPathname();
$excluded = false; $excluded = false;
foreach ($this->excludePatterns as $pattern) { foreach ($this->excludePatterns as $pattern) {

@ -537,6 +537,9 @@ class Translator extends \PhpAot\Core\Translator
case 'Stmt_Unset': case 'Stmt_Unset':
$result = $this->parseUnset($v); $result = $this->parseUnset($v);
break; break;
case 'Stmt_TryCatch':
$result = $this->parseTryCatch($v);
break;
default: default:
abort($v); abort($v);
} }
@ -671,6 +674,8 @@ class Translator extends \PhpAot\Core\Translator
return $this->parseNew($expr); return $this->parseNew($expr);
case 'Expr_Clone': case 'Expr_Clone':
return $this->parseClone($expr); return $this->parseClone($expr);
case 'Expr_Throw':
return $this->parseThrow($expr);
case 'Name_FullyQualified': case 'Name_FullyQualified':
return $expr->name; return $expr->name;
case 'Scalar_Int': case 'Scalar_Int':
@ -2245,4 +2250,68 @@ class Translator extends \PhpAot\Core\Translator
{ {
return $expr->getStartLine(); return $expr->getStartLine();
} }
private function parseThrow(mixed $expr): string
{
return 'php::throwException(' . $this->parseIdentifier($expr->expr). ')';
}
private function parseTryCatch(mixed $v): string
{
$code = 'zend_try {';
$stmts = $v->stmts;
$code .= PHP_EOL;
$this->indentLevel++;
$code .= $this->parseStmts($stmts);
$this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL;
$catches = $v->catches;
$finally = $v->finally;
$exVar = $this->genTmpVarName();
$this->addLocalVar($exVar, self::TYPE_OBJECT);
$code .= 'zend_catch {' . PHP_EOL;
if ($catches) {
$code .= $this->getIndent() . $exVar . ' = php::catchException();' . PHP_EOL;
$this->indentLevel++;
foreach ($catches as $catch) {
$code .= $this->parseCatch($catch, $exVar);
}
$this->indentLevel--;
}
$code .= '}' . PHP_EOL . 'zend_end_try();' . PHP_EOL;
if ($finally) {
$code .= $this->parseStmts($finally->stmts);
$code .= PHP_EOL;
$code .= 'if (' . $exVar . ') {' . PHP_EOL . $this->getIndent() . 'php::throwException(' . $exVar . ');' . PHP_EOL . $this->getIndent() . '}';
}
return $code;
}
private function parseCatch(mixed $catch, string $exVar): string
{
$types = $catch->types;
$var = $this->parseIdentifier($catch->var);
if (!$this->hasVar($var)) {
$this->addLocalVar($var, self::TYPE_OBJECT);
}
$code = $this->getIndent() . $var . ' = ' . $exVar . ';' . PHP_EOL;
$code .= $this->getIndent() . 'if (';
foreach ($types as $type) {
$code .= 'php::instanceOf(' . $var . ', "' . $this->parseIdentifier($type) . '")';
}
$code .= ') {' . PHP_EOL;
$this->indentLevel++;
$code .= $this->parseStmts($catch->stmts);
$code .= $this->getIndent() . "$exVar.unset();" . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '}';
return $code;
}
} }

@ -13,9 +13,9 @@ function abort($v)
$msg = 'Error: Unsupported ' . $lang . ' Syntax,'; $msg = 'Error: Unsupported ' . $lang . ' Syntax,';
$msg .= ' Line: ' . $translator->getLine($v) . ', Type: ' . $translator->getType($v) . PHP_EOL; $msg .= ' Line: ' . $translator->getLine($v) . ', Type: ' . $translator->getType($v) . PHP_EOL;
if ($translator->mode == 'cli') { if ($translator->mode == 'cli') {
// if (DEBUG) { if (DEBUG) {
// var_dump($v); var_dump($v);
// } }
} else { } else {
header('Content-Type: application/json'); header('Content-Type: application/json');
echo json_encode($v, JSON_PRETTY_PRINT); echo json_encode($v, JSON_PRETTY_PRINT);

@ -0,0 +1,23 @@
--TEST--
try catch
--FILE--
<?php
function inverse($x) {
return number_format(1.0 / $x, 2);
}
function main() {
try {
echo inverse(5.0) . "\n";
echo inverse(0) . "\n";
} catch (DivisionByZeroError $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
} finally {
echo "Finally\n";
}
}
?>
--EXPECT--
0.20
Caught exception: Division by zero
Finally

@ -0,0 +1,28 @@
--TEST--
try catch
--FILE--
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Division by zero.');
}
return number_format(1.0 / $x, 2);
}
function main() {
try {
echo inverse(5.0) . "\n";
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
} catch (RuntimeException $e) {
echo 'Caught runtime exception: ', $e->getMessage(), "\n";
} finally {
echo "Finally\n";
}
}
?>
--EXPECT--
0.20
Caught exception: Division by zero.
Finally
Loading…
Cancel
Save