feat(compiler): 实现常量值解析和类型安全的值生成

- 在 ConstantDef 中添加 valueExpr 属性以支持表达式解析
- 将 gen_stub.php 移动到 src 目录并重构为可重用函数
- 实现 getClassConstValue 和 getConstValue 方法处理类常量和全局常量
- 添加 genCValue 工具方法支持不同类型的 C 值生成
- 修改预处理器存储常量表达式节点信息
- 更新编译器基础类 getRelativePath 方法为公共访问
- 优化 stub 文件生成逻辑使用内部函数替代 shell 执行
- 添加错误处理机制替换原有的直接退出方式
pull/1/head
韩天峰 4 months ago
parent 36cf7dbf16
commit d85353f8eb
  1. 1
      bin/compiler.php
  2. 7
      examples/extends/A.php
  3. 2
      examples/extends/Foo.php
  4. 2
      src/Php/CompilerBase.php
  5. 3
      src/Php/Entity/ConstantDef.php
  6. 11
      src/Php/Generator/Utils.php
  7. 1
      src/Php/Preprocessor.php
  8. 67
      src/Php/Translator.php
  9. 70
      src/gen_stub.php

@ -1,6 +1,7 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php <?php
require __DIR__ . '/bootstrap.php'; require __DIR__ . '/bootstrap.php';
require __DIR__ . '/../src/gen_stub.php';
use PhpAot\Php\Exception\SyntaxError; use PhpAot\Php\Exception\SyntaxError;
use PhpAot\Php\Exception\Unsupported; use PhpAot\Php\Exception\Unsupported;

@ -1,8 +1,15 @@
<?php <?php
class A extends Foo class A extends Foo
{ {
const FOO = PHP_OS;
const FOO2 = PHP_INT_SIZE;
const BAZ = self::INIT_STATE;
public function __construct() public function __construct()
{ {
var_dump(self::BAZ);
echo "A::__construct()\n"; echo "A::__construct()\n";
} }
} }

@ -1,4 +1,4 @@
<?php <?php
class Foo extends ArrayObject { class Foo extends ArrayObject {
const INIT_STATE = 'init';
} }

@ -562,7 +562,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return false; return false;
} }
protected function getRelativePath($path, $cwd = ''): string public function getRelativePath($path, $cwd = ''): string
{ {
$cwd = $cwd ?: getcwd(); $cwd = $cwd ?: getcwd();
return ltrim($this->removeCommonPrefix($cwd, $path), '/'); return ltrim($this->removeCommonPrefix($cwd, $path), '/');

@ -8,6 +8,8 @@
namespace PhpAot\Php\Entity; namespace PhpAot\Php\Entity;
use PhpParser\NodeAbstract;
class ConstantDef class ConstantDef
{ {
public string $name; public string $name;
@ -16,6 +18,7 @@ class ConstantDef
public string $value; public string $value;
public string $arrayExpr = ''; public string $arrayExpr = '';
public string $class = ''; public string $class = '';
public ?NodeAbstract $valueExpr = null;
public function __construct(string $name, int $flags, string $type, string $value) public function __construct(string $name, int $flags, string $type, string $value)
{ {

@ -13,6 +13,17 @@ use PhpAot\Php\Constants;
trait Utils trait Utils
{ {
protected function genCValue(mixed $value): mixed
{
if (is_int($value) or is_float($value)) {
return $value;
} elseif (is_string($value)) {
return $this->genCharPtr($value);
} else {
$this->error('Unsupported constant type: ' . gettype($value));
}
}
protected function genCharPtr(string $str, bool $escape = false): string protected function genCharPtr(string $str, bool $escape = false): string
{ {
return '"' . ($escape ? $this->escapeString($str) : $str) . '"'; return '"' . ($escape ? $this->escapeString($str) : $str) . '"';

@ -518,6 +518,7 @@ class Preprocessor extends CompilerBase
$constValue = $this->parseIdentifier($const->value); $constValue = $this->parseIdentifier($const->value);
$constInfo = new ConstantDef($constName, $flags, $type, $constValue); $constInfo = new ConstantDef($constName, $flags, $type, $constValue);
$constInfo->valueExpr = $const->value;
if ($this->context->beforeStmtLines) { if ($this->context->beforeStmtLines) {
$arrayExpr = ''; $arrayExpr = '';

@ -21,6 +21,7 @@ use PhpAot\Php\Exception\Unsupported;
use PhpParser\Modifiers; use PhpParser\Modifiers;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\Node\Stmt\Foreach_; use PhpParser\Node\Stmt\Foreach_;
use PhpParser\NodeAbstract;
use PhpParser\NodeTraverser; use PhpParser\NodeTraverser;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
@ -106,7 +107,6 @@ class Translator extends Preprocessor
return $this->getCppFile($file); return $this->getCppFile($file);
} }
$phpCode = $this->loadFile($file); $phpCode = $this->loadFile($file);
$this->genStubFile($this->file);
$this->localHeaders = []; $this->localHeaders = [];
while (true) { while (true) {
try { try {
@ -114,7 +114,8 @@ class Translator extends Preprocessor
$cppFile = $this->getCppFile($file); $cppFile = $this->getCppFile($file);
$this->save($cppCode, $cppFile); $this->save($cppCode, $cppFile);
$this->phpSrcFiles[] = $file; $this->phpSrcFiles[] = $file;
// 生成 stub 文件,依赖 convert 阶段的 use 等信息
$this->genStubFile($this->file);
return $cppFile; return $cppFile;
} catch (Redo $e) { } catch (Redo $e) {
continue; continue;
@ -646,6 +647,11 @@ class Translator extends Preprocessor
return $list; return $list;
} }
public function getDefinedConstants(): array
{
return $this->internalConstants;
}
protected function getInternalCeInfo(string $ce): array protected function getInternalCeInfo(string $ce): array
{ {
return [ return [
@ -856,14 +862,13 @@ class Translator extends Preprocessor
$stubFilenameWithoutExtension = str_replace(['.stub.php', '.php'], '', $file); $stubFilenameWithoutExtension = str_replace(['.stub.php', '.php'], '', $file);
$headerFile = $this->getArgInfoHeaderFile($stubFilenameWithoutExtension, true); $headerFile = $this->getArgInfoHeaderFile($stubFilenameWithoutExtension, true);
$genStubCmd = PHP_BINARY . ' ' . $this->rootPath . '/bin/gen_stub.php -f -o ' . $this->getIncludeDir() . '/' . $headerFile . ' ' . $file; try {
$output = shell_exec($genStubCmd); generateStubFile($file, $this->getIncludeDir() . '/' . $headerFile, true);
$this->climate->info('generate stub file: ' . $this->getRelativePath($file)); } catch (\Throwable $e) {
$this->climate->comment($genStubCmd); $this->error("failed to generate arginfo header file: `{$headerFile}`, Error: {$e->getMessage()}");
if (!str_contains($output, 'Saved')) {
$this->error("failed to generate arginfo header file: `{$headerFile}`, output: {$output}");
} }
$this->climate->info('generate stub file: ' . $this->getRelativePath($file));
if ($this->useRegisterSymbolsFn) { if ($this->useRegisterSymbolsFn) {
preg_match('/php_(.*)_arginfo.h/', $headerFile, $matches); preg_match('/php_(.*)_arginfo.h/', $headerFile, $matches);
$registerSymbolFn = 'register_' . $matches[1] . '_symbols'; $registerSymbolFn = 'register_' . $matches[1] . '_symbols';
@ -1301,4 +1306,48 @@ class Translator extends Preprocessor
return $code; return $code;
} }
/**
* 仅用于 gen_stub 脚本
* @param NodeAbstract $expr
* @param string $class
* @param string $name
* @return mixed
* @throws \Exception
*/
public function getClassConstValue(NodeAbstract $expr, string $class, string $name): mixed
{
$class = $this->getNamespacedClassName($class);
$nativeConst = $this->findNativeClassConst($expr, $class, $name);
if ($nativeConst and $expr->hasAttribute('nativeConst')) {
$constDef = $expr->getAttribute('nativeConst');
return $this->genCValue($constDef->valueExpr->value);
}
throw new \Exception("Class constant `$class::$name` not found");
}
public function getConstValue(string $name): mixed
{
if ($this->isInternalConstant($name)) {
$value = $this->internalConstants[$name];
if (is_int($value)) {
$expr = strval($value);
if ($value === PHP_INT_MIN) {
$expr = 'LONG_MIN';
} elseif ($value === PHP_INT_MAX) {
$expr = 'LONG_MAX';
} else {
$expr = $expr . 'L';
}
} elseif (is_float($value)) {
return $value;
} elseif (is_string($value)) {
return $this->genCharPtr($value);
} else {
$this->error('Unsupported constant type: ' . gettype($value));
}
return $expr;
}
throw new \Exception('Constant ' . $name . ' not found');
}
} }

@ -1,5 +1,5 @@
#!/usr/bin/env php <?php
<?php declare(strict_types=1); declare(strict_types=1);
use PhpAot\Php\Translator; use PhpAot\Php\Translator;
use PhpParser\Comment\Doc as DocComment; use PhpParser\Comment\Doc as DocComment;
@ -37,9 +37,7 @@ const ALL_PHP_VERSION_IDS = [
// file_put_contents() but with a success message printed after saving // file_put_contents() but with a success message printed after saving
function reportFilePutContents(string $filename, string $content): void { function reportFilePutContents(string $filename, string $content): void {
global $translator; getTranslator()->writeFile($filename, $content);
$translator->writeFile($filename, $content);
echo "Saved $filename\n";
} }
/** /**
@ -149,8 +147,7 @@ function processStubFile(string $stubFile, Context $context, bool $includeOnly =
return $fileInfo; return $fileInfo;
} catch (Exception $e) { } catch (Exception $e) {
echo "In $stubFile:\n{$e->getMessage()}\n"; throw new RuntimeException("In " . getTranslator()->getRelativePath($stubFile) . ": {$e->getMessage()}");
exit(1);
} }
} }
@ -2309,7 +2306,7 @@ class EvaluatedValue
if (isset($allConstInfos[$constName])) { if (isset($allConstInfos[$constName])) {
return $allConstInfos[$constName]->getValue($allConstInfos)->value; return $allConstInfos[$constName]->getValue($allConstInfos)->value;
} else { } else {
throw new Exception("Class constant `$constName` not found"); return getTranslator()->getClassConstValue($expr, ClassInfo::$currentClass->name->toString(), $expr->name->toString());
} }
} elseif ($expr->name->__toString() === 'class') { } elseif ($expr->name->__toString() === 'class') {
return $class; return $class;
@ -2337,6 +2334,7 @@ class EvaluatedValue
} elseif ($constType->isFloat()) { } elseif ($constType->isFloat()) {
return M_PI; return M_PI;
} elseif ($constType->isString()) { } elseif ($constType->isString()) {
var_dump($const);
return $const->name; return $const->name;
} elseif ($constType->isArray()) { } elseif ($constType->isArray()) {
return []; return [];
@ -2346,7 +2344,7 @@ class EvaluatedValue
return null; return null;
} }
global $definedConstants; $definedConstants = getTranslator()->getDefinedConstants();
if (isset($definedConstants[$constName])) { if (isset($definedConstants[$constName])) {
$constValue = $definedConstants[$constName]; $constValue = $definedConstants[$constName];
if (is_scalar($constValue)) { if (is_scalar($constValue)) {
@ -2448,28 +2446,17 @@ class EvaluatedValue
$this->expr->name->__toString() === 'class') { $this->expr->name->__toString() === 'class') {
$expr = '"' . addcslashes($this->expr->class->name, '\\') . '"'; $expr = '"' . addcslashes($this->expr->class->name, '\\') . '"';
} else { } else {
var_dump($this->value); return $this->value;
} }
} elseif (!($this->expr instanceof String_)) { } elseif (!($this->expr instanceof String_)) {
if ($this->expr instanceof Expr\ConstFetch) {
return getTranslator()->getConstValue($this->expr->name->toString());
}
throw new Exception("Expression at line " . $this->expr->getStartLine() . " must be a scalar string"); throw new Exception("Expression at line " . $this->expr->getStartLine() . " must be a scalar string");
} }
$expr = preg_replace("/(^'|'$)/", '"', $expr); $expr = preg_replace("/(^'|'$)/", '"', $expr);
} elseif ($this->type->isInt() or $this->type->isFloat()) { } elseif ($this->type->isInt() or $this->type->isFloat() or $this->expr instanceof Expr\ConstFetch) {
return strval($this->value); return strval($this->value);
} else {
if ($this->expr instanceof Expr\ConstFetch) {
$value = constant($this->expr->name->__toString());
if (is_int($value)) {
$expr = strval($value);
if ($value === PHP_INT_MIN) {
$expr = 'LONG_MIN';
} elseif ($value === PHP_INT_MAX) {
$expr = 'LONG_MAX';
} else {
$expr = $expr . 'L';
}
}
}
} }
return $expr[0] == '"' ? $expr : preg_replace('(\bnull\b)', 'NULL', str_replace('\\', '', $expr)); return $expr[0] == '"' ? $expr : preg_replace('(\bnull\b)', 'NULL', str_replace('\\', '', $expr));
} }
@ -6172,20 +6159,17 @@ function initPhpParser() {
$isInitialized = true; $isInitialized = true;
} }
function main() function getTranslator(): Translator
{ {
global $argv, $argc, $translator, $definedConstants; global $translator;
return $translator;
error_reporting(E_ALL & ~E_DEPRECATED); }
ini_set("precision", "-1");
require __DIR__ . '/bootstrap.php';
$translator = new PhpAot\Php\Translator(ROOT_PATH);
$translator->setIndent("\t");
$translator->setIndentLevel(1);
$definedConstants = get_defined_constants();
/**
* @throws Exception
*/
function generateStubFile(string $stubFile, string $objectFile, bool $forceRegeneration): void
{
$opt_index = 0; $opt_index = 0;
$options = getopt( $options = getopt(
"fho:", "fho:",
@ -6208,15 +6192,15 @@ function main()
$replaceMethodSynopses = isset($options["replace-methodsynopses"]); $replaceMethodSynopses = isset($options["replace-methodsynopses"]);
$generateOptimizerInfo = isset($options["generate-optimizer-info"]); $generateOptimizerInfo = isset($options["generate-optimizer-info"]);
$context->forceRegeneration = isset($options["f"]) || isset($options["force-regeneration"]); $context->forceRegeneration = $forceRegeneration;
$context->objectFile = $options["o"] ?? ''; $context->objectFile = $objectFile;
$context->forceParse = $context->forceRegeneration || $printParameterStats || $verify || $verifyManual || $replacePredefinedConstants || $generateClassSynopses || $generateOptimizerInfo || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses; $context->forceParse = $context->forceRegeneration || $printParameterStats || $verify || $verifyManual || $replacePredefinedConstants || $generateClassSynopses || $generateOptimizerInfo || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses;
if (isset($options["h"]) || isset($options["help"])) { if (isset($options["h"]) || isset($options["help"])) {
die("\nUsage: gen_stub.php [ -f | --force-regeneration ] [ --replace-predefined-constants ] [ --generate-classsynopses ] [ --replace-classsynopses ] [ --generate-methodsynopses ] [ --replace-methodsynopses ] [ --parameter-stats ] [ --verify ] [ --verify-manual ] [ --generate-optimizer-info ] [ -h | --help ] [ name.stub.php | directory ] [ directory ]\n\n"); die("\nUsage: gen_stub.php [ -f | --force-regeneration ] [ --replace-predefined-constants ] [ --generate-classsynopses ] [ --replace-classsynopses ] [ --generate-methodsynopses ] [ --replace-methodsynopses ] [ --parameter-stats ] [ --verify ] [ --verify-manual ] [ --generate-optimizer-info ] [ -h | --help ] [ name.stub.php | directory ] [ directory ]\n\n");
} }
$locations = array_slice($argv, $opt_index); $locations = [$stubFile];
$locationCount = count($locations); $locationCount = count($locations);
if ($replacePredefinedConstants && $locationCount < 2) { if ($replacePredefinedConstants && $locationCount < 2) {
die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --replace-predefined-constants ./ ../doc-en/\n"); die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --replace-predefined-constants ./ ../doc-en/\n");
@ -6388,10 +6372,8 @@ function main()
} }
} }
echo implode("\n", $errors);
if (!empty($errors)) { if (!empty($errors)) {
echo "\n"; throw new Exception("Errors found: " . implode("\n", $errors));
exit(1);
} }
} }
@ -6492,5 +6474,3 @@ function main()
} }
} }
} }
main();
Loading…
Cancel
Save