refactor(php): 重构PHP编译器基础功能并优化代码结构

- 移除不支持函数列表的硬编码定义
- 添加对不支持函数调用的运行时检查和错误报告
- 简化静态属性和类常量解析中的self关键字处理逻辑
- 修复类方法调用中self关键字的替换逻辑
- 将扩展模块入口声明从static改为全局作用域
- 重写gen_stub.php脚本的主函数结构和命令行参数处理
- 添加类信息和函数信息的独立生成选项
- 优化桩文件生成时的跳过提示输出
- 重构翻译器中的类解析方法实现
- 更新不支持函数列表包含更多动态函数
pull/1/head
韩天峰 7 months ago
parent ec106db274
commit 8b0c3d2b72
  1. 516
      bin/gen_stub.php
  2. 22
      src/Php/CompilerBase.php
  3. 34
      src/Php/Translator.php
  4. 10
      src/cpp/main.cc
  5. 2
      src/template/extension.cc.php

@ -16,14 +16,6 @@ use PhpParser\Node\Stmt\Trait_;
use PhpParser\PrettyPrinter\Standard; use PhpParser\PrettyPrinter\Standard;
use PhpParser\PrettyPrinterAbstract; use PhpParser\PrettyPrinterAbstract;
error_reporting(E_ALL);
ini_set("precision", "-1");
require __DIR__ . '/bootstrap.php';
$translator = new PhpAot\Php\Translator(ROOT_PATH);
$translator->setIndent("\t");
$translator->setIndentLevel(1);
const PHP_70_VERSION_ID = 70000; const PHP_70_VERSION_ID = 70000;
const PHP_80_VERSION_ID = 80000; const PHP_80_VERSION_ID = 80000;
const PHP_81_VERSION_ID = 80100; const PHP_81_VERSION_ID = 80100;
@ -94,6 +86,7 @@ function processStubFile(string $stubFile, Context $context, bool $includeOnly =
$oldStubHash = extractStubHash($arginfoFile); $oldStubHash = extractStubHash($arginfoFile);
if ($stubHash === $oldStubHash && !$context->forceParse) { if ($stubHash === $oldStubHash && !$context->forceParse) {
/* Stub file did not change, do not regenerate. */ /* Stub file did not change, do not regenerate. */
echo "Skipping $stubFile, stub hash unchanged\n";
return null; return null;
} }
} }
@ -4238,10 +4231,15 @@ class FileInfo {
/** /**
* @return iterable<FuncInfo> * @return iterable<FuncInfo>
*/ */
public function getAllFuncInfos(): iterable { public function getAllFuncInfos(): iterable
yield from $this->funcInfos; {
foreach ($this->classInfos as $classInfo) { global $genClass;
yield from $classInfo->funcInfos; if ($genClass) {
foreach ($this->classInfos as $classInfo) {
yield from $classInfo->funcInfos;
}
} else {
yield from $this->funcInfos;
} }
} }
@ -5171,6 +5169,8 @@ function generateArgInfoCode(
$generatedFuncInfos = []; $generatedFuncInfos = [];
global $genClass;
$argInfoCode = generateCodeWithConditions( $argInfoCode = generateCodeWithConditions(
$fileInfo->getAllFuncInfos(), "\n", $fileInfo->getAllFuncInfos(), "\n",
static function (FuncInfo $funcInfo) use (&$generatedFuncInfos, $fileInfo) { static function (FuncInfo $funcInfo) use (&$generatedFuncInfos, $fileInfo) {
@ -5221,14 +5221,16 @@ function generateArgInfoCode(
$code .= generateFunctionEntries(null, $fileInfo->funcInfos); $code .= generateFunctionEntries(null, $fileInfo->funcInfos);
foreach ($fileInfo->classInfos as $classInfo) { if ($genClass) {
$code .= generateFunctionEntries($classInfo->name, $classInfo->funcInfos, $classInfo->cond); foreach ($fileInfo->classInfos as $classInfo) {
$code .= generateFunctionEntries($classInfo->name, $classInfo->funcInfos, $classInfo->cond);
}
} }
} }
$php80MinimumCompatibility = $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_80_VERSION_ID; $php80MinimumCompatibility = $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_80_VERSION_ID;
if ($fileInfo->generateClassEntries) { if ($genClass and $fileInfo->generateClassEntries) {
$declaredStrings = []; $declaredStrings = [];
$attributeInitializationCode = generateFunctionAttributeInitialization($fileInfo->funcInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null, $declaredStrings); $attributeInitializationCode = generateFunctionAttributeInitialization($fileInfo->funcInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null, $declaredStrings);
$attributeInitializationCode .= generateGlobalConstantAttributeInitialization($fileInfo->constInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null, $declaredStrings); $attributeInitializationCode .= generateGlobalConstantAttributeInitialization($fileInfo->constInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null, $declaredStrings);
@ -6097,306 +6099,330 @@ function initPhpParser() {
$isInitialized = true; $isInitialized = true;
} }
$optind = null; function main()
$options = getopt( {
"fh", global $argv, $argc, $genClass, $translator;
[
"force-regeneration", "parameter-stats", "help", "verify", "verify-manual", "replace-predefined-constants",
"generate-classsynopses", "replace-classsynopses", "generate-methodsynopses", "replace-methodsynopses",
"generate-optimizer-info",
],
$optind
);
$context = new Context;
$printParameterStats = isset($options["parameter-stats"]);
$verify = isset($options["verify"]);
$verifyManual = isset($options["verify-manual"]);
$replacePredefinedConstants = isset($options["replace-predefined-constants"]);
$generateClassSynopses = isset($options["generate-classsynopses"]);
$replaceClassSynopses = isset($options["replace-classsynopses"]);
$generateMethodSynopses = isset($options["generate-methodsynopses"]);
$replaceMethodSynopses = isset($options["replace-methodsynopses"]);
$generateOptimizerInfo = isset($options["generate-optimizer-info"]);
$context->forceRegeneration = isset($options["f"]) || isset($options["force-regeneration"]);
$context->forceParse = $context->forceRegeneration || $printParameterStats || $verify || $verifyManual || $replacePredefinedConstants || $generateClassSynopses || $generateOptimizerInfo || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses;
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");
}
$locations = array_slice($argv, $optind); error_reporting(E_ALL);
$locationCount = count($locations); ini_set("precision", "-1");
if ($replacePredefinedConstants && $locationCount < 2) { require __DIR__ . '/bootstrap.php';
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");
}
if ($replaceClassSynopses && $locationCount < 2) {
die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --replace-classsynopses ./ ../doc-en/\n");
}
if ($generateMethodSynopses && $locationCount < 2) {
die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --generate-methodsynopses ./ ../doc-en/\n");
}
if ($replaceMethodSynopses && $locationCount < 2) {
die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --replace-methodsynopses ./ ../doc-en/\n");
}
if ($verifyManual && $locationCount < 2) {
die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --verify-manual ./ ../doc-en/\n");
}
$manualTarget = null;
if ($replacePredefinedConstants || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses || $verifyManual) {
$manualTarget = array_pop($locations);
}
if ($locations === []) {
$locations = ['.'];
}
$fileInfos = []; $translator = new PhpAot\Php\Translator(ROOT_PATH);
foreach (array_unique($locations) as $location) { $translator->setIndent("\t");
if (is_file($location)) { $translator->setIndentLevel(1);
// Generate single file.
$fileInfo = processStubFile($location, $context);
if ($fileInfo) {
$fileInfos[] = $fileInfo;
}
} else if (is_dir($location)) {
array_push($fileInfos, ...processDirectory($location, $context));
} else {
echo "$location is neither a file nor a directory.\n";
exit(1);
}
}
if ($printParameterStats) { $opt_index = 0;
$parameterStats = []; $options = getopt(
"fh",
[
"force-regeneration", "parameter-stats", "help", "verify", "verify-manual", "replace-predefined-constants",
"generate-classsynopses", "replace-classsynopses", "generate-methodsynopses", "replace-methodsynopses",
"generate-optimizer-info", "gen-class-info", "gen-func-info",
],
$opt_index
);
foreach ($fileInfos as $fileInfo) { $context = new Context;
foreach ($fileInfo->getAllFuncInfos() as $funcInfo) { $printParameterStats = isset($options["parameter-stats"]);
foreach ($funcInfo->args as $argInfo) { $verify = isset($options["verify"]);
if (!isset($parameterStats[$argInfo->name])) { $verifyManual = isset($options["verify-manual"]);
$parameterStats[$argInfo->name] = 0; $replacePredefinedConstants = isset($options["replace-predefined-constants"]);
} $generateClassSynopses = isset($options["generate-classsynopses"]);
$parameterStats[$argInfo->name]++; $replaceClassSynopses = isset($options["replace-classsynopses"]);
} $generateMethodSynopses = isset($options["generate-methodsynopses"]);
} $replaceMethodSynopses = isset($options["replace-methodsynopses"]);
$generateOptimizerInfo = isset($options["generate-optimizer-info"]);
if (isset($options["gen-class-info"])) {
$genClass = true;
} elseif (isset($options["gen-func-info"])) {
$genClass = false;
} else {
die("Please specify whether to generate class or function info\n");
} }
arsort($parameterStats); $context->forceRegeneration = isset($options["f"]) || isset($options["force-regeneration"]);
echo json_encode($parameterStats, JSON_PRETTY_PRINT), "\n"; $context->forceParse = $context->forceRegeneration || $printParameterStats || $verify || $verifyManual || $replacePredefinedConstants || $generateClassSynopses || $generateOptimizerInfo || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses;
}
/** @var array<string, ClassInfo> $classMap */
$classMap = [];
/** @var array<string, FuncInfo> $funcMap */
$funcMap = [];
/** @var array<string, FuncInfo> $aliasMap */
$aliasMap = [];
/** @var array<string, ConstInfo> $undocumentedConstMap */ if (isset($options["h"]) || isset($options["help"])) {
$undocumentedConstMap = []; 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");
/** @var array<string, ClassInfo> $undocumentedClassMap */ }
$undocumentedClassMap = [];
/** @var array<string, FuncInfo> $undocumentedFuncMap */
$undocumentedFuncMap = [];
/** @var array<int, string> $methodSynopsisWarnings */
$methodSynopsisWarnings = [];
foreach ($fileInfos as $fileInfo) { $locations = array_slice($argv, $opt_index);
foreach ($fileInfo->getAllFuncInfos() as $funcInfo) { $locationCount = count($locations);
$funcMap[$funcInfo->name->__toString()] = $funcInfo; 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");
}
if ($replaceClassSynopses && $locationCount < 2) {
die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --replace-classsynopses ./ ../doc-en/\n");
}
if ($generateMethodSynopses && $locationCount < 2) {
die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --generate-methodsynopses ./ ../doc-en/\n");
}
if ($replaceMethodSynopses && $locationCount < 2) {
die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --replace-methodsynopses ./ ../doc-en/\n");
}
if ($verifyManual && $locationCount < 2) {
die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --verify-manual ./ ../doc-en/\n");
}
$manualTarget = null;
if ($replacePredefinedConstants || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses || $verifyManual) {
$manualTarget = array_pop($locations);
}
if ($locations === []) {
$locations = ['.'];
}
// TODO: Don't use aliasMap for methodsynopsis? $fileInfos = [];
if ($funcInfo->aliasType === "alias") { foreach (array_unique($locations) as $location) {
$aliasMap[$funcInfo->alias->__toString()] = $funcInfo; if (is_file($location)) {
// Generate single file.
$fileInfo = processStubFile($location, $context);
if ($fileInfo) {
$fileInfos[] = $fileInfo;
}
} else if (is_dir($location)) {
array_push($fileInfos, ...processDirectory($location, $context));
} else {
echo "$location is neither a file nor a directory.\n";
exit(1);
} }
} }
foreach ($fileInfo->classInfos as $classInfo) { if ($printParameterStats) {
$classMap[$classInfo->name->__toString()] = $classInfo; $parameterStats = [];
if ($classInfo->alias !== null) { foreach ($fileInfos as $fileInfo) {
$classMap[$classInfo->alias] = $classInfo; foreach ($fileInfo->getAllFuncInfos() as $funcInfo) {
foreach ($funcInfo->args as $argInfo) {
if (!isset($parameterStats[$argInfo->name])) {
$parameterStats[$argInfo->name] = 0;
}
$parameterStats[$argInfo->name]++;
}
}
} }
arsort($parameterStats);
echo json_encode($parameterStats, JSON_PRETTY_PRINT), "\n";
} }
}
if ($verify) { /** @var array<string, ClassInfo> $classMap */
$errors = []; $classMap = [];
/** @var array<string, FuncInfo> $funcMap */
$funcMap = [];
/** @var array<string, FuncInfo> $aliasMap */
$aliasMap = [];
foreach ($funcMap as $aliasFunc) { /** @var array<string, ConstInfo> $undocumentedConstMap */
if (!$aliasFunc->alias || $aliasFunc->aliasType !== "alias") { $undocumentedConstMap = [];
continue; /** @var array<string, ClassInfo> $undocumentedClassMap */
} $undocumentedClassMap = [];
/** @var array<string, FuncInfo> $undocumentedFuncMap */
$undocumentedFuncMap = [];
/** @var array<int, string> $methodSynopsisWarnings */
$methodSynopsisWarnings = [];
if (!isset($funcMap[$aliasFunc->alias->__toString()])) { foreach ($fileInfos as $fileInfo) {
$errors[] = "Aliased function {$aliasFunc->alias}() cannot be found"; foreach ($fileInfo->getAllFuncInfos() as $funcInfo) {
continue; $funcMap[$funcInfo->name->__toString()] = $funcInfo;
// TODO: Don't use aliasMap for methodsynopsis?
if ($funcInfo->aliasType === "alias") {
$aliasMap[$funcInfo->alias->__toString()] = $funcInfo;
}
} }
if (!$aliasFunc->verify) { foreach ($fileInfo->classInfos as $classInfo) {
continue; $classMap[$classInfo->name->__toString()] = $classInfo;
if ($classInfo->alias !== null) {
$classMap[$classInfo->alias] = $classInfo;
}
} }
}
$aliasedFunc = $funcMap[$aliasFunc->alias->__toString()]; if ($verify) {
$aliasedArgs = $aliasedFunc->args; $errors = [];
$aliasArgs = $aliasFunc->args;
if ($aliasFunc->isInstanceMethod() !== $aliasedFunc->isInstanceMethod()) { foreach ($funcMap as $aliasFunc) {
if ($aliasFunc->isInstanceMethod()) { if (!$aliasFunc->alias || $aliasFunc->aliasType !== "alias") {
$aliasedArgs = array_slice($aliasedArgs, 1); continue;
} }
if ($aliasedFunc->isInstanceMethod()) { if (!isset($funcMap[$aliasFunc->alias->__toString()])) {
$aliasArgs = array_slice($aliasArgs, 1); $errors[] = "Aliased function {$aliasFunc->alias}() cannot be found";
continue;
} }
}
array_map( if (!$aliasFunc->verify) {
function(?ArgInfo $aliasArg, ?ArgInfo $aliasedArg) use ($aliasFunc, $aliasedFunc, &$errors) { continue;
if ($aliasArg === null) { }
assert($aliasedArg !== null);
$errors[] = "{$aliasFunc->name}(): Argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() is missing";
return null;
}
if ($aliasedArg === null) { $aliasedFunc = $funcMap[$aliasFunc->alias->__toString()];
$errors[] = "{$aliasedFunc->name}(): Argument \$$aliasArg->name of alias function {$aliasFunc->name}() is missing"; $aliasedArgs = $aliasedFunc->args;
return null; $aliasArgs = $aliasFunc->args;
}
if ($aliasArg->name !== $aliasedArg->name) { if ($aliasFunc->isInstanceMethod() !== $aliasedFunc->isInstanceMethod()) {
$errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same name"; if ($aliasFunc->isInstanceMethod()) {
return null; $aliasedArgs = array_slice($aliasedArgs, 1);
} }
if ($aliasArg->type != $aliasedArg->type) { if ($aliasedFunc->isInstanceMethod()) {
$errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same type"; $aliasArgs = array_slice($aliasArgs, 1);
} }
}
if ($aliasArg->defaultValue !== $aliasedArg->defaultValue) { array_map(
$errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same default value"; function(?ArgInfo $aliasArg, ?ArgInfo $aliasedArg) use ($aliasFunc, $aliasedFunc, &$errors) {
} if ($aliasArg === null) {
}, assert($aliasedArg !== null);
$aliasArgs, $aliasedArgs $errors[] = "{$aliasFunc->name}(): Argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() is missing";
); return null;
}
if ($aliasedArg === null) {
$errors[] = "{$aliasedFunc->name}(): Argument \$$aliasArg->name of alias function {$aliasFunc->name}() is missing";
return null;
}
$aliasedReturn = $aliasedFunc->return; if ($aliasArg->name !== $aliasedArg->name) {
$aliasReturn = $aliasFunc->return; $errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same name";
return null;
}
if ($aliasArg->type != $aliasedArg->type) {
$errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same type";
}
if ($aliasArg->defaultValue !== $aliasedArg->defaultValue) {
$errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same default value";
}
},
$aliasArgs, $aliasedArgs
);
$aliasedReturn = $aliasedFunc->return;
$aliasReturn = $aliasFunc->return;
if (!$aliasedFunc->name->isConstructor() && !$aliasFunc->name->isConstructor()) {
$aliasedReturnType = $aliasedReturn->type ?? $aliasedReturn->phpDocType;
$aliasReturnType = $aliasReturn->type ?? $aliasReturn->phpDocType;
if ($aliasReturnType != $aliasedReturnType) {
$errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same return type";
}
}
if (!$aliasedFunc->name->isConstructor() && !$aliasFunc->name->isConstructor()) { $aliasedPhpDocReturnType = $aliasedReturn->phpDocType;
$aliasedReturnType = $aliasedReturn->type ?? $aliasedReturn->phpDocType; $aliasPhpDocReturnType = $aliasReturn->phpDocType;
$aliasReturnType = $aliasReturn->type ?? $aliasReturn->phpDocType; if ($aliasedPhpDocReturnType != $aliasPhpDocReturnType && $aliasedPhpDocReturnType != $aliasReturn->type && $aliasPhpDocReturnType != $aliasedReturn->type) {
if ($aliasReturnType != $aliasedReturnType) { $errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same PHPDoc return type";
$errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same return type";
} }
} }
$aliasedPhpDocReturnType = $aliasedReturn->phpDocType; echo implode("\n", $errors);
$aliasPhpDocReturnType = $aliasReturn->phpDocType; if (!empty($errors)) {
if ($aliasedPhpDocReturnType != $aliasPhpDocReturnType && $aliasedPhpDocReturnType != $aliasReturn->type && $aliasPhpDocReturnType != $aliasedReturn->type) { echo "\n";
$errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same PHPDoc return type"; exit(1);
} }
} }
echo implode("\n", $errors); if ($replacePredefinedConstants || $verifyManual) {
if (!empty($errors)) { $predefinedConstants = replacePredefinedConstants($manualTarget, $context->allConstInfos, $undocumentedConstMap);
echo "\n";
exit(1);
}
}
if ($replacePredefinedConstants || $verifyManual) {
$predefinedConstants = replacePredefinedConstants($manualTarget, $context->allConstInfos, $undocumentedConstMap);
if ($replacePredefinedConstants) { if ($replacePredefinedConstants) {
foreach ($predefinedConstants as $filename => $content) { foreach ($predefinedConstants as $filename => $content) {
reportFilePutContents($filename, $content); reportFilePutContents($filename, $content);
}
} }
} }
}
if ($generateClassSynopses) { if ($generateClassSynopses) {
$classSynopsesDirectory = getcwd() . "/classsynopses"; $classSynopsesDirectory = getcwd() . "/classsynopses";
$classSynopses = generateClassSynopses($classMap, $context->allConstInfos); $classSynopses = generateClassSynopses($classMap, $context->allConstInfos);
if (!empty($classSynopses)) { if (!empty($classSynopses)) {
if (!file_exists($classSynopsesDirectory)) { if (!file_exists($classSynopsesDirectory)) {
mkdir($classSynopsesDirectory); mkdir($classSynopsesDirectory);
} }
foreach ($classSynopses as $filename => $content) { foreach ($classSynopses as $filename => $content) {
reportFilePutContents("$classSynopsesDirectory/$filename", $content); reportFilePutContents("$classSynopsesDirectory/$filename", $content);
}
} }
} }
}
if ($replaceClassSynopses || $verifyManual) { if ($replaceClassSynopses || $verifyManual) {
$classSynopses = replaceClassSynopses($manualTarget, $classMap, $context->allConstInfos, $undocumentedClassMap); $classSynopses = replaceClassSynopses($manualTarget, $classMap, $context->allConstInfos, $undocumentedClassMap);
if ($replaceClassSynopses) { if ($replaceClassSynopses) {
foreach ($classSynopses as $filename => $content) { foreach ($classSynopses as $filename => $content) {
reportFilePutContents($filename, $content); reportFilePutContents($filename, $content);
}
} }
} }
}
if ($generateMethodSynopses) { if ($generateMethodSynopses) {
$methodSynopses = generateMethodSynopses($funcMap); $methodSynopses = generateMethodSynopses($funcMap);
if (!file_exists($manualTarget)) { if (!file_exists($manualTarget)) {
mkdir($manualTarget); mkdir($manualTarget);
} }
foreach ($methodSynopses as $filename => $content) { foreach ($methodSynopses as $filename => $content) {
$path = "$manualTarget/$filename"; $path = "$manualTarget/$filename";
if (!file_exists($path)) { if (!file_exists($path)) {
if (!file_exists(dirname($path))) { if (!file_exists(dirname($path))) {
mkdir(dirname($path)); mkdir(dirname($path));
} }
reportFilePutContents($path, $content); reportFilePutContents($path, $content);
}
} }
} }
}
if ($replaceMethodSynopses || $verifyManual) { if ($replaceMethodSynopses || $verifyManual) {
$methodSynopses = replaceMethodSynopses($manualTarget, $funcMap, $verifyManual, $methodSynopsisWarnings, $undocumentedFuncMap); $methodSynopses = replaceMethodSynopses($manualTarget, $funcMap, $verifyManual, $methodSynopsisWarnings, $undocumentedFuncMap);
if ($replaceMethodSynopses) { if ($replaceMethodSynopses) {
foreach ($methodSynopses as $filename => $content) { foreach ($methodSynopses as $filename => $content) {
reportFilePutContents($filename, $content); reportFilePutContents($filename, $content);
}
} }
} }
}
if ($generateOptimizerInfo) { if ($generateOptimizerInfo) {
$filename = dirname(__FILE__, 2) . "/Zend/Optimizer/zend_func_infos.h"; $filename = dirname(__FILE__, 2) . "/Zend/Optimizer/zend_func_infos.h";
$optimizerInfo = generateOptimizerInfo($funcMap); $optimizerInfo = generateOptimizerInfo($funcMap);
reportFilePutContents($filename, $optimizerInfo); reportFilePutContents($filename, $optimizerInfo);
} }
if ($verifyManual) { if ($verifyManual) {
foreach ($undocumentedConstMap as $constName => $info) { foreach ($undocumentedConstMap as $constName => $info) {
if ($info->name instanceof ClassConstName || $info->isUndocumentable) { if ($info->name instanceof ClassConstName || $info->isUndocumentable) {
continue; continue;
} }
echo "Warning: Missing predefined constant for $constName\n"; echo "Warning: Missing predefined constant for $constName\n";
} }
foreach ($methodSynopsisWarnings as $warning) { foreach ($methodSynopsisWarnings as $warning) {
echo "Warning: $warning\n"; echo "Warning: $warning\n";
} }
foreach ($undocumentedClassMap as $className => $info) { foreach ($undocumentedClassMap as $className => $info) {
if (!$info->isUndocumentable) { if (!$info->isUndocumentable) {
echo "Warning: Missing class synopsis for $className\n"; echo "Warning: Missing class synopsis for $className\n";
}
} }
}
foreach ($undocumentedFuncMap as $functionName => $info) { foreach ($undocumentedFuncMap as $functionName => $info) {
if (!$info->isUndocumentable) { if (!$info->isUndocumentable) {
echo "Warning: Missing method synopsis for $functionName()\n"; echo "Warning: Missing method synopsis for $functionName()\n";
}
} }
} }
} }
main();

@ -53,13 +53,7 @@ class CompilerBase extends \PhpAot\Core\Translator
'float' => self::TYPE_FLOAT, 'float' => self::TYPE_FLOAT,
'bool' => self::TYPE_BOOL, 'bool' => self::TYPE_BOOL,
]; ];
protected array $reservedNames; protected array $reservedNames;
protected array $unsupportedFunctions = [
'compact',
'extract'
];
protected array $globalHeaders = [ protected array $globalHeaders = [
'phpx.h', 'phpx.h',
'phpx_helper.h', 'phpx_helper.h',
@ -135,7 +129,6 @@ class CompilerBase extends \PhpAot\Core\Translator
$climate = new CLImate(); $climate = new CLImate();
$this->climate = $climate; $this->climate = $climate;
// $this->noLiteralStrings = $climate->arguments->get('no-literal-strings'); // $this->noLiteralStrings = $climate->arguments->get('no-literal-strings');
$this->noLiteralStrings = true;
} }
public function setPhpxDir($dir): void public function setPhpxDir($dir): void
@ -1295,6 +1288,9 @@ class CompilerBase extends \PhpAot\Core\Translator
$name = ''; $name = '';
} elseif ($expr->name->getType() === 'Name') { } elseif ($expr->name->getType() === 'Name') {
$name = $this->parseIdentifier($expr->name); $name = $this->parseIdentifier($expr->name);
if (in_array($name, $this->unsupportedFunctions)) {
$this->fatalError($expr, 'Unsupported function: `' . $name . '`');
}
$nativeFn = $this->findNativeFunction($name); $nativeFn = $this->findNativeFunction($name);
if ($nativeFn) { if ($nativeFn) {
return self::PREFIX . $nativeFn . '(' . $this->parseCallArgs($expr->args, $name) . ')'; return self::PREFIX . $nativeFn . '(' . $this->parseCallArgs($expr->args, $name) . ')';
@ -2106,10 +2102,7 @@ class CompilerBase extends \PhpAot\Core\Translator
} elseif ($var instanceof Node\Expr\ArrayDimFetch) { } elseif ($var instanceof Node\Expr\ArrayDimFetch) {
return $this->parseIdentifier($var->var) . ".offsetExists(" . $this->parseIdentifier($var->dim) . ')'; return $this->parseIdentifier($var->var) . ".offsetExists(" . $this->parseIdentifier($var->dim) . ')';
} elseif ($var instanceof Node\Expr\StaticPropertyFetch) { } elseif ($var instanceof Node\Expr\StaticPropertyFetch) {
$class = $this->parseIdentifier($var->class); return 'php::hasStaticProperty(' . $this->identifierToStr($var->class) . ', ' . $this->identifierToStr($var->name) . ')';
$prop = $var->name;
$class = $class === 'self' ? $this->class : $class;
return 'php::hasStaticProperty("' . $class . '", ' . $this->identifierToStr($prop) . ')';
} elseif ($var instanceof Node\Expr\PropertyFetch) { } elseif ($var instanceof Node\Expr\PropertyFetch) {
$object = $var->var; $object = $var->var;
$prop = $var->name; $prop = $var->name;
@ -2228,6 +2221,9 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
return $id; return $id;
} else { } else {
if ($id === 'self') {
$id = $this->class;
}
return '"'. $id . '"'; return '"'. $id . '"';
} }
} }
@ -2245,8 +2241,9 @@ class CompilerBase extends \PhpAot\Core\Translator
$fn = 'php::concat({' . $this->identifierToStr($expr->class). ', "::", ' . $this->identifierToStr($expr->name) . '})'; $fn = 'php::concat({' . $this->identifierToStr($expr->class). ', "::", ' . $this->identifierToStr($expr->name) . '})';
} else { } else {
$class = $this->parseIdentifier($expr->class); $class = $this->parseIdentifier($expr->class);
$class = $class === 'self' ? $this->class : $class;
$method = $this->parseIdentifier($expr->name); $method = $this->parseIdentifier($expr->name);
$fn = '"'. $class . '::' . $method . '"'; $fn = '"' . $class . '::' . $method . '"';
} }
if (empty($expr->args)) { if (empty($expr->args)) {
return 'php::call(' . $fn . ')'; return 'php::call(' . $fn . ')';
@ -2263,6 +2260,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseClassConstFetch(Node\Expr\ClassConstFetch $expr): string protected function parseClassConstFetch(Node\Expr\ClassConstFetch $expr): string
{ {
$class = $this->parseIdentifier($expr->class); $class = $this->parseIdentifier($expr->class);
$class = ($class === 'self' or $class === 'this_') ? $this->class : $class;
$const = $this->parseIdentifier($expr->name); $const = $this->parseIdentifier($expr->name);
return 'php::constant("' . $class . '::' . $const . '")'; return 'php::constant("' . $class . '::' . $const . '")';
} }

@ -9,6 +9,13 @@ use PhpParser\NodeTraverser;
class Translator extends Preprocessor class Translator extends Preprocessor
{ {
protected bool $verbose = false; protected bool $verbose = false;
protected array $unsupportedFunctions = [
'compact',
'extract',
'func_num_args',
'func_get_arg',
'func_get_args',
];
public function __construct(string $rootPath) public function __construct(string $rootPath)
{ {
@ -358,21 +365,26 @@ class Translator extends Preprocessor
return $code; return $code;
} }
protected function genClassStubFile(Node\Stmt\Class_ $class, string $file): void
{
$genStubCmd = PHP_BINARY. ' ' . $this->rootPath . '/bin/gen_stub.php --gen-class-info -f ' . $file;
$output = shell_exec($genStubCmd);
$this->climate->info('generate stub file: ' . $file);
$this->climate->comment($genStubCmd);
$stubFilenameWithoutExtension = str_replace([".stub.php", '.php'], "", $file);
$headerFile = $this->getArgInfoHeaderFile($stubFilenameWithoutExtension, true);
if (!str_starts_with($output, "Saved")) {
$this->fatalError($class, "failed to generate arginfo header file: `$headerFile`, output: $output");
}
$this->localHeaders[] = $headerFile;
$this->stubFileIncluded = true;
}
protected function parseClass(Node\Stmt\Class_ $class): string protected function parseClass(Node\Stmt\Class_ $class): string
{ {
$this->class = $this->parseIdentifier($class->name); $this->class = $this->parseIdentifier($class->name);
if (!$this->stubFileIncluded) { if (!$this->stubFileIncluded) {
$genStubCmd = PHP_BINARY. ' ' . $this->rootPath . '/bin/gen_stub.php -f ' . $this->file; $this->genClassStubFile($class, $this->file);
$output = shell_exec($genStubCmd);
$this->climate->info('generate stub file: ' . $this->file);
$this->climate->comment($genStubCmd);
$stubFilenameWithoutExtension = str_replace([".stub.php", '.php'], "", $this->file);
$headerFile = $this->getArgInfoHeaderFile($stubFilenameWithoutExtension, true);
if (!str_starts_with($output, "Saved")) {
$this->fatalError($class, "failed to generate arginfo header file: `$headerFile`, output: $output");
}
$this->localHeaders[] = $headerFile;
$this->stubFileIncluded = true;
} }
$this->classDef = new ClassDef(); $this->classDef = new ClassDef();

@ -9,9 +9,7 @@ extern php::Var argv;
extern void php_app_init(); extern void php_app_init();
extern void php_app_clean(); extern void php_app_clean();
BEGIN_EXTERN_C() extern zend_module_entry app_module_entry;
extern zend_module_entry *get_module();
END_EXTERN_C()
static void throw_exception(zend_object *ex) { static void throw_exception(zend_object *ex) {
zend_bailout(); zend_bailout();
@ -21,10 +19,14 @@ int main(int cpp_argc, char **cpp_argv) {
php_embed_init(cpp_argc, cpp_argv); php_embed_init(cpp_argc, cpp_argv);
zend_throw_exception_hook = throw_exception; zend_throw_exception_hook = throw_exception;
if (zend_register_module_ex(get_module(), MODULE_TEMPORARY) == NULL) { if (zend_register_module_ex(&app_module_entry, MODULE_TEMPORARY) == NULL) {
zend_error(E_ERROR, "Failed to register module"); zend_error(E_ERROR, "Failed to register module");
} }
if (zend_startup_module_ex(&app_module_entry) == FAILURE) {
zend_error(E_ERROR, "Failed to startup module");
}
int rc = 0; int rc = 0;
#if PPROF_ON #if PPROF_ON
ProfilerStart("myapp.prof"); ProfilerStart("myapp.prof");

@ -104,7 +104,7 @@ foreach ($this->nativeConstants as $name => $constant):
<?php endforeach; ?> <?php endforeach; ?>
} }
static zend_module_entry app_module_entry = { zend_module_entry app_module_entry = {
STANDARD_MODULE_HEADER, STANDARD_MODULE_HEADER,
"app", "app",
ext_functions, ext_functions,

Loading…
Cancel
Save