diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 00000000..71658932 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,66 @@ +setRiskyAllowed(true) + ->setRules([ + '@DoctrineAnnotation' => true, + '@PhpCsFixer' => true, + '@PSR2' => true, + '@Symfony' => true, + 'align_multiline_comment' => ['comment_type' => 'all_multiline'], + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => ['operators' => ['=' => 'align', '=>' => 'align', ]], + 'blank_line_after_namespace' => true, + 'blank_line_before_statement' => ['statements' => ['declare']], + 'class_attributes_separation' => true, + 'concat_space' => ['spacing' => 'one'], + 'constant_case' => ['case' => 'lower'], + 'combine_consecutive_unsets' => true, + 'declare_strict_types' => true, + 'fully_qualified_strict_types' => ['phpdoc_tags' => []], + 'general_phpdoc_annotation_remove' => ['annotations' => ['author']], + 'header_comment' => ['comment_type' => 'PHPDoc', 'header' => $header, 'location' => 'after_open', 'separate' => 'bottom'], + 'increment_style' => ['style' => 'post'], + 'lambda_not_used_import' => false, + 'linebreak_after_opening_tag' => true, + 'list_syntax' => ['syntax' => 'short'], + 'lowercase_static_reference' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'], + 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'allow_unused_params' => true, 'remove_inheritdoc' => false], + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'not_operator_with_space' => false, + 'not_operator_with_successor_space' => false, + 'php_unit_strict' => false, + 'phpdoc_align' => ['align' => 'left'], + 'phpdoc_annotation_without_dot' => false, + 'phpdoc_no_empty_return' => false, + 'phpdoc_types_order' => ['sort_algorithm' => 'none', 'null_adjustment' => 'always_last'], + 'phpdoc_separation' => false, + 'phpdoc_summary' => false, + 'ordered_class_elements' => true, + 'ordered_imports' => ['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha'], + 'ordered_types' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + 'single_line_comment_style' => ['comment_types' => []], + 'single_line_comment_spacing' => false, + 'single_line_empty_body' => false, + 'single_quote' => true, + 'standardize_increment' => false, + 'standardize_not_equals' => true, + 'yoda_style' => ['always_move_variable' => false, 'equal' => false, 'identical' => false], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->exclude(['html', 'vendor']) + ->in(__DIR__) + ) + ->setUsingCache(false); diff --git a/bin/gen_stub.php b/bin/gen_stub.php index aad6eaac..312f7d27 100755 --- a/bin/gen_stub.php +++ b/bin/gen_stub.php @@ -4467,6 +4467,10 @@ class FileInfo { } } + if ($stmt instanceof Stmt\Use_) { + continue; + } + throw new Exception("Unexpected node {$stmt->getType()}"); } if (!empty($conds)) { diff --git a/src/Php/ArgInfo.php b/src/Php/ArgInfo.php index abf142e5..03fcf367 100644 --- a/src/Php/ArgInfo.php +++ b/src/Php/ArgInfo.php @@ -1,10 +1,20 @@ */ public array $methods = []; + /** * @var array */ public array $properties = []; + /** * @var array */ public array $constants = []; + public array $implements = []; + public string $extends = ''; + public bool $requireCtor = false; + public int $flags; public function __construct(string $name, int $flags, string $namespace = '') diff --git a/src/Php/ClassLikeDef.php b/src/Php/ClassLikeDef.php index b66b2777..b2ef43eb 100644 --- a/src/Php/ClassLikeDef.php +++ b/src/Php/ClassLikeDef.php @@ -1,28 +1,38 @@ name = $name; + $this->name = $name; $this->namespace = $namespace; } public function getNamespacedName(bool $symbolic = true): string { - if ('' === $this->namespace) { + if ($this->namespace === '') { return $this->name; } if ($symbolic) { - return str_replace('\\', '_', $this->namespace.'_'.$this->name); + return str_replace('\\', '_', $this->namespace . '_' . $this->name); } - return $this->namespace.'\\\\'.$this->name; + return $this->namespace . '\\\\' . $this->name; } } diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index a3e24ace..71be343b 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -1,4 +1,12 @@ ::quiet_NaN()'; + public const string VALUE_INF = 'std::numeric_limits::infinity()'; + public const string LITERAL_STRINGS = '_literal_strings'; + public const string CLASS_ENTRY_MAP = 'class_entry_map'; + public const string EXPR_VARIABLE = 'Expr_Variable'; + public const string EXPR_NEW = 'Expr_New'; + public const string EXPR_ARRAY_DIM_FETCH = 'Expr_ArrayDimFetch'; + public const string NAMESPACE_SEPARATOR = '__'; + public const string PREFIX = 'php_'; + protected string $phpxDir = '~/workspace/projects/phpx'; + protected string $lang = 'PHP'; + protected string $cppCompiler = 'g++'; + protected array $arguments = []; + protected array $literalStrings = []; + protected int $literalStringIndex = 0; + protected int $tmpVarIndex = 0; + protected int $classIndex = 0; + /** * @var array */ protected array $classMap = []; + protected array $zendTypeMap = [ - 'int' => self::TYPE_INT, - 'float' => self::TYPE_FLOAT, - 'bool' => self::TYPE_BOOL, - 'void' => self::TYPE_VOID, + 'int' => self::TYPE_INT, + 'float' => self::TYPE_FLOAT, + 'bool' => self::TYPE_BOOL, + 'void' => self::TYPE_VOID, 'string' => self::TYPE_STR, - 'array' => self::TYPE_ARRAY, + 'array' => self::TYPE_ARRAY, 'object' => self::TYPE_OBJECT, - 'mixed' => self::TYPE_VAR, + 'mixed' => self::TYPE_VAR, ]; + protected array $globalHeaders = [ 'phpx.h', 'phpx_helper.h', @@ -68,109 +104,159 @@ class CompilerBase extends \PhpAot\Core\Translator 'php_func_decl.h', 'php_global_var_decl.h', ]; + protected array $localHeaders = []; + protected array $nativeFunctions = []; + protected array $internalFunctions = []; + protected array $nativeConstants = []; + protected array $functionDeclInFile = []; + protected array $functionCallInFile = []; + protected array $redoAfterDeclare = []; + protected int $optimizeLevel = 0; + protected string $buildMode = 'bin'; + protected string $cxxflags = ''; + protected string $ldflags = ''; + protected int $floatPrecision = 17; + protected bool $debugInfo = true; + protected bool $noLiteralStrings = false; protected bool $useCppNamespace = false; + protected string $file; + protected string $dir; + /** * 原始值,可能包含 `\\` 多层空间. */ protected string $namespace = ''; + protected string $method = ''; + protected string $function = ''; + protected array $useNamespaces = []; + + protected array $useAliases = []; + protected array $useFunctions = []; + /** * 原始类名,不包含命名空间. */ protected string $class = ''; + protected string $interface = ''; + /** * @var array */ protected array $classes = []; + protected array $interfaces = []; + /** * @var array */ protected array $functions = []; + /** * @var array */ protected array $classesDefineInFile = []; + /** * @var array */ protected array $interfacesDefineInFile = []; + /** * @var array */ protected array $functionDefineInFile = []; + /** * @var array */ protected array $classCeList = []; + protected array $classCeInfo = []; + protected FunctionDef $functionDef; + protected ClassDef $classDef; + protected InterfaceDef $interfaceDef; + protected array $globalVars = [ - '_GET' => self::TYPE_ARRAY, - '_POST' => self::TYPE_ARRAY, - '_COOKIE' => self::TYPE_ARRAY, - '_SERVER' => self::TYPE_ARRAY, - '_FILES' => self::TYPE_ARRAY, + '_GET' => self::TYPE_ARRAY, + '_POST' => self::TYPE_ARRAY, + '_COOKIE' => self::TYPE_ARRAY, + '_SERVER' => self::TYPE_ARRAY, + '_FILES' => self::TYPE_ARRAY, '_SESSION' => self::TYPE_ARRAY, '_REQUEST' => self::TYPE_ARRAY, - 'GLOBALS' => self::TYPE_ARRAY, - 'argc' => self::TYPE_INT, - 'argv' => self::TYPE_ARRAY, + 'GLOBALS' => self::TYPE_ARRAY, + 'argc' => self::TYPE_INT, + 'argv' => self::TYPE_ARRAY, ]; + /** * @var array */ protected array $objects = []; + protected array $localVars = []; + protected array $objectWrappers = []; + protected bool $strictTypes = false; - public const string PREFIX = 'php_'; protected string $rootPath; + protected string $buildDir; + protected int $debugLine = 0; + protected CLImate $climate; + protected array $beforeStmtLines = []; + protected array $afterStmtLines = []; + protected bool $inLoop = false; + /** * 赋值表达式的左值,写操作,右值为读操作. */ protected bool $inAssignExpr = false; + protected bool $stubFile = false; + protected bool $enableProfiler = false; + protected Parser $parser; public function __construct(string $rootPath) { $this->rootPath = $rootPath; - $this->parser = (new ParserFactory())->createForNewestSupportedVersion(); + $this->parser = (new ParserFactory())->createForNewestSupportedVersion(); // $this->prettyPrinter = new PrettyPrinter\Standard; - $this->setBuildDir($rootPath.'/build'); - $climate = new CLImate(); + $this->setBuildDir($rootPath . '/build'); + $climate = new CLImate(); $this->climate = $climate; // $this->noLiteralStrings = $climate->arguments->get('no-literal-strings'); } @@ -180,22 +266,6 @@ class CompilerBase extends \PhpAot\Core\Translator $this->phpxDir = $dir; } - protected function removeCommonPrefix(string $short, string $long): string - { - $len = min(strlen($short), strlen($long)); - $prefixLen = 0; - - for ($i = 0; $i < $len; ++$i) { - if ($short[$i] === $long[$i]) { - ++$prefixLen; - } else { - break; - } - } - - return substr($long, $prefixLen); - } - public function save(string $code, string $file): void { $this->writeFile($file, $code); @@ -212,18 +282,6 @@ class CompilerBase extends \PhpAot\Core\Translator return $node->getType(); } - protected function getVarType(string $name): string - { - if ($this->hasLocalVar($name)) { - return $this->localVars[$name]; - } - if ($this->hasLocalVar($name)) { - return $this->globalVars[$name]; - } - - return self::TYPE_VAR; - } - public function getTypeFromZendType(string $type): string { return $this->zendTypeMap[$type] ?? self::TYPE_VAR; @@ -239,383 +297,24 @@ class CompilerBase extends \PhpAot\Core\Translator return isset($this->objects[$object]); } - protected function resetFunction(): void - { - $this->localVars = []; - $this->arguments = []; - $this->objectWrappers = []; - $this->tmpVarIndex = 0; - $this->inLoop = false; - $this->function = ''; - } - - protected function resetClass(): void - { - $this->class = ''; - $this->interface = ''; - $this->method = ''; - } - - protected function resetFile(): void - { - $this->indentLevel = 0; - $this->strictTypes = false; - $this->classesDefineInFile = []; - $this->interfacesDefineInFile = []; - $this->functionDefineInFile = []; - } - - protected function resetNamespace(): void - { - $this->useNamespaces = []; - $this->useFunctions = []; - $this->namespace = ''; - } - - protected function getFunctionName(FunctionLike $v): string - { - return $this->getNativeName($this->parseIdentifier($v->name), $this->namespace, $this->class); - } - - protected function getPropertyOffset(string $property, string $class, string $namespace = ''): string - { - return $this->getNativeName('property_offset_'.$property, $namespace, $class); - } - - protected function getNativeName(string $fn, string $ns = '', string $class = ''): string - { - $names[] = $this->escapeName($fn); - if ($ns) { - $names[] = $this->escapeNamespace($ns); - } - if ($class) { - $names[] = $this->escapeClass($class); - } - - return implode(self::NAMESPACE_SEPARATOR, array_reverse($names)); - } - - protected function getClassEntryPtr(string $className): string - { - if (isset($this->classMap[$className])) { - $id = $this->classMap[$className]; - } else { - $id = $this->classIndex++; - $this->classMap[$className] = $id; - } - - return 'php_get_class_entry('.$id.', "'.$this->escapeString($className).'")'; - } - - protected function parseFunctionDeclaration(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): FunctionDef - { - // .stub 存根定义 C++ Native 函数,必须设置返回值类型 - if (!$v->returnType && $this->stubFile) { - throw new \Exception('No return type for '.$v->name); - } - $returnType = $v->returnType ? $this->getTypeFromZendType($this->parseIdentifier($v->returnType)) : self::TYPE_VOID; - $functionDef = new FunctionDef($this->parseIdentifier($v->name), $returnType); - $this->functionDef = $functionDef; - $this->parseParams($v->params, $functionDef); - - return $functionDef; - } - - protected function parseFunction(FunctionLike $v): string - { - $this->resetFunction(); - $this->function = $this->parseIdentifier($v->name); - $name = $this->getFunctionName($v); - if (isset($this->nativeFunctions[$name])) { - $this->functionDef = $this->nativeFunctions[$name]; - } else { - $this->nativeFunctions[$name] = $this->parseFunctionDeclaration($v); - if (isset($this->redoAfterDeclare[$name])) { - unset($this->redoAfterDeclare[$name]); - $this->climate->cyan('Received redo request, retrying...'); - throw new RedoException(); - } - } - - if ($this->class) { - $this->arguments['this_'] = self::TYPE_OBJECT; - $this->addLocalVar('this_', self::TYPE_OBJECT); - } else { - $this->functions[$name] = $this->functionDef; - $this->functionDefineInFile[$name] = $this->functionDef; - } - - foreach ($this->functionDef->argInfoList as $argInfo) { - $this->arguments[$argInfo->name] = $argInfo->type; - if (!$this->hasLocalVar($argInfo->name)) { - $this->addLocalVar($argInfo->name, $argInfo->type); - } - } - - if ($v->stmts) { - ++$this->indentLevel; - $stmts = $this->parseStmts($v->stmts); - --$this->indentLevel; - } else { - $stmts = ''; - } - - $functionDeclCode = $this->getReturnType().' '.self::PREFIX.$name.'('; - if ($this->class) { - $functionDeclCode .= self::TYPE_OBJECT.' &this_'; - if ($this->functionDef->params) { - $functionDeclCode .= ', '; - } - } - $functionDeclCode .= $this->functionDef->params.')'; - - $code = $functionDeclCode.' {'.PHP_EOL; - ++$this->indentLevel; - foreach ($this->localVars as $name => $type) { - if (isset($this->arguments[$name])) { - continue; - } - $code .= $this->getIndent().$type.' '.$name.';'.PHP_EOL; - } - $code .= "\n"; - --$this->indentLevel; - $code .= $stmts; - $code .= "}\n"; - - $this->resetFunction(); - - return $code; - } - - protected function writeLog($msg) - { - if ($this->verbose) { - echo $msg.PHP_EOL; - } - } - - protected function parseScalar(Node $expr) + public function parseExpr(mixed $expr) { $type = $expr->getType(); + $this->writeLog('Line ' . $this->getLine($expr) . ': ' . $type); + if ($expr->getLine() === $this->debugLine) { + dump($expr); + } switch ($type) { - case 'Scalar_Int': - return $expr->value.'L'; - case 'Scalar_Float': - return $this->parseScalarFloat($expr); - case 'Scalar_String': - if ($this->noLiteralStrings) { - return '"'.$this->escapeString($expr->value).'"'; - } else { - $index = $this->literalStrings[$expr->value] ?? $this->addLiteralString($expr->value); + case 'Expr_Isset': + return $this->parseIsset($expr); + case 'Expr_Empty': + return $this->parseEmpty($expr); + case 'Expr_Assign': + $result = $this->parseAssign($expr); - return self::LITERAL_STRINGS.'['.$index.']'; - } - // no break - default: - abort($expr); - } - } - - protected function parseIdentifier(Node $expr): string - { - $type = $expr->getType(); - switch ($type) { - case self::EXPR_VARIABLE: - if (is_object($expr->name) and $this->isVarExpr($expr->name)) { - $this->fatalError($expr, 'The `$$` syntax is not supported'); - } - - return $this->escapeVarName($expr->name); - case 'Name': - case 'VarLikeIdentifier': - case 'Identifier': - return $expr->name; - case 'Scalar_Int': - case 'Scalar_Float': - case 'Scalar_String': - return $this->parseScalar($expr); - case 'Expr_ConstFetch': - return $this->parseConstFetch($expr); - case 'Expr_Assign': - case 'Expr_AssignRef': - if (!$this->isVarExpr($expr->var)) { - $this->fatalError($expr, 'When an assignment expression serves as an rvalue, it must be an assignment of a variable'); - } - - return $this->parseExpr($expr); - default: - return $this->parseExpr($expr); - } - } - - protected function parseParams($params, FunctionDef $functionDef): void - { - $list = []; - $functionDef->argCountRequired = count($params); - foreach ($params as $param) { - // .stub 存根定义 C++ Native 函数,必须设置函数的参数类型 - if ($this->stubFile and !$param->type) { - throw new \RuntimeException('No type for '.$this->parseIdentifier($param->var)); - } - $name = $this->parseIdentifier($param->var); - $type = $this->parseParameterType($param, $name); - $list[] = $type.' '.$name; - $argInfo = new ArgInfo(); - $argInfo->name = $name; - $argInfo->type = $type; - if (isset($param->default)) { - $functionDef->argCountRequired = count($list) - 1; - $argInfo->default = $this->parseIdentifier($param->default); - } - $functionDef->argInfoList[] = $argInfo; - } - $functionDef->params = implode(', ', $list); - } - - protected function getComment(Node\Stmt $v, string $class): string - { - if ('Stmt_Expression' == $class) { - $class = 'Stmt_Expression('.$v->expr->getType().')'; - } - - return $this->getIndent().'// '.$class.' ['.$v->getStartLine().':'.$v->getEndLine().']'; - } - - /** - * 在 for/foreach 等包含子语句的语句,之前检查当前待添加的代码是否为空, - * 如果不为空,需要将语句追加到 {} 作用域符号之前. - */ - protected function parseBeforeStmtLines(): string - { - if ($this->beforeStmtLines) { - $code = implode(PHP_EOL, $this->beforeStmtLines); - $this->beforeStmtLines = []; - - return $code.PHP_EOL; - } else { - return ''; - } - } - - protected function parseStmts(array $stmts): string - { - $lines = []; - $inLoopTop = $this->inLoop; - foreach ($stmts as $v) { - $class = $v->getType(); - $this->beforeStmtLines = []; - $this->afterStmtLines = []; - $result = ''; - $this->writeLog('Line '.$this->getLine($v).': '.$class); - - $lines[] = $this->getComment($v, $class); - switch ($class) { - case 'Stmt_Expression': - $result = $this->parseExpr($v->expr).';'; - break; - case 'Stmt_Echo': - $result = $this->parseEcho($v); - break; - case 'Stmt_Return': - $result = $this->parseReturn($v).';'; - break; - case 'Stmt_For': - $this->inLoop = true; - $result = $this->parseFor($v); - $this->inLoop = $inLoopTop; - break; - case 'Stmt_Foreach': - $this->inLoop = true; - $result = $this->parseForeach($v); - $this->inLoop = $inLoopTop; - break; - case 'Stmt_Switch': - $this->inLoop = true; - $result = $this->parseSwitch($v); - $this->inLoop = $inLoopTop; - break; - case 'Stmt_While': - $this->inLoop = true; - $result = $this->parseWhile($v); - $this->inLoop = $inLoopTop; - break; - case 'Stmt_Do': - $this->inLoop = true; - $result = $this->parseDo($v); - $this->inLoop = $inLoopTop; - break; - case 'Stmt_If': - $result = $this->parseIf($v); - break; - case 'Stmt_Break': - $result = $this->parseBreak($v); - break; - case 'Stmt_Goto': - $result = $this->parseGoto($v); - break; - case 'Stmt_Label': - $result = $this->parseLabel($v); - break; - case 'Stmt_Continue': - $result = $this->parseContinue($v); - break; - case 'Stmt_Nop': - break; - case 'Stmt_Global': - $result = $this->parseGlobal($v); - break; - case 'Stmt_Static': - $result = $this->parseStatic($v); - break; - case 'Stmt_Unset': - $result = $this->parseUnset($v); - break; - case 'Stmt_TryCatch': - $result = $this->parseTryCatch($v); - break; - case 'Stmt_Class': - $this->fatalError($v, 'Cannot declare class in function'); - break; - default: - abort($v); - } - $lines = array_merge($lines, $this->beforeStmtLines); - $this->beforeStmtLines = []; - if ($result) { - $lines[] = $result; - } - if ($this->afterStmtLines) { - $lines = array_merge($lines, $this->afterStmtLines); - $this->afterStmtLines = []; - } - } - - $code = ''; - foreach ($lines as $line) { - $code .= $this->getIndent().$line.PHP_EOL; - } - - return $code; - } - - public function parseExpr(mixed $expr) - { - $type = $expr->getType(); - $this->writeLog('Line '.$this->getLine($expr).': '.$type); - if ($expr->getLine() === $this->debugLine) { - dump($expr); - } - switch ($type) { - case 'Expr_Isset': - return $this->parseIsset($expr); - case 'Expr_Empty': - return $this->parseEmpty($expr); - case 'Expr_Assign': - $result = $this->parseAssign($expr); - - return $result; - case 'Expr_AssignRef': - $result = $this->parseAssignRef($expr); + return $result; + case 'Expr_AssignRef': + $result = $this->parseAssignRef($expr); return $result; case 'Expr_Print': @@ -779,19 +478,483 @@ class CompilerBase extends \PhpAot\Core\Translator } } - private function parseAssignPropertyArrayDim(Node $left, Node $right): string + public function isClosedCall($expr, $call): bool + { + if ($call === '') { + if (!str_starts_with($expr, '(')) { + return false; + } + $startPos = 0; + } else { + if (!str_starts_with($expr, $call . '(')) { + return false; + } + $startPos = strlen($call); + } + + $bracketCount = 0; + $length = strlen($expr); + + for ($i = $startPos; $i < $length; $i++) { + $char = $expr[$i]; + if ($char === '(') { + $bracketCount++; + } elseif ($char === ')') { + $bracketCount--; + if ($bracketCount === 0) { + return $i === $length - 1; + } + } + } + + return false; + } + + public function stop(string $string): void + { + $this->climate->red($string . "\n"); + exit(1); + } + + public function genTmpVarName(): string + { + return 'tmp_var_' . $this->tmpVarIndex++; + } + + public function writeFile(string $file, string $content): void + { + $dir = dirname($file); + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + if (!file_put_contents($file, $content)) { + throw new \RuntimeException('Can not write file: ' . $file); + } + } + + public function getIncludeDir(): string + { + return $this->getBuildDir() . '/include'; + } + + public function getBuildDir(): string + { + return $this->buildDir; + } + + protected function removeCommonPrefix(string $short, string $long): string + { + $len = min(strlen($short), strlen($long)); + $prefixLen = 0; + + for ($i = 0; $i < $len; $i++) { + if ($short[$i] === $long[$i]) { + $prefixLen++; + } else { + break; + } + } + + return substr($long, $prefixLen); + } + + protected function getVarType(string $name): string + { + if ($this->hasLocalVar($name)) { + return $this->localVars[$name]; + } + if ($this->hasLocalVar($name)) { + return $this->globalVars[$name]; + } + + return self::TYPE_VAR; + } + + protected function resetFunction(): void + { + $this->localVars = []; + $this->arguments = []; + $this->objectWrappers = []; + $this->tmpVarIndex = 0; + $this->inLoop = false; + $this->function = ''; + } + + protected function resetClass(): void + { + $this->class = ''; + $this->interface = ''; + $this->method = ''; + } + + protected function resetFile(): void + { + $this->indentLevel = 0; + $this->strictTypes = false; + $this->classesDefineInFile = []; + $this->interfacesDefineInFile = []; + $this->functionDefineInFile = []; + } + + protected function resetNamespace(): void + { + $this->useNamespaces = []; + $this->useFunctions = []; + $this->namespace = ''; + } + + protected function getFunctionName(FunctionLike $v): string + { + return $this->getNativeName($this->parseIdentifier($v->name), $this->namespace, $this->class); + } + + protected function getNamespacedClassName(string $class): string + { + $ns2 = explode('\\', trim($class, '\\')); + + if (isset($this->useAliases[$ns2[0]])) { + $ns = '\\' . $this->useAliases[$ns2[0]]; + _return: + if (count($ns2) > 1) { + $ns .= '\\' . implode('\\', array_slice($ns2, 1)); + } + + return $ns; + } + + foreach ($this->useNamespaces as $useNamespace) { + $ns1 = explode('\\', trim($useNamespace, '\\')); + if ($ns1[array_key_last($ns1)] === $ns2[0]) { + $ns = '\\' . implode('\\', $ns1); + goto _return; + } + } + + $currentNamespace = $this->namespace; + if (!empty($currentNamespace)) { + return '\\' . trim($currentNamespace, '\\') . '\\' . $class; + } + + return '\\' . $class; + } + + protected function getPropertyOffset(string $property, string $class, string $namespace = ''): string + { + return $this->getNativeName('property_offset_' . $property, $namespace, $class); + } + + protected function getNativeName(string $fn, string $ns = '', string $class = ''): string + { + $names[] = $this->escapeName($fn); + if ($ns) { + $names[] = $this->escapeNamespace($ns); + } + if ($class) { + $names[] = $this->escapeClass($class); + } + + return implode(self::NAMESPACE_SEPARATOR, array_reverse($names)); + } + + protected function getClassEntryPtr(string $className): string + { + if (isset($this->classMap[$className])) { + $id = $this->classMap[$className]; + } else { + $id = $this->classIndex++; + $this->classMap[$className] = $id; + } + + return 'php_get_class_entry(' . $id . ', "' . $this->escapeString($className) . '")'; + } + + protected function parseFunctionDeclaration(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): FunctionDef + { + // .stub 存根定义 C++ Native 函数,必须设置返回值类型 + if (!$v->returnType && $this->stubFile) { + throw new \Exception('No return type for ' . $v->name); + } + $returnType = $v->returnType ? $this->getTypeFromZendType($this->parseIdentifier($v->returnType)) : self::TYPE_VOID; + $functionDef = new FunctionDef($this->parseIdentifier($v->name), $returnType); + $this->functionDef = $functionDef; + $this->parseParams($v->params, $functionDef); + + return $functionDef; + } + + protected function parseFunction(FunctionLike $v): string + { + $this->resetFunction(); + $this->function = $this->parseIdentifier($v->name); + $name = $this->getFunctionName($v); + if (isset($this->nativeFunctions[$name])) { + $this->functionDef = $this->nativeFunctions[$name]; + } else { + $this->nativeFunctions[$name] = $this->parseFunctionDeclaration($v); + if (isset($this->redoAfterDeclare[$name])) { + unset($this->redoAfterDeclare[$name]); + $this->climate->cyan('Received redo request, retrying...'); + throw new RedoException(); + } + } + + if ($this->class) { + $this->arguments['this_'] = self::TYPE_OBJECT; + $this->addLocalVar('this_', self::TYPE_OBJECT); + } else { + $this->functions[$name] = $this->functionDef; + $this->functionDefineInFile[$name] = $this->functionDef; + } + + foreach ($this->functionDef->argInfoList as $argInfo) { + $this->arguments[$argInfo->name] = $argInfo->type; + if (!$this->hasLocalVar($argInfo->name)) { + $this->addLocalVar($argInfo->name, $argInfo->type); + } + } + + if ($v->stmts) { + $this->indentLevel++; + $stmts = $this->parseStmts($v->stmts); + $this->indentLevel--; + } else { + $stmts = ''; + } + + $functionDeclCode = $this->getReturnType() . ' ' . self::PREFIX . $name . '('; + if ($this->class) { + $functionDeclCode .= self::TYPE_OBJECT . ' &this_'; + if ($this->functionDef->params) { + $functionDeclCode .= ', '; + } + } + $functionDeclCode .= $this->functionDef->params . ')'; + + $code = $functionDeclCode . ' {' . PHP_EOL; + $this->indentLevel++; + foreach ($this->localVars as $name => $type) { + if (isset($this->arguments[$name])) { + continue; + } + $code .= $this->getIndent() . $type . ' ' . $name . ';' . PHP_EOL; + } + $code .= "\n"; + $this->indentLevel--; + $code .= $stmts; + $code .= "}\n"; + + $this->resetFunction(); + + return $code; + } + + protected function writeLog($msg) + { + if ($this->verbose) { + echo $msg . PHP_EOL; + } + } + + protected function parseScalar(Node $expr) + { + $type = $expr->getType(); + switch ($type) { + case 'Scalar_Int': + return $expr->value . 'L'; + case 'Scalar_Float': + return $this->parseScalarFloat($expr); + case 'Scalar_String': + if ($this->noLiteralStrings) { + return '"' . $this->escapeString($expr->value) . '"'; + } + $index = $this->literalStrings[$expr->value] ?? $this->addLiteralString($expr->value); + + return self::LITERAL_STRINGS . '[' . $index . ']'; + + default: + abort($expr); + } + } + + protected function parseIdentifier(Node $expr): string + { + $type = $expr->getType(); + switch ($type) { + case self::EXPR_VARIABLE: + if (is_object($expr->name) and $this->isVarExpr($expr->name)) { + $this->fatalError($expr, 'The `$$` syntax is not supported'); + } + + return $this->escapeVarName($expr->name); + case 'Name': + case 'VarLikeIdentifier': + case 'Identifier': + return $expr->name; + case 'Scalar_Int': + case 'Scalar_Float': + case 'Scalar_String': + return $this->parseScalar($expr); + case 'Expr_ConstFetch': + return $this->parseConstFetch($expr); + case 'Expr_Assign': + case 'Expr_AssignRef': + if (!$this->isVarExpr($expr->var)) { + $this->fatalError($expr, 'When an assignment expression serves as an rvalue, it must be an assignment of a variable'); + } + + return $this->parseExpr($expr); + default: + return $this->parseExpr($expr); + } + } + + protected function parseParams($params, FunctionDef $functionDef): void + { + $list = []; + $functionDef->argCountRequired = count($params); + foreach ($params as $param) { + // .stub 存根定义 C++ Native 函数,必须设置函数的参数类型 + if ($this->stubFile and !$param->type) { + throw new \RuntimeException('No type for ' . $this->parseIdentifier($param->var)); + } + $name = $this->parseIdentifier($param->var); + $type = $this->parseParameterType($param, $name); + $list[] = $type . ' ' . $name; + $argInfo = new ArgInfo(); + $argInfo->name = $name; + $argInfo->type = $type; + if (isset($param->default)) { + $functionDef->argCountRequired = count($list) - 1; + $argInfo->default = $this->parseIdentifier($param->default); + } + $functionDef->argInfoList[] = $argInfo; + } + $functionDef->params = implode(', ', $list); + } + + protected function getComment(Node\Stmt $v, string $class): string + { + if ($class == 'Stmt_Expression') { + $class = 'Stmt_Expression(' . $v->expr->getType() . ')'; + } + + return $this->getIndent() . '// ' . $class . ' [' . $v->getStartLine() . ':' . $v->getEndLine() . ']'; + } + + /** + * 在 for/foreach 等包含子语句的语句,之前检查当前待添加的代码是否为空, + * 如果不为空,需要将语句追加到 {} 作用域符号之前. + */ + protected function parseBeforeStmtLines(): string { - $obj = $this->parseIdentifier($left->var->var); - $propName = $this->identifierToStr($left->var->name); - $code = ''; - $value = $this->trimBrackets($this->parseExpr($right)); - if (null === $left->dim) { - return $code."$obj.appendArrayProperty($propName, $value)"; - } else { - $dim = $this->trimBrackets($this->parseIdentifier($left->dim)); + if ($this->beforeStmtLines) { + $code = implode(PHP_EOL, $this->beforeStmtLines); + $this->beforeStmtLines = []; + + return $code . PHP_EOL; + } + return ''; + } + + protected function parseStmts(array $stmts): string + { + $lines = []; + $inLoopTop = $this->inLoop; + foreach ($stmts as $v) { + $class = $v->getType(); + $this->beforeStmtLines = []; + $this->afterStmtLines = []; + $result = ''; + $this->writeLog('Line ' . $this->getLine($v) . ': ' . $class); + + $lines[] = $this->getComment($v, $class); + switch ($class) { + case 'Stmt_Expression': + $result = $this->parseExpr($v->expr) . ';'; + break; + case 'Stmt_Echo': + $result = $this->parseEcho($v); + break; + case 'Stmt_Return': + $result = $this->parseReturn($v) . ';'; + break; + case 'Stmt_For': + $this->inLoop = true; + $result = $this->parseFor($v); + $this->inLoop = $inLoopTop; + break; + case 'Stmt_Foreach': + $this->inLoop = true; + $result = $this->parseForeach($v); + $this->inLoop = $inLoopTop; + break; + case 'Stmt_Switch': + $this->inLoop = true; + $result = $this->parseSwitch($v); + $this->inLoop = $inLoopTop; + break; + case 'Stmt_While': + $this->inLoop = true; + $result = $this->parseWhile($v); + $this->inLoop = $inLoopTop; + break; + case 'Stmt_Do': + $this->inLoop = true; + $result = $this->parseDo($v); + $this->inLoop = $inLoopTop; + break; + case 'Stmt_If': + $result = $this->parseIf($v); + break; + case 'Stmt_Break': + $result = $this->parseBreak($v); + break; + case 'Stmt_Goto': + $result = $this->parseGoto($v); + break; + case 'Stmt_Label': + $result = $this->parseLabel($v); + break; + case 'Stmt_Continue': + $result = $this->parseContinue($v); + break; + case 'Stmt_Nop': + break; + case 'Stmt_Global': + $result = $this->parseGlobal($v); + break; + case 'Stmt_Static': + $result = $this->parseStatic($v); + break; + case 'Stmt_Unset': + $result = $this->parseUnset($v); + break; + case 'Stmt_TryCatch': + $result = $this->parseTryCatch($v); + break; + case 'Stmt_Class': + $this->fatalError($v, 'Cannot declare class in function'); + break; + default: + abort($v); + } + $lines = array_merge($lines, $this->beforeStmtLines); + $this->beforeStmtLines = []; + if ($result) { + $lines[] = $result; + } + if ($this->afterStmtLines) { + $lines = array_merge($lines, $this->afterStmtLines); + $this->afterStmtLines = []; + } + } - return $code."$obj.updateArrayProperty($propName, $dim, $value)"; + $code = ''; + foreach ($lines as $line) { + $code .= $this->getIndent() . $line . PHP_EOL; } + + return $code; } protected function parseAssignArrayDim(Node $left, Node $right): string @@ -799,32 +962,31 @@ class CompilerBase extends \PhpAot\Core\Translator if ($this->isPropertyFetch($left)) { return $this->parseAssignPropertyArrayDim($left, $right); } - $oriInAssignExpr = $this->inAssignExpr; + $oriInAssignExpr = $this->inAssignExpr; $this->inAssignExpr = true; - $array = $this->parseIdentifier($left->var); + $array = $this->parseIdentifier($left->var); $this->inAssignExpr = $oriInAssignExpr; - $code = ''; + $code = ''; // 这是 PHP 的初始化+赋值写法,需要先创建数组 if (!$this->hasVar($array) and $this->isVarExpr($left->var)) { $this->addLocalVar($array, self::TYPE_ARRAY); } $value = $this->trimBrackets($this->parseExpr($right)); - if (null === $left->dim) { - return $code."$array.offsetSet(php::null, $value)"; - } else { - $dim = $this->trimBrackets($this->parseIdentifier($left->dim)); - - return $code."$array.offsetSet($dim, $value)"; + if ($left->dim === null) { + return $code . "{$array}.offsetSet(php::null, {$value})"; } + $dim = $this->trimBrackets($this->parseIdentifier($left->dim)); + + return $code . "{$array}.offsetSet({$dim}, {$value})"; } protected function parseAssignPropertyFetch(Node $left, Node $right): string { - $array = $this->parseIdentifier($left->var); + $array = $this->parseIdentifier($left->var); $propName = $this->identifierToStr($left->name); - return "$array.setProperty($propName, ".$this->trimBrackets($this->parseExpr($right)).')'; + return "{$array}.setProperty({$propName}, " . $this->trimBrackets($this->parseExpr($right)) . ')'; } protected function parseRightAssociativeAssign(NodeAbstract $left, Node\Expr\Assign $right): string @@ -840,12 +1002,12 @@ class CompilerBase extends \PhpAot\Core\Translator $checkVarFn($left); $chain[] = $left; - $next = $right; - while ('Expr_Assign' === $next->getType()) { + $next = $right; + while ($next->getType() === 'Expr_Assign') { $var = $next->var; $checkVarFn($var); $chain[] = $var; - $next = $next->expr; + $next = $next->expr; } $tmpVar = $this->genTmpVarName(); @@ -853,32 +1015,34 @@ class CompilerBase extends \PhpAot\Core\Translator // 翻转赋值链 $chain = array_reverse($chain); - $list = []; + $list = []; - $list[] = $this->getIndent().$tmpVar.' = '.$this->parseExpr($next); - $right = new Variable($tmpVar); + $list[] = $this->getIndent() . $tmpVar . ' = ' . $this->parseExpr($next); + $right = new Variable($tmpVar); foreach ($chain as $var) { - $list[] = $this->getIndent().$this->parseFinallyAssign($var, $right); + $list[] = $this->getIndent() . $this->parseFinallyAssign($var, $right); } - return implode(";\n".$this->getIndent(), $list); + return implode(";\n" . $this->getIndent(), $list); } protected function parseAssign(Node\Expr\Assign $v): string { - $left = $v->var; + $left = $v->var; $right = $v->expr; - if ('Expr_Assign' === $right->getType()) { + if ($right->getType() === 'Expr_Assign') { return $this->parseRightAssociativeAssign($left, $right); - } elseif (self::EXPR_ARRAY_DIM_FETCH === $left->getType()) { + } + if ($left->getType() === self::EXPR_ARRAY_DIM_FETCH) { return $this->parseAssignArrayDim($left, $right); - } elseif ('Expr_StaticPropertyFetch' === $left->getType()) { - $class = $this->identifierToStr($left->class); + } + if ($left->getType() === 'Expr_StaticPropertyFetch') { + $class = $this->identifierToStr($left->class); $propName = $this->identifierToStr($left->name); - $value = $this->trimBrackets($this->parseExpr($right)); + $value = $this->trimBrackets($this->parseExpr($right)); - return "php::setStaticProperty($class, $propName, $value)"; + return "php::setStaticProperty({$class}, {$propName}, {$value})"; } return $this->parseFinallyAssign($left, $right); @@ -888,11 +1052,11 @@ class CompilerBase extends \PhpAot\Core\Translator { if ($left instanceof Node\Expr\List_) { $items = $left->items; - $code = '{'; - ++$this->indentLevel; + $code = '{'; + $this->indentLevel++; $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, self::TYPE_VAR); - $code .= $this->getIndent().$tmpVar.' = '.$this->parseExpr($right).'; '; + $code .= $this->getIndent() . $tmpVar . ' = ' . $this->parseExpr($right) . '; '; foreach ($items as $k => $item) { if (!$item) { continue; @@ -902,20 +1066,20 @@ class CompilerBase extends \PhpAot\Core\Translator if (!$this->hasVar($var)) { $this->addLocalVar($var, self::TYPE_VAR); } - $code .= "$var = $tmpVar.item($k); "; + $code .= "{$var} = {$tmpVar}.item({$k}); "; } else { abort($item); } } - --$this->indentLevel; + $this->indentLevel--; - return $code.'}'; + return $code . '}'; } $this->inAssignExpr = true; - $var = $this->parseIdentifier($left); + $var = $this->parseIdentifier($left); $this->inAssignExpr = false; - if ('this_' === $var) { + if ($var === 'this_') { $this->fatalError($left, 'Cannot re-assign $this'); } @@ -925,23 +1089,23 @@ class CompilerBase extends \PhpAot\Core\Translator if ($this->isVarExpr($left)) { // 类型推断,获取对象的类名 if ($this->isNewExpr($right) and $this->isNameExpr($right->class)) { - $class = $this->parseIdentifier($right->class); + $class = $this->parseIdentifier($right->class); $this->objects[$var] = $class; - $type = self::TYPE_OBJECT; + $type = self::TYPE_OBJECT; } elseif ($this->isFuncCallExpr($right) and $this->isNameExpr($right->name)) { $fn = $this->parseIdentifier($right->name); - if (2 === count($right->args) and 'objval' === $fn and $this->isScalarString($right->args[1]->value)) { + if (count($right->args) === 2 and $fn === 'objval' and $this->isScalarString($right->args[1]->value)) { $this->objects[$var] = $this->parseIdentifier($right->args[1]->value); - $type = self::TYPE_OBJECT; - } elseif (1 === count($right->args) and 'any' === $fn) { + $type = self::TYPE_OBJECT; + } elseif (count($right->args) === 1 and $fn === 'any') { $type = self::TYPE_VAR; if (!$this->hasVar($var)) { $this->addLocalVar($var, $type); } - return $var.' = '.$this->parseIdentifier($right->args[0]->value); + return $var . ' = ' . $this->parseIdentifier($right->args[0]->value); } else { - $type = self::TYPE_VOID === $type ? self::TYPE_VAR : $type; + $type = $type === self::TYPE_VOID ? self::TYPE_VAR : $type; } } @@ -952,7 +1116,7 @@ class CompilerBase extends \PhpAot\Core\Translator $var = $this->parsePropertyFetch($left, true); } - return $var.' = '.$this->convertExprType($expr, $this->detectExprType($left), $this->detectExprType($right)); + return $var . ' = ' . $this->convertExprType($expr, $this->detectExprType($left), $this->detectExprType($right)); } protected function parseEcho(mixed $v): string @@ -961,26 +1125,26 @@ class CompilerBase extends \PhpAot\Core\Translator if ($expr instanceof Node\Expr\Assign) { $this->fatalError($expr, 'Cannot echo assign expression'); } else { - $lines[] = 'php::echo('.$this->parseExpr($expr).');'; + $lines[] = 'php::echo(' . $this->parseExpr($expr) . ');'; } } - return implode("\n".$this->getIndent(), $lines); + return implode("\n" . $this->getIndent(), $lines); } protected function isFloatStr(string $str): bool { - return false !== filter_var($str, FILTER_VALIDATE_FLOAT); + return filter_var($str, FILTER_VALIDATE_FLOAT) !== false; } protected function isIntStr(string $str): bool { - return false !== filter_var($str, FILTER_VALIDATE_INT); + return filter_var($str, FILTER_VALIDATE_INT) !== false; } protected function isBoolStr(string $str): bool { - return 'true' === $str || 'false' === $str; + return $str === 'true' || $str === 'false'; } protected function isInternalFunction(string $fname): bool @@ -990,25 +1154,28 @@ class CompilerBase extends \PhpAot\Core\Translator protected function isAssignOpConcat(string $op): bool { - return '.=' === $op; + return $op === '.='; } protected function isAssignOpPow(string $op): bool { - return '**=' === $op; + return $op === '**='; } /** * 尽可能转为数字,优先级 浮点 > 整数 > 字符串. + * @param mixed $expr */ protected function parseNumericIdentifier($expr) { - if ('Scalar_String' === $expr->getType()) { + if ($expr->getType() === 'Scalar_String') { if ($this->isFloatStr($expr->value)) { return floatval($expr->value); - } elseif ($this->isIntStr($expr->value)) { + } + if ($this->isIntStr($expr->value)) { return intval($expr->value); - } elseif ('0' === $expr->value) { + } + if ($expr->value === '0') { return 0; } } @@ -1019,27 +1186,27 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseBinaryOp($left, $right, $op): string { // 运算逻辑,优先转为数字 - $leftExpr = $this->parseNumericIdentifier($left); + $leftExpr = $this->parseNumericIdentifier($left); $rightExpr = $this->parseNumericIdentifier($right); - $leftType = $this->detectExprType($left); + $leftType = $this->detectExprType($left); $rightType = $this->detectExprType($right); - if (self::TYPE_FLOAT === $leftType) { + if ($leftType === self::TYPE_FLOAT) { $rightExpr = $this->convertExprType($rightExpr, self::TYPE_FLOAT, $rightType); - } elseif (self::TYPE_FLOAT === $rightType) { + } elseif ($rightType === self::TYPE_FLOAT) { $leftExpr = $this->convertExprType($leftExpr, $leftType, self::TYPE_FLOAT); - } elseif (self::TYPE_INT === $leftType) { + } elseif ($leftType === self::TYPE_INT) { $rightExpr = $this->convertExprType($rightExpr, self::TYPE_INT, $rightType); - } elseif (self::TYPE_INT === $rightType) { + } elseif ($rightType === self::TYPE_INT) { $leftExpr = $this->convertExprType($leftExpr, $leftType, self::TYPE_INT); } - if ('%' === $op and !(self::TYPE_INT === $leftType and self::TYPE_INT === $rightType)) { - return 'php::math::mod('.$leftExpr.', '.$rightExpr.')'; + if ($op === '%' and !($leftType === self::TYPE_INT and $rightType === self::TYPE_INT)) { + return 'php::math::mod(' . $leftExpr . ', ' . $rightExpr . ')'; } - return '(('.$leftExpr.') '.$op.' ('.$rightExpr.'))'; + return '((' . $leftExpr . ') ' . $op . ' (' . $rightExpr . '))'; } protected function parseBinaryOpPlus(mixed $expr): string @@ -1049,16 +1216,16 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseReturn(mixed $v): string { - if (null === $v->expr) { + if ($v->expr === null) { return 'return;'; } // 实际函数的返回值 $type = $this->detectExprType($v->expr); $expr = $this->parseExpr($v->expr); // 函数定义时没有声明返回值,但函数体中有返回值,修改为实际的返回值类型 - if ('void' === $this->getReturnType()) { + if ($this->getReturnType() === 'void') { $this->resetReturnType($type); - } elseif (self::TYPE_VAR !== $this->getReturnType() and $this->getReturnType() !== $type) { + } elseif ($this->getReturnType() !== self::TYPE_VAR and $this->getReturnType() !== $type) { // 返回值类型不一致,说明存在多种类型的返回值,修改为 var 表示 any $this->resetReturnType(self::TYPE_VAR); } @@ -1069,10 +1236,10 @@ class CompilerBase extends \PhpAot\Core\Translator $tmpVar = $this->genTmpVarName(); // 必须提前声明变量,否则在末尾声明并 return 可能会被 gcc 优化掉 $this->addLocalVar($tmpVar, $type); - $code = $tmpVar.' = '.$exprCode.';'.PHP_EOL; - $code .= $this->getIndent().'return '.$tmpVar; + $code = $tmpVar . ' = ' . $exprCode . ';' . PHP_EOL; + $code .= $this->getIndent() . 'return ' . $tmpVar; } else { - $code = 'return '.$exprCode; + $code = 'return ' . $exprCode; } return $code; @@ -1090,7 +1257,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function addLiteralString(string $value): int { - $index = $this->literalStringIndex++; + $index = $this->literalStringIndex++; $this->literalStrings[$value] = $index; return $index; @@ -1154,12 +1321,12 @@ class CompilerBase extends \PhpAot\Core\Translator case 'Expr_BinaryOp_BitwiseOr': case 'Expr_BinaryOp_BitwiseXor': case 'Expr_BinaryOp_BooleanAnd': - $leftType = $this->detectExprType($expr->left); + $leftType = $this->detectExprType($expr->left); $rightType = $this->detectExprType($expr->right); - if (self::TYPE_FLOAT === $leftType || self::TYPE_FLOAT === $rightType) { + if ($leftType === self::TYPE_FLOAT || $rightType === self::TYPE_FLOAT) { return self::TYPE_FLOAT; } - if (self::TYPE_INT === $leftType || self::TYPE_INT === $rightType) { + if ($leftType === self::TYPE_INT || $rightType === self::TYPE_INT) { return self::TYPE_INT; } break; @@ -1191,8 +1358,8 @@ class CompilerBase extends \PhpAot\Core\Translator { $items = $node->items; // 优化代码风格,空数组直接返回{},否则会产生一些空洞内容 - if (0 === count($items)) { - return self::TYPE_ARRAY.'{}'; + if (count($items) === 0) { + return self::TYPE_ARRAY . '{}'; } $assocArray = false; @@ -1204,35 +1371,35 @@ class CompilerBase extends \PhpAot\Core\Translator } $list = []; - ++$this->indentLevel; + $this->indentLevel++; foreach ($items as $item) { $value = $this->parseIdentifier($item->value); if ($assocArray) { // TODO 混杂模式数组赋值 $key = $item->key ? $this->parseIdentifier($item->key) : 'php::null'; if (str_starts_with($key, self::LITERAL_STRINGS)) { - $key = "$key.toStdString()"; - } elseif ('0L' === $key) { + $key = "{$key}.toStdString()"; + } elseif ($key === '0L') { $key = 'php::zero'; } - $list[] = $this->getIndent().'{ '.$key.', '. - self::TYPE_VAR.'('.$value.') }'; + $list[] = $this->getIndent() . '{ ' . $key . ', ' . + self::TYPE_VAR . '(' . $value . ') }'; } else { - $list[] = $this->getIndent().self::TYPE_VAR.'('.$value.')'; + $list[] = $this->getIndent() . self::TYPE_VAR . '(' . $value . ')'; } } - --$this->indentLevel; + $this->indentLevel--; - return self::TYPE_ARRAY.'{'.PHP_EOL. - implode(', '.PHP_EOL, $list).PHP_EOL. - $this->getIndent(). + return self::TYPE_ARRAY . '{' . PHP_EOL . + implode(', ' . PHP_EOL, $list) . PHP_EOL . + $this->getIndent() . '}'; } protected function parseParameterType(Node\Param $param, string $var): string { $type = $param->type; - if (null == $type) { + if ($type == null) { return self::TYPE_VAR; } if ($type instanceof NullableType or $type instanceof UnionType) { @@ -1268,12 +1435,12 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseIncludes(): string { $list = [ - $this->phpxDir.'/include', - $this->getBuildDir().'/include', + $this->phpxDir . '/include', + $this->getBuildDir() . '/include', ]; $out = '$(php-config --includes) '; foreach ($list as $li) { - $out .= '-I '.$li.' '; + $out .= '-I ' . $li . ' '; } return $out; @@ -1283,11 +1450,11 @@ class CompilerBase extends \PhpAot\Core\Translator { $list = [ '$(php-config --prefix)/lib', - $this->phpxDir.'/lib', + $this->phpxDir . '/lib', ]; $out = ''; foreach ($list as $li) { - $out .= '-L '.$li.' '; + $out .= '-L ' . $li . ' '; } return $out; @@ -1296,12 +1463,12 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseLibs(): string { $list = ['phpx']; - if ('bin' === $this->buildMode) { + if ($this->buildMode === 'bin') { $list[] = 'php'; } $out = ''; foreach ($list as $li) { - $out .= '-l'.$li.' '; + $out .= '-l' . $li . ' '; } return $out; @@ -1309,8 +1476,8 @@ class CompilerBase extends \PhpAot\Core\Translator protected function addCompilationOption(string &$cmd, bool $link): void { - $cmd .= ' '.$this->parseIncludes(); - $cmd .= ' -O'.$this->optimizeLevel; + $cmd .= ' ' . $this->parseIncludes(); + $cmd .= ' -O' . $this->optimizeLevel; $cmd .= ' -g'; $cmd .= ' -Wall'; if ($this->enableProfiler) { @@ -1318,7 +1485,7 @@ class CompilerBase extends \PhpAot\Core\Translator $cmd .= ' -DPPROF_ON=1'; } - if ('ext' === $this->buildMode) { + if ($this->buildMode === 'ext') { if ($link) { $cmd .= ' -shared'; } else { @@ -1327,44 +1494,44 @@ class CompilerBase extends \PhpAot\Core\Translator } if ($link) { - $cmd .= ' '.$this->parseLdflags(); - $cmd .= ' '.$this->parseLibs(); + $cmd .= ' ' . $this->parseLdflags(); + $cmd .= ' ' . $this->parseLibs(); if ($this->ldflags) { - $cmd .= ' '.$this->ldflags; + $cmd .= ' ' . $this->ldflags; } } else { if ($this->cxxflags) { - $cmd .= ' '.$this->cxxflags; + $cmd .= ' ' . $this->cxxflags; } } } protected function parseBinaryOpConcat(mixed $expr): string { - $left = $this->parseIdentifier($expr->left); + $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); - return 'php::concat('.$left.', '.$right.')'; + return 'php::concat(' . $left . ', ' . $right . ')'; } protected function parseFor(mixed $v): string { - $init = $v->init; - $cond = $v->cond; - $loop = $v->loop; + $init = $v->init; + $cond = $v->cond; + $loop = $v->loop; $stmts = $v->stmts; - $code = ''; + $code = ''; $list_expr = []; foreach ($init as $expr) { $list_expr[] = $this->parseExpr($expr); } $list_expr[] = ''; - $code .= implode(";\n".$this->getIndent(), $list_expr); + $code .= implode(";\n" . $this->getIndent(), $list_expr); $list_cond = []; foreach ($cond as $expr) { - if ('Expr_Assign' === $expr->getType()) { + if ($expr->getType() === 'Expr_Assign') { $left = $expr->var; $name = $this->parseIdentifier($left); $type = $this->detectExprType($expr->expr); @@ -1372,12 +1539,12 @@ class CompilerBase extends \PhpAot\Core\Translator if (!$this->hasVar($name)) { $this->addLocalVar($name, $type); } - $code .= $name.' = ('.$this->parseIdentifier($expr->expr).');'; + $code .= $name . ' = (' . $this->parseIdentifier($expr->expr) . ');'; } $list_cond[] = $this->parseExpr($expr); } - $code .= $this->parseBeforeStmtLines().PHP_EOL; + $code .= $this->parseBeforeStmtLines() . PHP_EOL; $code .= 'for (;'; $code .= implode(', ', $list_cond); $code .= '; '; @@ -1387,13 +1554,13 @@ class CompilerBase extends \PhpAot\Core\Translator $list_loop[] = $this->parseExpr($expr); } $code .= implode(', ', $list_loop); - $code .= ') {'.PHP_EOL; + $code .= ') {' . PHP_EOL; - ++$this->indentLevel; + $this->indentLevel++; $code .= $this->parseStmts($stmts); - --$this->indentLevel; + $this->indentLevel--; - $code .= $this->getIndent().'}'.PHP_EOL; + $code .= $this->getIndent() . '}' . PHP_EOL; return $code; } @@ -1405,7 +1572,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parsePreInc(mixed $expr): string { - return '++'.$this->parseIdentifier($expr->var); + return '++' . $this->parseIdentifier($expr->var); } protected function removeAssignOp(string $op): string @@ -1415,57 +1582,57 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseAssignOp(mixed $node, string $op): string { - $var = $this->parseIdentifier($node->var); - $expr = $this->parseIdentifier($node->expr); + $var = $this->parseIdentifier($node->var); + $expr = $this->parseIdentifier($node->expr); $leftExprType = $node->var->getType(); if ($this->isVarExpr($node->var)) { if (!$this->hasVar($var)) { $this->fatalError($node->var, 'Cannot assign to undefined variable'); } - $type = $this->detectVarType($node->var); + $type = $this->detectVarType($node->var); $rightExprStr = $this->convertExprType($expr, $type, $this->detectExprType($node->expr)); if ($this->isAssignOpConcat($op)) { if ($this->isArrayVar($node->var)) { $this->fatalError($node->var, 'Cannot concat string to array'); } - return $var.'.append('.$rightExprStr.')'; - } elseif ($this->isAssignOpPow($op)) { - $powExpr = 'php::call(php::pow, {'.$var.', '.$rightExprStr.'})'; + return $var . '.append(' . $rightExprStr . ')'; + } + if ($this->isAssignOpPow($op)) { + $powExpr = 'php::call(php::pow, {' . $var . ', ' . $rightExprStr . '})'; - return $var.' = '.$this->convertVarType($var, $powExpr); - } else { - return $var.' '.$op.' '.$rightExprStr; + return $var . ' = ' . $this->convertVarType($var, $powExpr); } - } elseif (self::EXPR_ARRAY_DIM_FETCH === $leftExprType) { + return $var . ' ' . $op . ' ' . $rightExprStr; + } + if ($leftExprType === self::EXPR_ARRAY_DIM_FETCH) { /** * $count[$r] -= 1; * 需要转为下面语句: * $tmp_var = $count[$r] - 1; * $count[$r] = $tmp_var;. */ - $type = $this->detectVarType($node->var); + $type = $this->detectVarType($node->var); $rightType = $this->detectExprType($node->expr); - $tmpVar = $this->genTmpVarName(); + $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, $rightType); - $dim = $this->parseIdentifier($node->var->dim); + $dim = $this->parseIdentifier($node->var->dim); $binaryOp = $this->removeAssignOp($op); - if ('.' === $binaryOp) { - $this->beforeStmtLines[] = "$tmpVar = php::concat(". - $this->convertVarType($tmpVar, $var).', '. - $this->convertExprType($expr, $type, $rightType).');'; + if ($binaryOp === '.') { + $this->beforeStmtLines[] = "{$tmpVar} = php::concat(" . + $this->convertVarType($tmpVar, $var) . ', ' . + $this->convertExprType($expr, $type, $rightType) . ');'; } else { - $this->beforeStmtLines[] = "$tmpVar = ". - $this->convertVarType($tmpVar, $var).' '. - $binaryOp.' '. - $this->convertExprType($expr, $type, $rightType).';'; + $this->beforeStmtLines[] = "{$tmpVar} = " . + $this->convertVarType($tmpVar, $var) . ' ' . + $binaryOp . ' ' . + $this->convertExprType($expr, $type, $rightType) . ';'; } return $this->parseArrayDimStore($node->var->var, $dim, $tmpVar); - } else { - return $var.' '.$op.' ('.$expr.')'; } + return $var . ' ' . $op . ' (' . $expr . ')'; } protected function parseAssignOpConcat(mixed $expr): string @@ -1510,14 +1677,14 @@ class CompilerBase extends \PhpAot\Core\Translator protected function error(string $msg): void { - $this->climate->red("Fatal error: $msg"); + $this->climate->red("Fatal error: {$msg}"); debug_print_backtrace(); exit(255); } protected function fatalError(Node $node, string $msg): void { - $this->error("$msg in {$this->file}:{$node->getStartLine()}"); + $this->error("{$msg} in {$this->file}:{$node->getStartLine()}"); } protected function dump(NodeAbstract $v): void @@ -1530,16 +1697,16 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseArrayDimFetch($node, bool $write): string { $var = $this->parseIdentifier($node->var); - if (null === $node->dim) { + if ($node->dim === null) { if (!$write) { $this->fatalError($node, 'Cannot use [] for reading'); } else { - return $var.'.newItem()'; + return $var . '.newItem()'; } } else { $dim = $this->trimBrackets($this->parseIdentifier($node->dim)); - return $var.'.item('.$dim.', '.$this->escapeBool($write).')'; + return $var . '.item(' . $dim . ', ' . $this->escapeBool($write) . ')'; } } @@ -1547,7 +1714,7 @@ class CompilerBase extends \PhpAot\Core\Translator { $id = $this->parseIdentifier($array); - return $id.'.offsetSet('.$this->trimBrackets($dim).', '.$this->trimBrackets($var).')'; + return $id . '.offsetSet(' . $this->trimBrackets($dim) . ', ' . $this->trimBrackets($var) . ')'; } protected function parseBinaryOpShiftLeft($expr): string @@ -1574,10 +1741,10 @@ class CompilerBase extends \PhpAot\Core\Translator { $possibleFunctionNames = [$this->escapeName($fname)]; if ($this->namespace) { - $possibleFunctionNames[] = $this->escapeNamespace($this->namespace).self::NAMESPACE_SEPARATOR.$fname; + $possibleFunctionNames[] = $this->escapeNamespace($this->namespace) . self::NAMESPACE_SEPARATOR . $fname; } if (isset($this->useFunctions[$fname])) { - $possibleFunctionNames[] = $this->escapeNamespace($this->useFunctions[$fname]).self::NAMESPACE_SEPARATOR.$fname; + $possibleFunctionNames[] = $this->escapeNamespace($this->useFunctions[$fname]) . self::NAMESPACE_SEPARATOR . $fname; } foreach ($possibleFunctionNames as $name) { // 在预处理阶段检测到函数声明,但是未定义,说明在当前文件,但是顺序错误 @@ -1599,21 +1766,21 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseFuncCall(Node\Expr\FuncCall $expr, bool $silent = false): string { if ($this->isVarExpr($expr->name)) { - $fn = $this->parseIdentifier($expr->name); + $fn = $this->parseIdentifier($expr->name); $name = ''; - } elseif ('Name' === $expr->name->getType()) { + } elseif ($expr->name->getType() === 'Name') { $name = $this->parseIdentifier($expr->name); if (in_array($name, $this->unsupportedFunctions)) { - $this->fatalError($expr, 'Unsupported function: `'.$name.'`'); + $this->fatalError($expr, 'Unsupported function: `' . $name . '`'); } $nativeFn = $this->findNativeFunction($name); if ($nativeFn) { - return self::PREFIX.$nativeFn.'('.$this->parseNativeCallArgs($expr->args, $nativeFn).')'; + return self::PREFIX . $nativeFn . '(' . $this->parseNativeCallArgs($expr->args, $nativeFn) . ')'; } if ($this->isInternalFunction($name)) { - $fn = 'php::'.$name; + $fn = 'php::' . $name; } else { - $fn = '"'.$name.'"'; + $fn = '"' . $name . '"'; } $code = $this->parseFuncCallWithOptimizer($name, $expr); if ($code) { @@ -1622,23 +1789,22 @@ class CompilerBase extends \PhpAot\Core\Translator } else { $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, self::TYPE_VAR); - $this->beforeStmtLines[] = $tmpVar.' = '.$this->parseExpr($expr->name).';'; - $fn = $tmpVar; - $name = ''; + $this->beforeStmtLines[] = $tmpVar . ' = ' . $this->parseExpr($expr->name) . ';'; + $fn = $tmpVar; + $name = ''; } $call = $silent ? 'php::silentCall' : 'php::call'; if (empty($expr->args)) { - return $call.'('.$fn.')'; - } else { - return $call.'('.$fn.', {'.$this->parseCallArgs($expr->args, $name).'})'; + return $call . '(' . $fn . ')'; } + return $call . '(' . $fn . ', {' . $this->parseCallArgs($expr->args, $name) . '})'; } protected function parseNativeCallArgs(array $args, string $nativeFunc): string { $list_args = []; foreach ($args as $i => $arg) { - $argInfo = $this->getArgInfo($arg, $nativeFunc, $i); + $argInfo = $this->getArgInfo($arg, $nativeFunc, $i); $list_args[] = $this->getTypeConvertedArg($arg, $argInfo); } @@ -1660,19 +1826,19 @@ class CompilerBase extends \PhpAot\Core\Translator // 调用了不存在的变量,可能是引用 if (!$this->hasVar($name)) { $this->addLocalVar($name, self::TYPE_REF); - $this->beforeStmtLines[] = $name.' = php::newReference();'; + $this->beforeStmtLines[] = $name . ' = php::newReference();'; } elseif ($funcName and Reflection::isReferenceArg($funcName, $i)) { // 需要引用类型的参数,使用临时变量作为引用,并替换掉实际的参数 $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, self::TYPE_REF); - $this->beforeStmtLines[] = $tmpVar.' = '.$this->parseExpr($arg->value).'.toReference();'; - $list_args[] = '&'.$tmpVar; + $this->beforeStmtLines[] = $tmpVar . ' = ' . $this->parseExpr($arg->value) . '.toReference();'; + $list_args[] = '&' . $tmpVar; continue; } } elseif ($this->isPropertyFetch($arg->value)) { if ($funcName and Reflection::isReferenceArg($funcName, $i)) { - $obj = $this->parseIdentifier($arg->value->var); - $list_args[] = $obj.'.getPropertyReference('.$this->identifierToStr($arg->value->name).')'; + $obj = $this->parseIdentifier($arg->value->var); + $list_args[] = $obj . '.getPropertyReference(' . $this->identifierToStr($arg->value->name) . ')'; continue; } } @@ -1694,23 +1860,25 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parsePostOp($expr, string $op): string { if ($this->isVarExpr($expr->var)) { - return $this->parseIdentifier($expr->var).str_repeat($op, 2); - } elseif ($this->isPropertyFetch($expr->var)) { - $obj = $this->parseIdentifier($expr->var->var); - $prop = $this->identifierToStr($expr->var->name); + return $this->parseIdentifier($expr->var) . str_repeat($op, 2); + } + if ($this->isPropertyFetch($expr->var)) { + $obj = $this->parseIdentifier($expr->var->var); + $prop = $this->identifierToStr($expr->var->name); $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, self::TYPE_VAR); - $this->beforeStmtLines[] = $tmpVar.' = '.$obj.'.getProperty('.$prop.');'; - $this->afterStmtLines[] = $obj.'.setProperty('.$prop.', '.$tmpVar.' '.$op.' 1);'; + $this->beforeStmtLines[] = $tmpVar . ' = ' . $obj . '.getProperty(' . $prop . ');'; + $this->afterStmtLines[] = $obj . '.setProperty(' . $prop . ', ' . $tmpVar . ' ' . $op . ' 1);'; return $tmpVar; - } elseif ($this->isStaticPropertyFetch($expr->var)) { - $class = $this->identifierToStr($expr->var->class); - $prop = $this->identifierToStr($expr->var->name); + } + if ($this->isStaticPropertyFetch($expr->var)) { + $class = $this->identifierToStr($expr->var->class); + $prop = $this->identifierToStr($expr->var->name); $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, self::TYPE_VAR); - $this->beforeStmtLines[] = $tmpVar.' = php::getStaticProperty('.$class.', '.$prop.');'; - $this->afterStmtLines[] = 'php::setStaticProperty('.$class.', '.$prop.', '.$tmpVar.' '.$op.' 1);'; + $this->beforeStmtLines[] = $tmpVar . ' = php::getStaticProperty(' . $class . ', ' . $prop . ');'; + $this->afterStmtLines[] = 'php::setStaticProperty(' . $class . ', ' . $prop . ', ' . $tmpVar . ' ' . $op . ' 1);'; return $tmpVar; } @@ -1730,15 +1898,14 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseTernary(mixed $expr): string { $cond = $expr->cond; - $if = $expr->if; + $if = $expr->if; $else = $expr->else; - if (null === $if) { + if ($if === null) { $cond = $this->parseExpr($cond); - return '('.$cond.') ? ('.$cond.') : ('.$this->parseExpr($else).')'; - } else { - return '('.$this->parseExpr($cond).') ? ('.$this->parseExpr($if).') : ('.$this->parseExpr($else).')'; + return '(' . $cond . ') ? (' . $cond . ') : (' . $this->parseExpr($else) . ')'; } + return '(' . $this->parseExpr($cond) . ') ? (' . $this->parseExpr($if) . ') : (' . $this->parseExpr($else) . ')'; } protected function parseBinaryOpGreater(mixed $expr): string @@ -1748,15 +1915,15 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseBinaryOpPow(mixed $expr): string { - $left = $this->parseIdentifier($expr->left); + $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); - return 'php::pow('.$left.', '.$right.')'; + return 'php::pow(' . $left . ', ' . $right . ')'; } protected function parsePreDec(mixed $expr): string { - return '--'.$this->parseIdentifier($expr->var); + return '--' . $this->parseIdentifier($expr->var); } protected function parseBinaryOpBitwiseAnd(mixed $expr): string @@ -1778,50 +1945,50 @@ class CompilerBase extends \PhpAot\Core\Translator { $var = $this->parseIdentifier($expr->expr); - return '~'.$var; + return '~' . $var; } protected function parseIf(mixed $v): string { $cond = $this->parseExpr($v->cond); - $code = $this->parseBeforeStmtLines().PHP_EOL; - $code .= 'if ('.$cond.') {'.PHP_EOL; - ++$this->indentLevel; + $code = $this->parseBeforeStmtLines() . PHP_EOL; + $code .= 'if (' . $cond . ') {' . PHP_EOL; + $this->indentLevel++; $code .= $this->parseStmts($v->stmts); - --$this->indentLevel; - $code .= $this->getIndent().'}'; + $this->indentLevel--; + $code .= $this->getIndent() . '}'; if ($v->elseifs) { foreach ($v->elseifs as $elseif) { $elseifCond = $this->parseExpr($elseif->cond); - $code .= ' else if ('.$elseifCond.') {'.PHP_EOL; - ++$this->indentLevel; + $code .= ' else if (' . $elseifCond . ') {' . PHP_EOL; + $this->indentLevel++; $code .= $this->parseStmts($elseif->stmts); - --$this->indentLevel; - $code .= $this->getIndent().'}'; + $this->indentLevel--; + $code .= $this->getIndent() . '}'; } } if ($v->else) { - $code .= ' else {'.PHP_EOL; - ++$this->indentLevel; + $code .= ' else {' . PHP_EOL; + $this->indentLevel++; $code .= $this->parseStmts($v->else->stmts); - --$this->indentLevel; - $code .= $this->getIndent().'}'; + $this->indentLevel--; + $code .= $this->getIndent() . '}'; } - return $code.PHP_EOL; + return $code . PHP_EOL; } protected function parseBinaryOpEqual(mixed $expr): string { - return 'php::equals('.$this->parseExpr($expr->left).', '.$this->parseExpr($expr->right).')'; + return 'php::equals(' . $this->parseExpr($expr->left) . ', ' . $this->parseExpr($expr->right) . ')'; } protected function parseBinaryOpNotEqual(mixed $expr): string { - return '!php::equals('.$this->parseExpr($expr->left).', '.$this->parseExpr($expr->right).')'; + return '!php::equals(' . $this->parseExpr($expr->left) . ', ' . $this->parseExpr($expr->right) . ')'; } /** @@ -1846,56 +2013,24 @@ class CompilerBase extends \PhpAot\Core\Translator { $expr = $this->parseExpr($expr->expr); - return '!'.$expr; + return '!' . $expr; } protected function parseWhile(Node $v): string { - $cond = $this->parseExpr($v->cond); + $cond = $this->parseExpr($v->cond); $stmts = $v->stmts; - $code = $this->parseBeforeStmtLines().PHP_EOL; - $code .= 'while ('.$cond.') {'.PHP_EOL; - ++$this->indentLevel; + $code = $this->parseBeforeStmtLines() . PHP_EOL; + $code .= 'while (' . $cond . ') {' . PHP_EOL; + $this->indentLevel++; $code .= $this->parseStmts($stmts); - --$this->indentLevel; - $code .= $this->getIndent().'}'.PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '}' . PHP_EOL; return $code; } - public function isClosedCall($expr, $call): bool - { - if ('' === $call) { - if (!str_starts_with($expr, '(')) { - return false; - } - $startPos = 0; - } else { - if (!str_starts_with($expr, $call.'(')) { - return false; - } - $startPos = strlen($call); - } - - $bracketCount = 0; - $length = strlen($expr); - - for ($i = $startPos; $i < $length; ++$i) { - $char = $expr[$i]; - if ('(' === $char) { - ++$bracketCount; - } elseif (')' === $char) { - --$bracketCount; - if (0 === $bracketCount) { - return $i === $length - 1; - } - } - } - - return false; - } - protected function trimBrackets(string $str): string { if ($this->isClosedCall($str, '')) { @@ -1908,7 +2043,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function convertIntExpr(string $expr): string { if (!$this->isClosedCall($expr, 'php::toInt')) { - return 'php::toInt('.$this->trimBrackets($expr).')'; + return 'php::toInt(' . $this->trimBrackets($expr) . ')'; } return $expr; @@ -1917,22 +2052,16 @@ class CompilerBase extends \PhpAot\Core\Translator protected function convertFloatExpr(string $expr): string { if (!$this->isClosedCall($expr, 'php::toFloat')) { - return 'php::toFloat('.$this->trimBrackets($expr).')'; + return 'php::toFloat(' . $this->trimBrackets($expr) . ')'; } return $expr; } - public function stop(string $string): void - { - $this->climate->red($string."\n"); - exit(1); - } - protected function convertStringExpr(string $expr): string { if (!$this->isClosedCall($expr, 'php::toString')) { - return 'php::toString('.$this->trimBrackets($expr).')'; + return 'php::toString(' . $this->trimBrackets($expr) . ')'; } return $expr; @@ -1941,11 +2070,10 @@ class CompilerBase extends \PhpAot\Core\Translator protected function convertObjectExpr(string $expr, string $class = ''): string { if (!$this->isClosedCall($expr, 'php::toObject')) { - if ('' === $class) { - return 'php::toObject('.$this->trimBrackets($expr).')'; - } else { - return 'php::toObject('.$this->trimBrackets($expr).', '.$class.')'; + if ($class === '') { + return 'php::toObject(' . $this->trimBrackets($expr) . ')'; } + return 'php::toObject(' . $this->trimBrackets($expr) . ', ' . $class . ')'; } return $expr; @@ -1954,7 +2082,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function convertArrayExpr(string $expr): string { if (!$this->isClosedCall($expr, 'php::toArray')) { - return 'php::toArray('.$this->trimBrackets($expr).')'; + return 'php::toArray(' . $this->trimBrackets($expr) . ')'; } return $expr; @@ -1963,7 +2091,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function convertBoolExpr(string $expr): string { if (!$this->isClosedCall($expr, 'php::toBool')) { - return 'php::toBool('.$this->trimBrackets($expr).')'; + return 'php::toBool(' . $this->trimBrackets($expr) . ')'; } return $expr; @@ -1981,72 +2109,72 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parsePrint(Node\Expr\Print_ $expr): string { - return 'php::echo('.$this->parseExpr($expr->expr).')'; + return 'php::echo(' . $this->parseExpr($expr->expr) . ')'; } protected function parseDo(Node\Stmt\Do_ $v): string { $stmts = $v->stmts; - $cond = $this->parseExpr($v->cond); - $code = $this->parseBeforeStmtLines().PHP_EOL; - $code .= 'do {'.PHP_EOL; - ++$this->indentLevel; + $cond = $this->parseExpr($v->cond); + $code = $this->parseBeforeStmtLines() . PHP_EOL; + $code .= 'do {' . PHP_EOL; + $this->indentLevel++; $code .= $this->parseStmts($stmts); - --$this->indentLevel; - $code .= $this->getIndent().'} while ('.$cond.');'.PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '} while (' . $cond . ');' . PHP_EOL; return $code; } protected function parseBinaryOpIdentical(Node\Expr\BinaryOp $expr): string { - $left = $this->parseIdentifier($expr->left); + $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); - if ('nullptr' === $right) { - return $left.'.isNull()'; + if ($right === 'nullptr') { + return $left . '.isNull()'; } - return 'php::same('.$left.', '.$right.')'; + return 'php::same(' . $left . ', ' . $right . ')'; } protected function parseBinaryOpSpaceship(Node\Expr\BinaryOp\Spaceship $expr): string { - $left = $this->parseIdentifier($expr->left); + $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); - return 'php::compare('.$left.', '.$right.')'; + return 'php::compare(' . $left . ', ' . $right . ')'; } protected function parseBinaryOpNotIdentical(Node\Expr\BinaryOp $expr): string { - return '!('.$this->parseBinaryOpIdentical($expr).')'; + return '!(' . $this->parseBinaryOpIdentical($expr) . ')'; } protected function parseNew(Node\Expr\New_ $expr): string { $className = $this->parseIdentifier($expr->class); - $args = $expr->args; - $cePtr = $this->getClassEntryPtr($className); + $className = $this->getNamespacedClassName($className); + $args = $expr->args; + $cePtr = $this->getClassEntryPtr($className); if (empty($args)) { - return 'php::newObject('.$cePtr.')'; - } else { - return 'php::newObject('.$cePtr.', {'.$this->parseCallArgs($args).'})'; + return 'php::newObject(' . $cePtr . ')'; } + return 'php::newObject(' . $cePtr . ', {' . $this->parseCallArgs($args) . '})'; } protected function parseClone(Node\Expr\Clone_ $expr): string { $var = $this->parseIdentifier($expr->expr); - return $var.'.clone()'; + return $var . '.clone()'; } protected function parseInstanceof(Node\Expr\Instanceof_ $expr): string { $var = $this->parseIdentifier($expr->expr); - return $var.'.instanceOf('.$this->identifierToStr($expr->class).')'; + return $var . '.instanceOf(' . $this->identifierToStr($expr->class) . ')'; } protected function parseCastInt(Node\Expr\Cast\Int_ $node): string @@ -2071,31 +2199,34 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseConstFetch(Node\Expr\ConstFetch $expr): string { - if ('Name' != $expr->name->getType() and !($expr->name instanceof Node\Name\FullyQualified)) { + if ($expr->name->getType() != 'Name' and !($expr->name instanceof Node\Name\FullyQualified)) { abort($expr); } $name = $this->parseIdentifier($expr->name); if ($this->hasConstant($name)) { return $this->getConstant($name); } - if ('null' === $name) { + if ($name === 'null') { return 'php::null'; - } elseif ('true' === $name) { + } + if ($name === 'true') { return 'true'; - } elseif ('false' === $name) { + } + if ($name === 'false') { return 'false'; - } elseif ('PHP_EOL' === $name) { - return '"'.$this->escapeString(PHP_EOL).'"'; + } + if ($name === 'PHP_EOL') { + return '"' . $this->escapeString(PHP_EOL) . '"'; } - return 'php::constant("'.$name.'")'; + return 'php::constant("' . $this->escapeString($name) . '")'; } protected function parseUnaryMinus(Node $expr): string { $code = $this->parseExpr($expr->expr); - return '-'.$code; + return '-' . $code; } protected function parseUnaryPlus(mixed $expr) @@ -2105,10 +2236,10 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseBinaryOpDiv(Node\Expr\BinaryOp\Div $expr): string { - $left = $this->parseIdentifier($expr->left); + $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); - return $left.' / ('.$right.')'; + return $left . ' / (' . $right . ')'; } protected function parseBinaryOpMinus(Node\Expr\BinaryOp\Minus $expr): string @@ -2119,12 +2250,12 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseInterpolatedString(Node\Scalar\InterpolatedString $expr): string { $parts = $expr->parts; - $list = []; + $list = []; foreach ($parts as $part) { $list[] = $this->parseExpr($part); } - return 'php::concat({'.implode(', ', $list).'})'; + return 'php::concat({' . implode(', ', $list) . '})'; } protected function escapeString(string $str): string @@ -2140,12 +2271,12 @@ class CompilerBase extends \PhpAot\Core\Translator protected function escapeVarName(string $name): string { if (in_array($name, Constants::CPP_RESERVED_NAMES)) { - return '_php__var__'.$name; - } elseif ('this' === $name) { + return '_php__var__' . $name; + } + if ($name === 'this') { return 'this_'; - } else { - return $name; } + return $name; } protected function escapeNamespace(string $ns): string @@ -2170,7 +2301,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseInterpolatedStringPart(Node $expr): string { - return '"'.$this->escapeString($expr->value).'"'; + return '"' . $this->escapeString($expr->value) . '"'; } protected function parseGlobal(Node $v): string @@ -2189,7 +2320,7 @@ class CompilerBase extends \PhpAot\Core\Translator { $funcDef = $this->nativeFunctions[$funcName]; if (!array_key_exists($index, $funcDef->argInfoList)) { - $this->fatalError($arg, "Argument `$index` of function `$funcName` not found"); + $this->fatalError($arg, "Argument `{$index}` of function `{$funcName}` not found"); } return $funcDef->argInfoList[$index]; @@ -2210,13 +2341,13 @@ class CompilerBase extends \PhpAot\Core\Translator protected function convertExprType(string $expr, $leftType, $rightType): string { - if (self::TYPE_FLOAT === $leftType or self::TYPE_FLOAT === $rightType) { + if ($leftType === self::TYPE_FLOAT or $rightType === self::TYPE_FLOAT) { return $this->convertFloatExpr($expr); } - if (self::TYPE_INT === $leftType or self::TYPE_INT === $rightType) { + if ($leftType === self::TYPE_INT or $rightType === self::TYPE_INT) { return $this->convertIntExpr($expr); } - if (self::TYPE_BOOL === $leftType or self::TYPE_BOOL === $rightType) { + if ($leftType === self::TYPE_BOOL or $rightType === self::TYPE_BOOL) { return $this->convertBoolExpr($expr); } @@ -2225,42 +2356,42 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseExit(Node $node): string { - return 'php::exit('.$this->parseIdentifier($node->expr).')'; + return 'php::exit(' . $this->parseIdentifier($node->expr) . ')'; } protected function parseUnset(Node\Stmt\Unset_ $node): string { - $vars = $node->vars; + $vars = $node->vars; $lines = []; foreach ($vars as $var) { $type = $var->getType(); - if (self::EXPR_ARRAY_DIM_FETCH === $type) { - $array = $this->parseIdentifier($var->var); - $dim = $this->parseIdentifier($var->dim); - $lines[] = $array.'.offsetUnset('.$dim.');'; - } elseif ('Expr_PropertyFetch' === $type) { - $object = $this->parseIdentifier($var->var); + if ($type === self::EXPR_ARRAY_DIM_FETCH) { + $array = $this->parseIdentifier($var->var); + $dim = $this->parseIdentifier($var->dim); + $lines[] = $array . '.offsetUnset(' . $dim . ');'; + } elseif ($type === 'Expr_PropertyFetch') { + $object = $this->parseIdentifier($var->var); $propName = $this->parseIdentifier($var->name); - $lines[] = $object.'.unsetProperty("'.$propName.'");'; - } elseif (self::EXPR_VARIABLE === $type) { - $name = $this->parseIdentifier($var); - $lines[] = "$name.unset();"; + $lines[] = $object . '.unsetProperty("' . $propName . '");'; + } elseif ($type === self::EXPR_VARIABLE) { + $name = $this->parseIdentifier($var); + $lines[] = "{$name}.unset();"; } else { abort($var); } } - return implode(PHP_EOL.$this->getIndent(), $lines); + return implode(PHP_EOL . $this->getIndent(), $lines); } protected function getPropertyIdentifier(NodeAbstract $object, NodeAbstract $property): string { $id = $this->identifierToStr($property); if ($this->isVarExpr($object) and $this->isIdExpr($property)) { - $objectName = $this->parseIdentifier($object); + $objectName = $this->parseIdentifier($object); $propertyName = $this->parseIdentifier($property); - if ('this_' === $objectName) { - $id = self::PREFIX.$this->getPropertyOffset($propertyName, $this->class, $this->namespace); + if ($objectName === 'this_') { + $id = self::PREFIX . $this->getPropertyOffset($propertyName, $this->class, $this->namespace); } elseif ($this->isTypedObject($objectName)) { $class = $this->objects[$objectName]; if (isset($this->classes[$class])) { @@ -2268,9 +2399,9 @@ class CompilerBase extends \PhpAot\Core\Translator if ($classDef->hasProperty($propertyName)) { $propertyDef = $classDef->getProperty($propertyName); if ($propertyDef->isPublic() or $this->class === $class) { - $id = self::PREFIX.$this->getPropertyOffset($propertyName, $class, $classDef->namespace); + $id = self::PREFIX . $this->getPropertyOffset($propertyName, $class, $classDef->namespace); } else { - $this->fatalError($property, "Cannot access private/protected property `$propertyName` of class `$class`"); + $this->fatalError($property, "Cannot access private/protected property `{$propertyName}` of class `{$class}`"); } } } @@ -2282,11 +2413,11 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parsePropertyFetch(Node\Expr\PropertyFetch $expr, bool $update = false): string { - $object = $expr->var; + $object = $expr->var; $property = $expr->name; - $id = $this->getPropertyIdentifier($object, $property); + $id = $this->getPropertyIdentifier($object, $property); - return $this->convertToObject($object).'.attr('.$id.', '.$this->escapeBool($update).')'; + return $this->convertToObject($object) . '.attr(' . $id . ', ' . $this->escapeBool($update) . ')'; } protected function parseAssignOpShiftRight(Node $node): string @@ -2303,17 +2434,17 @@ class CompilerBase extends \PhpAot\Core\Translator { switch ($expr->getType()) { case 'Scalar_MagicConst_Dir': - return '"'.$this->escapeString($this->dir).'"'; + return '"' . $this->escapeString($this->dir) . '"'; case 'Scalar_MagicConst_File': - return '"'.$this->escapeString($this->file).'"'; + return '"' . $this->escapeString($this->file) . '"'; case 'Scalar_MagicConst_Line': return (string) $expr->getStartLine(); case 'Scalar_MagicConst_Function': - return '"'.$this->escapeString($this->function).'"'; + return '"' . $this->escapeString($this->function) . '"'; case 'Scalar_MagicConst_Class': - return '"'.$this->escapeString($this->class).'"'; + return '"' . $this->escapeString($this->class) . '"'; case 'Scalar_MagicConst_Method': - return '"'.$this->escapeString($this->class).'::'.$this->escapeString($this->method).'"'; + return '"' . $this->escapeString($this->class) . '::' . $this->escapeString($this->method) . '"'; default: abort($expr); } @@ -2325,33 +2456,33 @@ class CompilerBase extends \PhpAot\Core\Translator $keyVar = $this->parseIdentifier($node->keyVar); } - $code = 'for (auto iter = '.$iteratorVar.'.begin(); iter != '.$iteratorVar.'.end(); ++iter) {'.PHP_EOL; - ++$this->indentLevel; + $code = 'for (auto iter = ' . $iteratorVar . '.begin(); iter != ' . $iteratorVar . '.end(); ++iter) {' . PHP_EOL; + $this->indentLevel++; if ($node->keyVar) { $this->checkVar($node, $keyVar); - $code .= $this->getIndent().' '.$keyVar.' = iter.key();'.PHP_EOL; + $code .= $this->getIndent() . ' ' . $keyVar . ' = iter.key();' . PHP_EOL; } - if (self::EXPR_ARRAY_DIM_FETCH == $node->valueVar->getType()) { + if ($node->valueVar->getType() == self::EXPR_ARRAY_DIM_FETCH) { $array = $this->parseIdentifier($node->valueVar->var); - if (!$this->hasVar($array) or null === $node->valueVar->dim) { + if (!$this->hasVar($array) or $node->valueVar->dim === null) { abort($node->valueVar); } $dim = $this->parseIdentifier($node->valueVar->dim); - $code .= $this->getIndent()."$array.offsetSet($dim, iter.value());"; + $code .= $this->getIndent() . "{$array}.offsetSet({$dim}, iter.value());"; } else { $valueVar = $this->parseIdentifier($node->valueVar); $this->checkVar($node, $valueVar); - $code .= $this->getIndent().' '.$valueVar.' = iter.value();'.PHP_EOL; + $code .= $this->getIndent() . ' ' . $valueVar . ' = iter.value();' . PHP_EOL; } $body = $this->parseStmts($node->stmts); - --$this->indentLevel; + $this->indentLevel--; - $code .= $this->parseBeforeStmtLines().PHP_EOL; - $code .= $body.PHP_EOL; + $code .= $this->parseBeforeStmtLines() . PHP_EOL; + $code .= $body . PHP_EOL; - $code .= $this->getIndent().'}'; + $code .= $this->getIndent() . '}'; return $code; } @@ -2365,7 +2496,7 @@ class CompilerBase extends \PhpAot\Core\Translator $name = $this->parseIdentifier($node->expr); if ($this->hasVar($name)) { $type = $this->getVarType($name); - if (self::TYPE_OBJECT === $type) { + if ($type === self::TYPE_OBJECT) { return $this->parseForeachObject($node); } } @@ -2375,8 +2506,8 @@ class CompilerBase extends \PhpAot\Core\Translator $code = ''; $expr = $this->parseIdentifier($node->expr); - $code .= self::TYPE_ARRAY." $iteratorVar = ".$expr.';'.PHP_EOL; - $code .= $this->parseBeforeStmtLines().PHP_EOL; + $code .= self::TYPE_ARRAY . " {$iteratorVar} = " . $expr . ';' . PHP_EOL; + $code .= $this->parseBeforeStmtLines() . PHP_EOL; $code .= $this->parseForeachArray($node, $iteratorVar); return $code; @@ -2384,30 +2515,25 @@ class CompilerBase extends \PhpAot\Core\Translator protected function formatCppCode(string $file): void { - $cmd = 'cd '.$this->rootPath.' && clang-format -i '.$file; - $this->climate->info('format: '.$file); + $cmd = 'cd ' . $this->rootPath . ' && clang-format -i ' . $file; + $this->climate->info('format: ' . $file); $this->climate->comment($cmd); shell_exec($cmd); } - public function genTmpVarName(): string - { - return 'tmp_var_'.$this->tmpVarIndex++; - } - protected function detectConstType($expr): string { $name = $this->parseIdentifier($expr->name); if ($this->hasConstant($name)) { return $this->getConstantType($name); } - if ('true' === $name) { + if ($name === 'true') { return self::TYPE_BOOL; } - if ('false' === $name) { + if ($name === 'false') { return self::TYPE_BOOL; } - if ('NAN' === $name or 'INF' === $name) { + if ($name === 'NAN' or $name === 'INF') { return self::TYPE_FLOAT; } @@ -2416,58 +2542,58 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseSwitch(mixed $v): string { - $cond = $v->cond; + $cond = $v->cond; $tmp_var = $this->genTmpVarName(); - $type = $this->detectExprType($cond); - $var_def = $type.' '.$tmp_var.' = '.$this->parseExpr($cond).';'.PHP_EOL; + $type = $this->detectExprType($cond); + $var_def = $type . ' ' . $tmp_var . ' = ' . $this->parseExpr($cond) . ';' . PHP_EOL; // 保存作用域,switch 可能会解析失败,在这个过程中会增加变量,需重置 $localVars = $this->localVars; - $code = $this->parseBeforeStmtLines().PHP_EOL; + $code = $this->parseBeforeStmtLines() . PHP_EOL; - if (self::TYPE_INT === $type or self::TYPE_FLOAT === $type) { - $code .= 'switch ('.$tmp_var.') {'.PHP_EOL; - ++$this->indentLevel; + if ($type === self::TYPE_INT or $type === self::TYPE_FLOAT) { + $code .= 'switch (' . $tmp_var . ') {' . PHP_EOL; + $this->indentLevel++; foreach ($v->cases as $case) { if (empty($case->cond)) { - $code .= $this->getIndent().'default: {'.PHP_EOL; + $code .= $this->getIndent() . 'default: {' . PHP_EOL; } else { $condType = $case->cond->getType(); - if ('Scalar_Int' !== $condType and 'Scalar_Float' !== $condType) { + if ($condType !== 'Scalar_Int' and $condType !== 'Scalar_Float') { $this->localVars = $localVars; goto _fail; } - $code .= $this->getIndent().'case '.$this->parseScalar($case->cond).': {'.PHP_EOL; + $code .= $this->getIndent() . 'case ' . $this->parseScalar($case->cond) . ': {' . PHP_EOL; } - ++$this->indentLevel; + $this->indentLevel++; $code .= $this->parseStmts($case->stmts); - --$this->indentLevel; - $code .= $this->getIndent().'}'.PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '}' . PHP_EOL; } - --$this->indentLevel; - $code .= $this->getIndent().'}'; + $this->indentLevel--; + $code .= $this->getIndent() . '}'; - return $var_def.$code; + return $var_def . $code; } _fail: - $code = 'do {'.PHP_EOL; - ++$this->indentLevel; + $code = 'do {' . PHP_EOL; + $this->indentLevel++; foreach ($v->cases as $case) { if (empty($case->cond)) { - $code .= $this->getIndent().'else {'.PHP_EOL; + $code .= $this->getIndent() . 'else {' . PHP_EOL; } else { - $code .= $this->getIndent().'if ('.$tmp_var.'=='.$this->parseIdentifier($case->cond).') {'.PHP_EOL; + $code .= $this->getIndent() . 'if (' . $tmp_var . '==' . $this->parseIdentifier($case->cond) . ') {' . PHP_EOL; } - ++$this->indentLevel; + $this->indentLevel++; $code .= $this->parseStmts($case->stmts); - --$this->indentLevel; - $code .= $this->getIndent().'}'.PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '}' . PHP_EOL; } - --$this->indentLevel; - $code .= $this->getIndent().'} while (0);'; + $this->indentLevel--; + $code .= $this->getIndent() . '} while (0);'; - return $var_def.$code; + return $var_def . $code; } protected function parseStatic(mixed $v): string @@ -2475,19 +2601,19 @@ class CompilerBase extends \PhpAot\Core\Translator $list = []; foreach ($v->vars as $var) { if ($var->default) { - $type = $this->detectExprType($var->default); - $list[] = 'static '.$type.' '.$this->parseIdentifier($var->var).' = '.$this->parseIdentifier($var->default).';'; + $type = $this->detectExprType($var->default); + $list[] = 'static ' . $type . ' ' . $this->parseIdentifier($var->var) . ' = ' . $this->parseIdentifier($var->default) . ';'; } else { - $list[] = 'static '.self::TYPE_VAR.' '.$this->parseIdentifier($var->var).';'; + $list[] = 'static ' . self::TYPE_VAR . ' ' . $this->parseIdentifier($var->var) . ';'; } } - return implode(PHP_EOL.$this->getIndent(), $list); + return implode(PHP_EOL . $this->getIndent(), $list); } protected function parseEval(mixed $expr): string { - return 'php::eval('.$this->parseIdentifier($expr->expr).')'; + return 'php::eval(' . $this->parseIdentifier($expr->expr) . ')'; } protected function parseInclude(Node\Expr\Include_ $expr): string @@ -2509,7 +2635,7 @@ class CompilerBase extends \PhpAot\Core\Translator $this->fatalError($expr, 'Invalid include type'); } - return 'php::include('.$this->parseIdentifier($expr->expr).', '.$type.')'; + return 'php::include(' . $this->parseIdentifier($expr->expr) . ', ' . $type . ')'; } protected function parseBreak(mixed $v): string @@ -2534,13 +2660,14 @@ class CompilerBase extends \PhpAot\Core\Translator if (is_nan($value)) { return self::VALUE_NAN; - } elseif (is_infinite($value)) { - return $value > 0 ? self::VALUE_INF : '-'.self::VALUE_INF; - } elseif (floor($value) == $value && abs($value) < 1e15) { + } + if (is_infinite($value)) { + return $value > 0 ? self::VALUE_INF : '-' . self::VALUE_INF; + } + if (floor($value) == $value && abs($value) < 1e15) { return number_format($value, 1, '.', ''); - } else { - return sprintf('%.'.$this->floatPrecision.'g', $value); } + return sprintf('%.' . $this->floatPrecision . 'g', $value); } protected function parseIsset(mixed $expr) @@ -2549,24 +2676,26 @@ class CompilerBase extends \PhpAot\Core\Translator foreach ($vars as $var) { if ($var instanceof Variable) { return $this->hasVar($var->name) ? 'true' : 'false'; - } elseif ($var instanceof Node\Expr\ArrayDimFetch) { - return $this->parseIdentifier($var->var).'.offsetExists('.$this->parseIdentifier($var->dim).')'; - } elseif ($var instanceof Node\Expr\StaticPropertyFetch) { - return 'php::hasStaticProperty('.$this->identifierToStr($var->class).', '.$this->identifierToStr($var->name).')'; - } elseif ($var instanceof Node\Expr\PropertyFetch) { + } + if ($var instanceof Node\Expr\ArrayDimFetch) { + return $this->parseIdentifier($var->var) . '.offsetExists(' . $this->parseIdentifier($var->dim) . ')'; + } + if ($var instanceof Node\Expr\StaticPropertyFetch) { + return 'php::hasStaticProperty(' . $this->identifierToStr($var->class) . ', ' . $this->identifierToStr($var->name) . ')'; + } + if ($var instanceof Node\Expr\PropertyFetch) { $object = $var->var; - $prop = $var->name; + $prop = $var->name; - return $this->parseIdentifier($object).'.propertyExists('.$this->identifierToStr($prop).')'; - } else { - abort($var); + return $this->parseIdentifier($object) . '.propertyExists(' . $this->identifierToStr($prop) . ')'; } + abort($var); } } protected function parseEmpty(mixed $expr): string { - return 'php::empty('.$this->parseExpr($expr->expr).')'; + return 'php::empty(' . $this->parseExpr($expr->expr) . ')'; } protected function parseCastArray(mixed $expr): string @@ -2589,20 +2718,19 @@ class CompilerBase extends \PhpAot\Core\Translator $returnType = Reflection::getFunctionReturnType($name); if ($returnType) { return $this->getTypeFromZendType($returnType); - } else { - return self::TYPE_VAR; } + return self::TYPE_VAR; } protected function convertExprFromType(string $type, string $expr): string { - if (self::TYPE_FLOAT === $type) { + if ($type === self::TYPE_FLOAT) { return $this->convertFloatExpr($expr); } - if (self::TYPE_INT === $type) { + if ($type === self::TYPE_INT) { return $this->convertIntExpr($expr); } - if (self::TYPE_BOOL === $type) { + if ($type === self::TYPE_BOOL) { return $this->convertBoolExpr($expr); } @@ -2621,7 +2749,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function convertToObject(NodeAbstract $object): string { $id = $this->parseIdentifier($object); - if ($this->isVarExpr($object) and self::TYPE_OBJECT === $this->getVarType($id)) { + if ($this->isVarExpr($object) and $this->getVarType($id) === self::TYPE_OBJECT) { return $id; } @@ -2631,7 +2759,7 @@ class CompilerBase extends \PhpAot\Core\Translator $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, self::TYPE_OBJECT); - $this->beforeStmtLines[] = $this->getIndent().$tmpVar.' = '.$id.';'; + $this->beforeStmtLines[] = $this->getIndent() . $tmpVar . ' = ' . $id . ';'; $this->objectWrappers[$id] = $tmpVar; return $tmpVar; @@ -2641,26 +2769,28 @@ class CompilerBase extends \PhpAot\Core\Translator { if ($this->isVarExpr($expr->var)) { $this->inAssignExpr = true; - $left = $this->parseIdentifier($expr->var); + $left = $this->parseIdentifier($expr->var); $this->inAssignExpr = false; if (!$this->hasVar($left)) { $this->addLocalVar($left, self::TYPE_REF); } else { $type = $this->getVarType($left); - if (self::TYPE_REF !== $type) { - $this->fatalError($expr, 'Cannot assign reference to variable of type '.$type); + if ($type !== self::TYPE_REF) { + $this->fatalError($expr, 'Cannot assign reference to variable of type ' . $type); } } if ($this->isVarExpr($expr->expr)) { - return $left.' = '.$this->parseIdentifier($expr->expr).'.toReference()'; - } elseif (self::EXPR_ARRAY_DIM_FETCH === $expr->expr->getType()) { - return $left.' = '.$this->parseIdentifier($expr->expr); - } elseif ($this->isPropertyFetch($expr->expr)) { - $left = $this->parseIdentifier($expr->var); + return $left . ' = ' . $this->parseIdentifier($expr->expr) . '.toReference()'; + } + if ($expr->expr->getType() === self::EXPR_ARRAY_DIM_FETCH) { + return $left . ' = ' . $this->parseIdentifier($expr->expr); + } + if ($this->isPropertyFetch($expr->expr)) { + $left = $this->parseIdentifier($expr->var); $object = $this->convertToObject($expr->expr->var); - $prop = $this->identifierToStr($expr->expr->name); + $prop = $this->identifierToStr($expr->expr->name); - return $left.' = '.$object.'.attrRef('.$prop.')'; + return $left . ' = ' . $object . '.attrRef(' . $prop . ')'; } } abort($expr); @@ -2668,17 +2798,16 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseMethodCall(Node\Expr\MethodCall $expr): string { - $object = $this->convertToObject($expr->var); - $method = $this->parseIdentifier($expr->name); + $object = $this->convertToObject($expr->var); + $method = $this->parseIdentifier($expr->name); $nativeFunc = $this->findNativeMethod($expr, $object, $method); if ($nativeFunc) { return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args); } if (empty($expr->args)) { - return $object.'.exec("'.$method.'")'; - } else { - return $object.'.exec("'.$method.'", '.$this->parseCallArgs($expr->args).')'; + return $object . '.exec("' . $method . '")'; } + return $object . '.exec("' . $method . '", ' . $this->parseCallArgs($expr->args) . ')'; } protected function identifierToStr(NodeAbstract $node, bool $require = true): string @@ -2690,87 +2819,79 @@ class CompilerBase extends \PhpAot\Core\Translator } return $id; - } else { - if ('self' === $id) { - $id = $this->class; - } - - return '"'.$id.'"'; } + if ($id === 'self') { + $id = $this->class; + } + + return '"' . $id . '"'; } protected function requireVar($node, string $var): void { if (!$this->hasVar($var)) { - $this->fatalError($node, 'The variable `'.$var.'` is not defined'); - } - } - - private function parseParentMethodCall(Node\Expr\StaticCall $expr): string - { - $method = $this->identifierToStr($expr->name); - if (empty($expr->args)) { - return 'this_.callParentMethod('.$method.')'; - } else { - return 'this_.callParentMethod('.$method.', {'.$this->parseCallArgs($expr->args).'})'; + $this->fatalError($node, 'The variable `' . $var . '` is not defined'); } } protected function parseStaticCall(Node\Expr\StaticCall $expr): string { if ($this->isVarExpr($expr->class) or $this->isVarExpr($expr->name)) { - $fn = 'php::concat({'.$this->identifierToStr($expr->class).', "::", '.$this->identifierToStr($expr->name).'})'; + $fn = 'php::concat({' . $this->identifierToStr($expr->class) . ', "::", ' . $this->identifierToStr($expr->name) . '})'; } else { $class = $this->parseIdentifier($expr->class); - if ('self' === $class) { + if ($class === 'self') { $class = $this->class; - } elseif ('parent' === $class) { + } elseif ($class === 'parent') { return $this->parseParentMethodCall($expr); } $method = $this->parseIdentifier($expr->name); - $fn = '"'.$class.'::'.$method.'"'; + $fn = '"' . $class . '::' . $method . '"'; } if (empty($expr->args)) { - return 'php::call('.$fn.')'; - } else { - return 'php::call('.$fn.', {'.$this->parseCallArgs($expr->args).'})'; + return 'php::call(' . $fn . ')'; } + return 'php::call(' . $fn . ', {' . $this->parseCallArgs($expr->args) . '})'; } protected function parseStaticPropertyFetch(Node $expr): string { - return 'php::getStaticProperty('.$this->identifierToStr($expr->class).', '.$this->identifierToStr($expr->name).')'; + return 'php::getStaticProperty(' . $this->identifierToStr($expr->class) . ', ' . $this->identifierToStr($expr->name) . ')'; } protected function parseClassConstFetch(Node\Expr\ClassConstFetch $expr): string { $class = $this->parseIdentifier($expr->class); - $class = ('self' === $class or 'this_' === $class) ? $this->class : $class; - $const = $this->parseIdentifier($expr->name); + $class = ($class === 'self' or $class === 'this_') ? $this->class : $class; + $const = $this->escapeString($this->parseIdentifier($expr->name)); + $class = $this->escapeString($this->getNamespacedClassName($class)); + if ($const === 'class') { + return '"' . $class . '"'; + } - return 'php::constant("'.$class.'::'.$const.'")'; + return 'php::constant("' . $class . '::' . $const . '")'; } protected function parseThrow(mixed $expr): string { - if (!$this->isVarExpr($expr->expr) and self::EXPR_NEW != $expr->expr->getType()) { + if (!$this->isVarExpr($expr->expr) and $expr->expr->getType() != self::EXPR_NEW) { $this->fatalError($expr, 'The throw statement only accepts a object variable'); } - return 'php::throwException('.$this->parseIdentifier($expr->expr).')'; + return 'php::throwException(' . $this->parseIdentifier($expr->expr) . ')'; } protected function parseTryCatch(mixed $v): string { - $code = $this->parseBeforeStmtLines().PHP_EOL; + $code = $this->parseBeforeStmtLines() . PHP_EOL; $code .= 'try {'; $stmts = $v->stmts; $code .= PHP_EOL; - ++$this->indentLevel; + $this->indentLevel++; $code .= $this->parseStmts($stmts); - --$this->indentLevel; - $code .= $this->getIndent().'}'.PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '}' . PHP_EOL; $catches = $v->catches; $finally = $v->finally; @@ -2778,21 +2899,21 @@ class CompilerBase extends \PhpAot\Core\Translator $exVar = $this->genTmpVarName(); $this->addLocalVar($exVar, self::TYPE_OBJECT); - $code .= 'catch(zend_object *_ex) {'.PHP_EOL; + $code .= 'catch(zend_object *_ex) {' . PHP_EOL; if ($catches) { - $code .= $this->getIndent().$exVar.' = php::catchException();'.PHP_EOL; - ++$this->indentLevel; + $code .= $this->getIndent() . $exVar . ' = php::catchException();' . PHP_EOL; + $this->indentLevel++; foreach ($catches as $catch) { $code .= $this->parseCatch($catch, $exVar); } - --$this->indentLevel; + $this->indentLevel--; } - $code .= '}'.PHP_EOL; + $code .= '}' . 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().'}'; + $code .= 'if (' . $exVar . ') {' . PHP_EOL . $this->getIndent() . 'php::throwException(' . $exVar . ');' . PHP_EOL . $this->getIndent() . '}'; return $code; } @@ -2800,52 +2921,52 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseCatch(mixed $catch, string $exVar): string { $types = $catch->types; - $var = $this->parseIdentifier($catch->var); + $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() . $var . ' = ' . $exVar . ';' . PHP_EOL; - $code .= $this->parseBeforeStmtLines().PHP_EOL; + $code .= $this->parseBeforeStmtLines() . PHP_EOL; - $code .= $this->getIndent().'if ('.$var.' && '; + $code .= $this->getIndent() . 'if (' . $var . ' && '; foreach ($types as $type) { - $code .= 'php::instanceOf('.$var.', "'.$this->parseIdentifier($type).'")'; + $code .= 'php::instanceOf(' . $var . ', "' . $this->parseIdentifier($type) . '")'; } - $code .= ') {'.PHP_EOL; - ++$this->indentLevel; + $code .= ') {' . PHP_EOL; + $this->indentLevel++; $code .= $this->parseStmts($catch->stmts); - $code .= $this->getIndent()."$exVar.unset();".PHP_EOL; - --$this->indentLevel; - $code .= $this->getIndent().'}'; + $code .= $this->getIndent() . "{$exVar}.unset();" . PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '}'; return $code; } protected function parseShellExec(mixed $expr): string { - return 'php::call("shell_exec", {'.$this->parseInterpolatedString($expr).'})'; + return 'php::call("shell_exec", {' . $this->parseInterpolatedString($expr) . '})'; } protected function parseGoto(Node $v): string { $this->fatalError($v, 'Goto statement is not supported'); - return 'goto '.$v->name->name.';'; + return 'goto ' . $v->name->name . ';'; } protected function parseLabel(Node $v): string { $this->fatalError($v, 'Label statement is not supported'); - return $v->name->name.':'; + return $v->name->name . ':'; } protected function parseConstDef(mixed $v2): string { foreach ($v2->consts as $const) { - $name = $this->parseIdentifier($const->name); + $name = $this->parseIdentifier($const->name); $value = $this->parseIdentifier($const->value); $this->addConstant($name, $value); } @@ -2855,9 +2976,9 @@ class CompilerBase extends \PhpAot\Core\Translator protected function addConstant(string $name, string $value): void { - $constInfo = new \stdClass(); - $constInfo->value = $value; - $constInfo->type = $this->detectStrValueType($value); + $constInfo = new \stdClass(); + $constInfo->value = $value; + $constInfo->type = $this->detectStrValueType($value); $this->nativeConstants[$name] = $constInfo; } @@ -2893,7 +3014,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function isArrayVar($var): bool { - return $this->isVarExpr($var) and $this->hasVar($var->name) and self::TYPE_ARRAY == $this->getVarType($var->name); + return $this->isVarExpr($var) and $this->hasVar($var->name) and $this->getVarType($var->name) == self::TYPE_ARRAY; } protected function setBuildDir(string $string): void @@ -2915,51 +3036,30 @@ class CompilerBase extends \PhpAot\Core\Translator protected function loadFile(string $file): string { if (!file_exists($file)) { - throw new \Exception('File not exists: '.$file); + throw new \Exception('File not exists: ' . $file); } $phpCode = file_get_contents($file); if (!$phpCode) { - throw new \Exception('Can not read file: '.$file); + throw new \Exception('Can not read file: ' . $file); } - $this->file = realpath($file); - $this->dir = dirname($this->file); + $this->file = realpath($file); + $this->dir = dirname($this->file); $this->stubFile = $this->isStubFile($file); return $phpCode; } - public function writeFile(string $file, string $content): void - { - $dir = dirname($file); - if (!is_dir($dir)) { - mkdir($dir, 0777, true); - } - if (!file_put_contents($file, $content)) { - throw new \RuntimeException('Can not write file: '.$file); - } - } - - public function getIncludeDir(): string - { - return $this->getBuildDir().'/include'; - } - - public function getBuildDir(): string - { - return $this->buildDir; - } - protected function parseDeclare(mixed $v): void { $declares = $v->declares; foreach ($declares as $declare) { - $key = $this->parseIdentifier($declare->key); + $key = $this->parseIdentifier($declare->key); $value = $this->parseIdentifier($declare->value); - if ('ticks' === $key) { + if ($key === 'ticks') { $this->fatalError($v, 'declare(ticks=1) is not supported'); - } elseif ('encoding' === $key) { - if ('utf-8' !== strtolower($value)) { - $this->fatalError($v, 'declare(encoding="'.$value.'") is not supported, only UTF-8 is supported'); + } elseif ($key === 'encoding') { + if (strtolower($value) !== 'utf-8') { + $this->fatalError($v, 'declare(encoding="' . $value . '") is not supported, only UTF-8 is supported'); } } $this->strictTypes = boolval(intval($value)); @@ -2971,19 +3071,22 @@ class CompilerBase extends \PhpAot\Core\Translator $code = ''; if ($this->useCppNamespace) { foreach ($v2->uses as $use) { - $code .= 'using '.str_replace('\\', '::', $use->name->toString()).';'.PHP_EOL; + $code .= 'using ' . str_replace('\\', '::', $use->name->toString()) . ';' . PHP_EOL; } } else { foreach ($v2->uses as $use) { $id = $this->parseIdentifier($use->name); - if (Node\Stmt\Use_::TYPE_NORMAL == $use->type) { - $this->useNamespaces[] = $id; - } else { + if ($use->type === Node\Stmt\Use_::TYPE_FUNCTION) { $rpos = strrpos($id, '\\'); - $fn = substr($id, $rpos + 1); - $ns = substr($id, 0, $rpos); + $fn = substr($id, $rpos + 1); + $ns = substr($id, 0, $rpos); // fn => namespace $this->useFunctions[$fn] = $ns; + } else { + $this->useNamespaces[] = $id; + if ($use->alias) { + $this->useAliases[$use->alias->toString()] = $id; + } } } } @@ -3009,8 +3112,8 @@ class CompilerBase extends \PhpAot\Core\Translator if (!$this->hasVar($name)) { $this->addLocalVar($name, self::TYPE_VAR); } else { - if (self::TYPE_VAR !== $this->getVarType($name)) { - $this->fatalError($node, 'Cannot assign value to variable of type '.$this->getVarType($name)); + if ($this->getVarType($name) !== self::TYPE_VAR) { + $this->fatalError($node, 'Cannot assign value to variable of type ' . $this->getVarType($name)); } } } @@ -3029,7 +3132,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function findNativeMethod(NodeAbstract $expr, string $object, string $method): string|false { $nativeFunc = ''; - if ('this_' === $object) { + if ($object === 'this_') { $nativeFunc = $this->getNativeName($method, $this->namespace, $this->class); } elseif (isset($this->objects[$object])) { $class = $this->objects[$object]; @@ -3042,23 +3145,44 @@ class CompilerBase extends \PhpAot\Core\Translator } $methodDef = $classDef->methods[$method]; if (!$this->checkAccessible($classDef, $methodDef)) { - $this->fatalError($expr, 'Method `'.$classDef->getNamespacedName().'::'.$method.'()` is not accessible'); + $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible'); } $nativeFunc = $this->getNativeName($method, $classDef->namespace, $classDef->name); } if ($nativeFunc and $this->isNativeFunction($nativeFunc)) { return $nativeFunc; - } else { - return false; } + return false; } protected function parseNativeMethodCall(string $object, string $nativeFunc, array $args): string { - if (0 === count($args)) { - return self::PREFIX.$nativeFunc.'('.$object.')'; - } else { - return self::PREFIX.$nativeFunc.'('.$object.', '.$this->parseNativeCallArgs($args, $nativeFunc).')'; + if (count($args) === 0) { + return self::PREFIX . $nativeFunc . '(' . $object . ')'; + } + return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $this->parseNativeCallArgs($args, $nativeFunc) . ')'; + } + + private function parseAssignPropertyArrayDim(Node $left, Node $right): string + { + $obj = $this->parseIdentifier($left->var->var); + $propName = $this->identifierToStr($left->var->name); + $code = ''; + $value = $this->trimBrackets($this->parseExpr($right)); + if ($left->dim === null) { + return $code . "{$obj}.appendArrayProperty({$propName}, {$value})"; + } + $dim = $this->trimBrackets($this->parseIdentifier($left->dim)); + + return $code . "{$obj}.updateArrayProperty({$propName}, {$dim}, {$value})"; + } + + private function parseParentMethodCall(Node\Expr\StaticCall $expr): string + { + $method = $this->identifierToStr($expr->name); + if (empty($expr->args)) { + return 'this_.callParentMethod(' . $method . ')'; } + return 'this_.callParentMethod(' . $method . ', {' . $this->parseCallArgs($expr->args) . '})'; } } diff --git a/src/Php/ConstantDef.php b/src/Php/ConstantDef.php index 231db6f6..16638f54 100644 --- a/src/Php/ConstantDef.php +++ b/src/Php/ConstantDef.php @@ -1,18 +1,29 @@ name = $name; - $this->type = $type; + $this->name = $name; + $this->type = $type; $this->flags = $flags; $this->value = $value; } diff --git a/src/Php/Constants.php b/src/Php/Constants.php index 78b813af..b8b36ab3 100644 --- a/src/Php/Constants.php +++ b/src/Php/Constants.php @@ -1,4 +1,12 @@ stmts = $stmts; - $this->encodeMap = require $confDir.'/functions.php'; + $confDir = __DIR__ . '/../../config'; + $this->stmts = $stmts; + $this->encodeMap = require $confDir . '/functions.php'; $this->decodeMap = array_flip($this->encodeMap); - $this->constants = require $confDir.'/constants.php'; + $this->constants = require $confDir . '/constants.php'; } public function parseHeaders(): string { $lines = []; foreach ($this->headers as $header) { - $lines[] = '#include <'.$header.'>'; + $lines[] = '#include <' . $header . '>'; } - return implode(PHP_EOL, $lines).PHP_EOL.PHP_EOL; + return implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL; } public function setPhpxDir($dir): void @@ -64,34 +79,11 @@ class Encryptor extends \PhpAot\Core\Translator return $node->getType(); } - private function parseFunctionDef($v) + public function compileFile($file) { - if ($v->name) { - $name = $this->parseIdentifier($v->name); - } else { - $name = ''; - } - - if ($v->returnType) { - $return = $this->parseIdentifier($v->returnType); - } else { - $return = ''; - } - - if ($v->params) { - $params = $this->parseParams($v->params); - } else { - $params = ''; - } - - $code = $return.' '.$name.'('.$params.') {'.PHP_EOL; - ++$this->indentLevel; - $stmts = $this->parseStmts($v->stmts); - --$this->indentLevel; - $code .= $stmts; - $code .= '}'; - - return $code; + $cmd = 'g++ -c ' . $file . ' -o ' . $file . '.o ' . $this->parseIncludes() . $this->parseLdflags() . $this->parseLibs(); + echo $cmd . PHP_EOL; + shell_exec($cmd); } protected function parseIdentifier($node) @@ -105,7 +97,7 @@ class Encryptor extends \PhpAot\Core\Translator case 'Scalar_Float': return $node->value; case 'Scalar_String': - return '"'.$node->value.'"'; + return '"' . $node->value . '"'; case 'Expr_Array': return $this->parseArray($node); case 'Expr_FuncCall': @@ -127,13 +119,43 @@ class Encryptor extends \PhpAot\Core\Translator } } + private function parseFunctionDef($v) + { + if ($v->name) { + $name = $this->parseIdentifier($v->name); + } else { + $name = ''; + } + + if ($v->returnType) { + $return = $this->parseIdentifier($v->returnType); + } else { + $return = ''; + } + + if ($v->params) { + $params = $this->parseParams($v->params); + } else { + $params = ''; + } + + $code = $return . ' ' . $name . '(' . $params . ') {' . PHP_EOL; + $this->indentLevel++; + $stmts = $this->parseStmts($v->stmts); + $this->indentLevel--; + $code .= $stmts; + $code .= '}'; + + return $code; + } + private function parseParams($params) { $list = []; foreach ($params as $param) { - $type = $param->type ? $this->parseType($param->type) : ''; - $name = $param->var ? $this->parseIdentifier($param->var) : ''; - $list[] = $type.' '.$name; + $type = $param->type ? $this->parseType($param->type) : ''; + $name = $param->var ? $this->parseIdentifier($param->var) : ''; + $list[] = $type . ' ' . $name; $this->typeMap[$name] = $type; } @@ -150,10 +172,10 @@ class Encryptor extends \PhpAot\Core\Translator $lines[] = $this->parseFunctionDef($v); break; case 'Stmt_Expression': - $lines[] = $this->parseExpr($v->expr).';'; + $lines[] = $this->parseExpr($v->expr) . ';'; break; case 'Stmt_Echo': - $lines[] = $this->parseEcho($v).';'; + $lines[] = $this->parseEcho($v) . ';'; break; case 'Stmt_Return': $this->parseReturn($v); @@ -175,7 +197,7 @@ class Encryptor extends \PhpAot\Core\Translator } $code = ''; foreach ($lines as $line) { - $code .= $this->getIndent().$line.PHP_EOL; + $code .= $this->getIndent() . $line . PHP_EOL; } return $code; @@ -229,7 +251,7 @@ class Encryptor extends \PhpAot\Core\Translator private function parseEcho(mixed $v) { - return 'php::echo('.$this->parseExprs($v->exprs).')'; + return 'php::echo(' . $this->parseExprs($v->exprs) . ')'; } private function parseExprs($exprs) @@ -244,23 +266,23 @@ class Encryptor extends \PhpAot\Core\Translator private function parseBinaryOpPlus(mixed $expr) { - $left = $this->parseIdentifier($expr->left); + $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); - return $left.' + '.$right; + return $left . ' + ' . $right; } private function parseReturn(mixed $v) { - return 'return '.$this->parseExpr($v->expr); + return 'return ' . $this->parseExpr($v->expr); } private function parseBinaryOpMul(mixed $expr) { - $left = $this->parseIdentifier($expr->left); + $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); - return $left.' * '.$right; + return $left . ' * ' . $right; } private function detectType($var, $expr) @@ -283,20 +305,20 @@ class Encryptor extends \PhpAot\Core\Translator private function parseArray($node) { $items = $node->items; - $list = []; - ++$this->indentLevel; + $list = []; + $this->indentLevel++; foreach ($items as $item) { if ($item->key) { - $list[] = $this->getIndent().'{ php::Variant('.$this->parseIdentifier($item->key).'), php::Variant('.$this->parseIdentifier($item->value).') }'; + $list[] = $this->getIndent() . '{ php::Variant(' . $this->parseIdentifier($item->key) . '), php::Variant(' . $this->parseIdentifier($item->value) . ') }'; } else { - $list[] = $this->getIndent().'php::Variant('.$this->parseIdentifier($item->value).')'; + $list[] = $this->getIndent() . 'php::Variant(' . $this->parseIdentifier($item->value) . ')'; } } - --$this->indentLevel; + $this->indentLevel--; - return '{'.PHP_EOL. - implode(', '.PHP_EOL, $list).PHP_EOL. - $this->getIndent(). + return '{' . PHP_EOL . + implode(', ' . PHP_EOL, $list) . PHP_EOL . + $this->getIndent() . '}'; } @@ -318,11 +340,11 @@ class Encryptor extends \PhpAot\Core\Translator private function parseIncludes() { $list = [ - $this->phpxDir.'/include', + $this->phpxDir . '/include', ]; $out = '$(php-config --includes) '; foreach ($list as $li) { - $out .= '-I '.$li.' '; + $out .= '-I ' . $li . ' '; } return $out; @@ -332,11 +354,11 @@ class Encryptor extends \PhpAot\Core\Translator { $list = [ '$(php-config --prefix)/lib', - $this->phpxDir.'/lib', + $this->phpxDir . '/lib', ]; $out = ''; foreach ($list as $li) { - $out .= '-L '.$li.' '; + $out .= '-L ' . $li . ' '; } return $out; @@ -350,25 +372,18 @@ class Encryptor extends \PhpAot\Core\Translator ]; $out = ''; foreach ($list as $li) { - $out .= '-l'.$li.' '; + $out .= '-l' . $li . ' '; } return $out; } - public function compileFile($file) - { - $cmd = 'g++ -c '.$file.' -o '.$file.'.o '.$this->parseIncludes().$this->parseLdflags().$this->parseLibs(); - echo $cmd.PHP_EOL; - shell_exec($cmd); - } - private function parseBinaryOpConcat(mixed $expr) { - $left = $this->parseIdentifier($expr->left); + $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); - return $left.' + '.$right; + return $left . ' + ' . $right; } private function parseFuncCall($expr) diff --git a/src/Php/Extractor.php b/src/Php/Extractor.php index 0ca68f19..8dc71ab1 100644 --- a/src/Php/Extractor.php +++ b/src/Php/Extractor.php @@ -1,10 +1,19 @@ checkCtags(); } - /** - * 检查 ctags 是否可用. - */ - private function checkCtags(): void - { - $output = shell_exec("{$this->ctagsPath} --version 2>&1"); - - if (null === $output) { - $this->error("未找到 ctags 命令\n安装: sudo apt install universal-ctags"); - } - - $this->isUniversalCtags = false !== stripos($output, 'Universal Ctags'); - - if (!$this->isUniversalCtags) { - $this->warn('建议使用 Universal Ctags 以获得更好的支持'); - } - } - /** * 提取函数定义. * * @param string $filename 文件路径 - * @param array $prefixes 函数名前缀列表 + * @param array $prefixes 函数名前缀列表 * * @return array 函数列表 */ @@ -45,7 +36,7 @@ class Extractor } $this->info("分析文件: {$filename}"); - $this->info('函数前缀: '.implode(', ', $prefixes)); + $this->info('函数前缀: ' . implode(', ', $prefixes)); // 运行 ctags $tags = $this->runCtags($filename); @@ -53,7 +44,7 @@ class Extractor // 过滤和解析函数 $functions = []; foreach ($tags as $tag) { - if ('function' !== $tag['kind']) { + if ($tag['kind'] !== 'function') { continue; } @@ -79,11 +70,156 @@ class Extractor } } - $this->info('找到 '.count($functions).' 个函数'); + $this->info('找到 ' . count($functions) . ' 个函数'); return $functions; } + /** + * 批量提取多个文件. + */ + public function extractFromFiles(array $files, array $prefixes = ['php_']): array + { + $allFunctions = []; + + foreach ($files as $file) { + try { + $functions = $this->extractFunctions($file, $prefixes); + $allFunctions = array_merge($allFunctions, $functions); + } catch (Exception $e) { + $this->error("处理文件 {$file} 失败: " . $e->getMessage()); + } + } + + return $allFunctions; + } + + /** + * 提取函数并添加额外信息. + */ + public function extractWithMetadata(string $filename, array $prefixes = ['php_']): array + { + $functions = $this->extractFunctions($filename, $prefixes); + + // 添加额外的元数据 + foreach ($functions as &$func) { + $func['metadata'] = $this->extractMetadata($filename, $func); + } + + return $functions; + } + + /** + * 生成函数统计信息. + */ + public function generateStatistics(array $functions): array + { + $stats = [ + 'total' => count($functions), + 'byReturnType' => [], + 'byParameterCount' => [], + 'byPrefix' => [], + 'withComments' => 0, + 'isPHPFunction' => 0, + ]; + + foreach ($functions as $func) { + // 按返回类型统计 + $returnType = $func['returnType']; + $stats['byReturnType'][$returnType] = + ($stats['byReturnType'][$returnType] ?? 0) + 1; + + // 按参数数量统计 + $paramCount = count($func['parameters']); + $stats['byParameterCount'][$paramCount] = + ($stats['byParameterCount'][$paramCount] ?? 0) + 1; + + // 按前缀统计 + $name = $func['name']; + $prefix = preg_match('/^([a-z_]+_)/i', $name, $m) ? $m[1] : 'other'; + $stats['byPrefix'][$prefix] = + ($stats['byPrefix'][$prefix] ?? 0) + 1; + + // 有注释的函数 + if (!empty($func['metadata']['comments'])) { + $stats['withComments']++; + } + + // PHP 函数宏 + if ($func['metadata']['isPHPFunction'] ?? false) { + $stats['isPHPFunction']++; + } + } + + return $stats; + } + + /** + * 导出为 Markdown 文档. + */ + public function exportToMarkdown(array $functions, string $title = 'API 文档'): string + { + $md = "# {$title}\n\n"; + $md .= '生成时间: ' . date('Y-m-d H:i:s') . "\n\n"; + $md .= '总计: ' . count($functions) . " 个函数\n\n"; + $md .= "---\n\n"; + + foreach ($functions as $func) { + $md .= "## {$func['name']}\n\n"; + + // 签名 + $md .= "```c\n{$func['signature']}\n```\n\n"; + + // 返回类型 + $md .= "**返回类型**: `{$func['returnType']}`\n\n"; + + // 参数 + if (!empty($func['parameters'])) { + $md .= "**参数**:\n\n"; + foreach ($func['parameters'] as $param) { + $name = $param['name'] ?: '(unnamed)'; + $md .= "- `{$param['type']}` **{$name}**\n"; + } + $md .= "\n"; + } else { + $md .= "**参数**: 无\n\n"; + } + + // 注释 + if (!empty($func['metadata']['comments'])) { + $md .= "**说明**:\n\n"; + foreach ($func['metadata']['comments'] as $comment) { + $md .= $comment . "\n\n"; + } + } + + // 位置 + $md .= "**位置**: {$func['location']['file']}:{$func['location']['line']}\n\n"; + + $md .= "---\n\n"; + } + + return $md; + } + + /** + * 检查 ctags 是否可用. + */ + private function checkCtags(): void + { + $output = shell_exec("{$this->ctagsPath} --version 2>&1"); + + if ($output === null) { + $this->error("未找到 ctags 命令\n安装: sudo apt install universal-ctags"); + } + + $this->isUniversalCtags = stripos($output, 'Universal Ctags') !== false; + + if (!$this->isUniversalCtags) { + $this->warn('建议使用 Universal Ctags 以获得更好的支持'); + } + } + /** * 运行 ctags 命令. */ @@ -97,12 +233,12 @@ class Extractor $output = shell_exec($cmd); - if (null === $output) { + if ($output === null) { throw new RuntimeException('ctags 执行失败'); } // 解析 JSON 输出 - $tags = []; + $tags = []; $lines = explode("\n", trim($output)); foreach ($lines as $line) { @@ -111,7 +247,7 @@ class Extractor } $tag = json_decode($line, true); - if (null === $tag) { + if ($tag === null) { continue; } @@ -127,7 +263,7 @@ class Extractor private function parseFunction(string $filename, array $tag): ?array { $funcName = $tag['name'] ?? ''; - $lineNum = $tag['line'] ?? 0; + $lineNum = $tag['line'] ?? 0; if (empty($funcName) || $lineNum < 1) { return null; @@ -147,15 +283,15 @@ class Extractor $parameters = $this->parseParameters($signature, $funcName); return [ - 'name' => $funcName, + 'name' => $funcName, 'returnType' => $returnType, - 'signature' => $signature, + 'signature' => $signature, 'parameters' => $parameters, - 'location' => [ + 'location' => [ 'file' => $filename, 'line' => $lineNum, ], - 'scope' => $tag['scope'] ?? null, + 'scope' => $tag['scope'] ?? null, 'scopeKind' => $tag['scopeKind'] ?? null, ]; } @@ -167,20 +303,20 @@ class Extractor { $lines = file($filename, FILE_IGNORE_NEW_LINES); - if (false === $lines || $lineNum > count($lines)) { + if ($lines === false || $lineNum > count($lines)) { return ''; } // 从函数声明行开始收集,直到遇到 { 或 ; $signatureLines = []; - $maxLines = min($lineNum + 20, count($lines)); + $maxLines = min($lineNum + 20, count($lines)); - for ($i = $lineNum - 1; $i < $maxLines; ++$i) { - $line = $lines[$i]; + for ($i = $lineNum - 1; $i < $maxLines; $i++) { + $line = $lines[$i]; $signatureLines[] = $line; // 检查是否到达函数体或声明结束 - if (false !== strpos($line, '{') || false !== strpos($line, ';')) { + if (strpos($line, '{') !== false || strpos($line, ';') !== false) { break; } } @@ -195,9 +331,7 @@ class Extractor $signature = preg_replace('/\s+/', ' ', $signature); // 清理首尾空白 - $signature = trim($signature); - - return $signature; + return trim($signature); } /** @@ -206,7 +340,7 @@ class Extractor private function parseReturnType(string $signature, string $funcName): string { // 匹配: <返回类型> <函数名>( - $pattern = '/^(.+?)\s+'.preg_quote($funcName, '/').'\s*\(/'; + $pattern = '/^(.+?)\s+' . preg_quote($funcName, '/') . '\s*\(/'; if (preg_match($pattern, $signature, $matches)) { $returnType = trim($matches[1]); @@ -228,7 +362,7 @@ class Extractor private function parseParameters(string $signature, string $funcName): array { // 提取括号内的参数 - $pattern = '/'.preg_quote($funcName, '/').'\s*\((.*?)\)/s'; + $pattern = '/' . preg_quote($funcName, '/') . '\s*\((.*?)\)/s'; if (!preg_match($pattern, $signature, $matches)) { return []; @@ -237,7 +371,7 @@ class Extractor $paramsStr = trim($matches[1]); // 空参数或 void - if (empty($paramsStr) || 'void' === $paramsStr) { + if (empty($paramsStr) || $paramsStr === 'void') { return []; } @@ -266,23 +400,23 @@ class Extractor */ private function splitParameters(string $paramsStr): array { - $params = []; + $params = []; $current = ''; - $depth = 0; - $length = strlen($paramsStr); + $depth = 0; + $length = strlen($paramsStr); - for ($i = 0; $i < $length; ++$i) { + for ($i = 0; $i < $length; $i++) { $char = $paramsStr[$i]; - if ('<' === $char || '(' === $char || '[' === $char) { - ++$depth; + if ($char === '<' || $char === '(' || $char === '[') { + $depth++; $current .= $char; - } elseif ('>' === $char || ')' === $char || ']' === $char) { - --$depth; + } elseif ($char === '>' || $char === ')' || $char === ']') { + $depth--; $current .= $char; - } elseif (',' === $char && 0 === $depth) { + } elseif ($char === ',' && $depth === 0) { $params[] = $current; - $current = ''; + $current = ''; } else { $current .= $char; } @@ -321,25 +455,6 @@ class Extractor ]; } - /** - * 批量提取多个文件. - */ - public function extractFromFiles(array $files, array $prefixes = ['php_']): array - { - $allFunctions = []; - - foreach ($files as $file) { - try { - $functions = $this->extractFunctions($file, $prefixes); - $allFunctions = array_merge($allFunctions, $functions); - } catch (Exception $e) { - $this->error("处理文件 {$file} 失败: ".$e->getMessage()); - } - } - - return $allFunctions; - } - /** * 输出信息. */ @@ -365,48 +480,33 @@ class Extractor exit(1); } - /** - * 提取函数并添加额外信息. - */ - public function extractWithMetadata(string $filename, array $prefixes = ['php_']): array - { - $functions = $this->extractFunctions($filename, $prefixes); - - // 添加额外的元数据 - foreach ($functions as &$func) { - $func['metadata'] = $this->extractMetadata($filename, $func); - } - - return $functions; - } - /** * 提取函数的元数据(注释、属性等). */ private function extractMetadata(string $filename, array $func): array { $lineNum = $func['location']['line']; - $lines = file($filename, FILE_IGNORE_NEW_LINES); + $lines = file($filename, FILE_IGNORE_NEW_LINES); $metadata = [ - 'comments' => [], - 'attributes' => [], + 'comments' => [], + 'attributes' => [], 'isPHPFunction' => false, - 'isStatic' => false, - 'isInline' => false, + 'isStatic' => false, + 'isInline' => false, ]; // 提取函数前的注释 - $comments = $this->extractComments($lines, $lineNum); + $comments = $this->extractComments($lines, $lineNum); $metadata['comments'] = $comments; // 检测 PHP 函数宏 $metadata['isPHPFunction'] = $this->isPHPFunction($func['signature']); // 检测修饰符 - $signature = $func['signature']; - $metadata['isStatic'] = false !== strpos($signature, 'static'); - $metadata['isInline'] = false !== strpos($signature, 'inline'); + $signature = $func['signature']; + $metadata['isStatic'] = strpos($signature, 'static') !== false; + $metadata['isInline'] = strpos($signature, 'inline') !== false; // 提取文档注释中的标签 $metadata['docTags'] = $this->parseDocTags($comments); @@ -420,7 +520,7 @@ class Extractor private function extractComments(array $lines, int $lineNum): array { $comments = []; - $i = $lineNum - 2; // 从函数声明的前一行开始 + $i = $lineNum - 2; // 从函数声明的前一行开始 // 向上查找注释 while ($i >= 0) { @@ -428,21 +528,21 @@ class Extractor // 空行 if (empty($line)) { - --$i; + $i--; continue; } // C++ 风格注释 if (str_starts_with($line, '//')) { array_unshift($comments, substr($line, 2)); - --$i; + $i--; continue; } // C 风格注释结束 if (str_ends_with($line, '*/')) { $commentLines = [$line]; - --$i; + $i--; // 继续向上查找注释开始 while ($i >= 0) { @@ -452,7 +552,7 @@ class Extractor if (str_starts_with($commentLine, '/*')) { break; } - --$i; + $i--; } // 解析多行注释 @@ -462,7 +562,7 @@ class Extractor $comment = preg_replace('#^\s*\*\s?#m', '', $comment); array_unshift($comments, trim($comment)); - --$i; + $i--; continue; } @@ -486,7 +586,7 @@ class Extractor ]; foreach ($phpMacros as $macro) { - if (false !== strpos($signature, $macro)) { + if (strpos($signature, $macro) !== false) { return true; } } @@ -505,7 +605,7 @@ class Extractor // 匹配 @tag 格式 if (preg_match_all('/@(\w+)\s+(.*)$/m', $comment, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { - $tagName = $match[1]; + $tagName = $match[1]; $tagValue = trim($match[2]); if (!isset($tags[$tagName])) { @@ -519,97 +619,4 @@ class Extractor return $tags; } - - /** - * 生成函数统计信息. - */ - public function generateStatistics(array $functions): array - { - $stats = [ - 'total' => count($functions), - 'byReturnType' => [], - 'byParameterCount' => [], - 'byPrefix' => [], - 'withComments' => 0, - 'isPHPFunction' => 0, - ]; - - foreach ($functions as $func) { - // 按返回类型统计 - $returnType = $func['returnType']; - $stats['byReturnType'][$returnType] = - ($stats['byReturnType'][$returnType] ?? 0) + 1; - - // 按参数数量统计 - $paramCount = count($func['parameters']); - $stats['byParameterCount'][$paramCount] = - ($stats['byParameterCount'][$paramCount] ?? 0) + 1; - - // 按前缀统计 - $name = $func['name']; - $prefix = preg_match('/^([a-z_]+_)/i', $name, $m) ? $m[1] : 'other'; - $stats['byPrefix'][$prefix] = - ($stats['byPrefix'][$prefix] ?? 0) + 1; - - // 有注释的函数 - if (!empty($func['metadata']['comments'])) { - ++$stats['withComments']; - } - - // PHP 函数宏 - if ($func['metadata']['isPHPFunction'] ?? false) { - ++$stats['isPHPFunction']; - } - } - - return $stats; - } - - /** - * 导出为 Markdown 文档. - */ - public function exportToMarkdown(array $functions, string $title = 'API 文档'): string - { - $md = "# {$title}\n\n"; - $md .= '生成时间: '.date('Y-m-d H:i:s')."\n\n"; - $md .= '总计: '.count($functions)." 个函数\n\n"; - $md .= "---\n\n"; - - foreach ($functions as $func) { - $md .= "## {$func['name']}\n\n"; - - // 签名 - $md .= "```c\n{$func['signature']}\n```\n\n"; - - // 返回类型 - $md .= "**返回类型**: `{$func['returnType']}`\n\n"; - - // 参数 - if (!empty($func['parameters'])) { - $md .= "**参数**:\n\n"; - foreach ($func['parameters'] as $param) { - $name = $param['name'] ?: '(unnamed)'; - $md .= "- `{$param['type']}` **{$name}**\n"; - } - $md .= "\n"; - } else { - $md .= "**参数**: 无\n\n"; - } - - // 注释 - if (!empty($func['metadata']['comments'])) { - $md .= "**说明**:\n\n"; - foreach ($func['metadata']['comments'] as $comment) { - $md .= $comment."\n\n"; - } - } - - // 位置 - $md .= "**位置**: {$func['location']['file']}:{$func['location']['line']}\n\n"; - - $md .= "---\n\n"; - } - - return $md; - } } diff --git a/src/Php/FileScanner.php b/src/Php/FileScanner.php index 4343273a..abb6a194 100644 --- a/src/Php/FileScanner.php +++ b/src/Php/FileScanner.php @@ -1,22 +1,32 @@ directory = rtrim($directory, DIRECTORY_SEPARATOR); + $this->directory = rtrim($directory, DIRECTORY_SEPARATOR); $this->excludePatterns = []; } @@ -59,22 +69,9 @@ class FileScanner return $this->directory; } - private function isExcluded(string $filePath): bool - { - $excluded = false; - foreach ($this->excludePatterns as $pattern) { - if ($this->matchPattern($pattern, $filePath)) { - $excluded = true; - break; - } - } - - return $excluded; - } - public function scan(): array { - $files = []; + $files = []; $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS) ); @@ -97,6 +94,19 @@ class FileScanner return $files; } + private function isExcluded(string $filePath): bool + { + $excluded = false; + foreach ($this->excludePatterns as $pattern) { + if ($this->matchPattern($pattern, $filePath)) { + $excluded = true; + break; + } + } + + return $excluded; + } + private function matchPattern(string $pattern, string $path): bool { return fnmatch($pattern, $path, FNM_PATHNAME); diff --git a/src/Php/FileSorter.php b/src/Php/FileSorter.php index 0b6c87c9..4646f4b4 100644 --- a/src/Php/FileSorter.php +++ b/src/Php/FileSorter.php @@ -1,10 +1,19 @@ $degree) { - if (0 === $degree) { + if ($degree === 0) { $queue[] = $file; } } $sorted = []; while (!empty($queue)) { - $current = array_shift($queue); + $current = array_shift($queue); $sorted[] = $current; if (isset($dependencies[$current])) { foreach ($dependencies[$current] as $dep) { - --$inDegree[$dep]; - if (0 === $inDegree[$dep]) { + $inDegree[$dep]--; + if ($inDegree[$dep] === 0) { $queue[] = $dep; } } @@ -60,7 +69,7 @@ class FileSorter $dependencies = []; foreach ($this->functionCallInFile as $call) { - $callerFile = $call['file']; + $callerFile = $call['file']; $functionName = $call['name']; if (isset($this->functionDeclInFile[$functionName])) { diff --git a/src/Php/FuncCallOptimizer.php b/src/Php/FuncCallOptimizer.php index 24a227c5..dace99d5 100644 --- a/src/Php/FuncCallOptimizer.php +++ b/src/Php/FuncCallOptimizer.php @@ -1,4 +1,12 @@ parseIdentifier($expr->args[0]->value).')'; + if ($name === 'strlen' or $name === 'sizeof' or $name === 'count') { + return 'php::len(' . $this->parseIdentifier($expr->args[0]->value) . ')'; } - if (1 == count($expr->args)) { + if (count($expr->args) == 1) { switch ($name) { case 'intval': return $this->convertIntExpr($this->parseExpr($expr->args[0]->value)); @@ -24,7 +32,7 @@ trait FuncCallOptimizer default: break; } - } elseif (2 == count($expr->args)) { + } elseif (count($expr->args) == 2) { switch ($name) { case 'objval': $arg1 = $expr->args[0]->value; @@ -35,8 +43,8 @@ trait FuncCallOptimizer break; } } - if ('abs' === $name) { - return 'php::math::abs('.$this->parseIdentifier($expr->args[0]->value).')'; + if ($name === 'abs') { + return 'php::math::abs(' . $this->parseIdentifier($expr->args[0]->value) . ')'; } return false; diff --git a/src/Php/FunctionDef.php b/src/Php/FunctionDef.php index 56f04c39..b2900025 100644 --- a/src/Php/FunctionDef.php +++ b/src/Php/FunctionDef.php @@ -1,22 +1,35 @@ */ public array $argInfoList = []; + public int $argCountRequired = 0; + public string $params = ''; + public bool $method = false; public function __construct(string $name, string $returnType) { - $this->name = $name; + $this->name = $name; $this->returnType = $returnType; } } diff --git a/src/Php/InterfaceDef.php b/src/Php/InterfaceDef.php index 058ee176..68b0637e 100644 --- a/src/Php/InterfaceDef.php +++ b/src/Php/InterfaceDef.php @@ -1,4 +1,12 @@ functionDef->argInfoList)) { - $this->fatalError($v, 'Method '.$this->class."::$name() must take exactly 2 arguments"); + if ($name == '__call' or $name == '__callStatic' or $name == '__set') { + if (count($methodDef->functionDef->argInfoList) != 2) { + $this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take exactly 2 arguments"); } - } elseif ('__get' == $name) { - if (1 != count($methodDef->functionDef->argInfoList)) { - $this->fatalError($v, 'Method '.$this->class."::$name() must take exactly 1 argument"); + } elseif ($name == '__get') { + if (count($methodDef->functionDef->argInfoList) != 1) { + $this->fatalError($v, 'Method ' . $this->class . "::{$name}() must take exactly 1 argument"); } } } diff --git a/src/Php/MethodDef.php b/src/Php/MethodDef.php index 11c1d15c..f455a8df 100644 --- a/src/Php/MethodDef.php +++ b/src/Php/MethodDef.php @@ -1,18 +1,28 @@ flags = $flags; - $this->name = $name; - $this->functionDef = $def; + $this->flags = $flags; + $this->name = $name; + $this->functionDef = $def; $this->functionDef->method = true; } diff --git a/src/Php/Preprocessor.php b/src/Php/Preprocessor.php index cef6797e..edfa5680 100644 --- a/src/Php/Preprocessor.php +++ b/src/Php/Preprocessor.php @@ -1,4 +1,12 @@ functionCallInFile[$k]); } } - $sorter = new FileSorter($this->functionDeclInFile, $this->functionCallInFile); + $sorter = new FileSorter($this->functionDeclInFile, $this->functionCallInFile); $sortedFiles = $sorter->sort(); foreach ($list as $file) { @@ -28,41 +36,18 @@ class Preprocessor extends CompilerBase $list = $sortedFiles; } - protected function prepareNamespaceDef(Node\Stmt\Namespace_ $node): void - { - $this->resetNamespace(); - $this->namespace = $this->parseIdentifier($node->name); - foreach ($node->stmts as $v2) { - $type2 = $v2->getType(); - switch ($type2) { - case 'Stmt_Class': - $this->prepareClass($v2); - break; - case 'Stmt_Function': - $this->prepareFunction($v2).PHP_EOL; - break; - case 'Stmt_Use': - case 'Stmt_Const': - break; - default: - abort($v2); - } - } - $this->resetNamespace(); - } - public function getCppFile(string $file): string { $info = pathinfo($file); - return $this->buildDir.'/'.$this->removeCommonPrefix($this->buildDir, $info['dirname'].'/'.$info['filename'].'.cc'); + return $this->buildDir . '/' . $this->removeCommonPrefix($this->buildDir, $info['dirname'] . '/' . $info['filename'] . '.cc'); } public function getObjectFile(string $cppFile): string { $info = pathinfo($cppFile); - return $info['dirname'].'/'.$info['filename'].'.o'; + return $info['dirname'] . '/' . $info['filename'] . '.o'; } public function hasCppFileCache(string $file): bool @@ -81,14 +66,14 @@ class Preprocessor extends CompilerBase public function prepare(string $file): void { if ($this->hasCppFileCache($file)) { - $this->climate->darkGray('skip: '.$file.', cache exists'); + $this->climate->darkGray('skip: ' . $file . ', cache exists'); return; } $phpCode = $this->loadFile($file); - $this->climate->info('prepare: '.$this->file); + $this->climate->info('prepare: ' . $this->file); try { $ast = $this->parser->parse($phpCode); } catch (\PhpParser\Error $e) { @@ -111,7 +96,7 @@ class Preprocessor extends CompilerBase $this->prepareClass($v); break; case 'Stmt_Function': - $this->prepareFunction($v).PHP_EOL; + $this->prepareFunction($v) . PHP_EOL; break; case 'Stmt_Declare': case 'Stmt_Use': @@ -120,17 +105,17 @@ class Preprocessor extends CompilerBase case 'Stmt_Nop': break; default: - $this->fatalError($v, 'Unsupported statement: '.$type); + $this->fatalError($v, 'Unsupported statement: ' . $type); break; } } - $nodeFinder = new NodeFinder(); + $nodeFinder = new NodeFinder(); $functionCalls = $nodeFinder->findInstanceOf($ast, Node\Expr\FuncCall::class); foreach ($functionCalls as $call) { if ($call->name instanceof Node\Name) { - $name = $call->name->toString(); + $name = $call->name->toString(); $this->functionCallInFile[] = [ 'name' => $name, 'file' => $this->file, @@ -140,6 +125,29 @@ class Preprocessor extends CompilerBase } } + protected function prepareNamespaceDef(Node\Stmt\Namespace_ $node): void + { + $this->resetNamespace(); + $this->namespace = $this->parseIdentifier($node->name); + foreach ($node->stmts as $v2) { + $type2 = $v2->getType(); + switch ($type2) { + case 'Stmt_Class': + $this->prepareClass($v2); + break; + case 'Stmt_Function': + $this->prepareFunction($v2) . PHP_EOL; + break; + case 'Stmt_Use': + case 'Stmt_Const': + break; + default: + abort($v2); + } + } + $this->resetNamespace(); + } + protected function prepareFunction(Node $v): void { $name = $this->getFunctionName($v); @@ -153,7 +161,7 @@ class Preprocessor extends CompilerBase protected function prepareClass(Node\Stmt\Class_|Node\Stmt\Trait_ $class): string { $this->class = $this->parseIdentifier($class->name); - $code = ''; + $code = ''; foreach ($class->stmts as $v) { $type = $v->getType(); switch ($type) { @@ -163,7 +171,7 @@ class Preprocessor extends CompilerBase case 'Stmt_TraitUse': break; case 'Stmt_ClassMethod': - $code .= $this->prepareFunction($v).PHP_EOL; + $code .= $this->prepareFunction($v) . PHP_EOL; break; default: abort($v); diff --git a/src/Php/PropertyDef.php b/src/Php/PropertyDef.php index 56f9bd3f..f38006ff 100644 --- a/src/Php/PropertyDef.php +++ b/src/Php/PropertyDef.php @@ -1,4 +1,12 @@ flags = $flags; - $this->name = $name; - $this->type = $type; + $this->flags = $flags; + $this->name = $name; + $this->type = $type; $this->default = $default; } diff --git a/src/Php/RedoException.php b/src/Php/RedoException.php index 2238636e..f6251c6c 100644 --- a/src/Php/RedoException.php +++ b/src/Php/RedoException.php @@ -1,4 +1,12 @@ climate->arguments->add(require __DIR__.'/../config/compiler_options.php'); + $this->climate->arguments->add(require __DIR__ . '/../config/compiler_options.php'); $this->preprocessArgvAdvanced(); $this->climate->arguments->parse(); $this->optimizeLevel = $this->climate->arguments->get('optimize'); - $this->buildMode = $this->climate->arguments->get('mode'); - $this->debugLine = intval($this->climate->arguments->get('debug-line')); + $this->buildMode = $this->climate->arguments->get('mode'); + $this->debugLine = intval($this->climate->arguments->get('debug-line')); // $this->noLiteralStrings = $this->climate->arguments->get('noLiteralStrings'); - $this->noLiteralStrings = true; - $this->enableProfiler = $this->climate->arguments->defined('profile'); + $this->noLiteralStrings = true; + $this->enableProfiler = $this->climate->arguments->defined('profile'); $this->internalFunctions = array_flip(get_defined_functions()['internal']); if ($this->climate->arguments->defined('help')) { $this->showUsage(); @@ -83,7 +94,7 @@ class Translator extends Preprocessor public function convert(string $file): string { if ($this->hasCppFileCache($file)) { - $this->climate->darkGray('skip: '.$file.', cache exists'); + $this->climate->darkGray('skip: ' . $file . ', cache exists'); return $this->getCppFile($file); } @@ -104,62 +115,246 @@ class Translator extends Preprocessor } } - protected function getRegisterClassFunction(string $name): string + public function getRegisterClassFunctionArgs(ClassDef|InterfaceDef $classDef): string { - return self::PREFIX.'register_class_'.$name; + return implode(', ', $this->getRegisterClassFunctionCeList($classDef)); } - protected function getRegisterClassFunctionCeList(ClassDef|InterfaceDef $classDef): array + public function setTargetName(string $name): void { - $list = []; - $parentCe = $this->getParentClassCe($classDef); - if ('' !== $parentCe) { - $list = [$parentCe]; + if ($this->climate->arguments->defined('output')) { + $name = $this->climate->arguments->get('output'); } - // interface 没有 implements - if ($classDef instanceof InterfaceDef) { - return $list; + $name = str_replace(['-', '*'], '_', $name); + if (!preg_match('/^[a-zA-Z0-9_]+$/', $name)) { + $this->climate->red('The target name must be a valid identifier'); + exit(1); } - $implements = $this->getImplementCe($classDef); + if (in_array($name, Constants::CPP_RESERVED_NAMES)) { + $this->climate->red('The target name must not be a reserved keyword'); + exit(1); + } + $this->targetName = $name; + } - return array_merge($list, $implements); + public function getFiles(string $path): array + { + $realpath = realpath($path); + if ($realpath === false) { + exit("path not exists: {$path}\n"); + } + $path = $realpath; + + if (is_dir($path)) { + $list = $this->getFilesFromDir($path); + $targetName = basename($path); + $this->setTargetName($targetName); + } else { + $ext = pathinfo($path, PATHINFO_EXTENSION); + if ($ext === 'yml') { + $list = $this->parseProjectYaml($path); + } elseif ($ext === 'php') { + $list = [$path]; + $targetName = FileScanner::getFileName($path); + $this->setTargetName($targetName); + } else { + $this->error('Unsupported file type: ' . $path); + } + } + + return $list; } - public function getRegisterClassFunctionArgs(ClassDef|InterfaceDef $classDef): string + public function preprocessArgvAdvanced(): void { - return implode(', ', $this->getRegisterClassFunctionCeList($classDef)); + global $argv; + $processed = [$argv[0]]; + + for ($i = 1; $i < count($argv); $i++) { + $arg = $argv[$i]; + if (preg_match('/^-([a-zA-Z])(.+)$/', $arg, $matches)) { + $option = $matches[1]; + $value = $matches[2]; + $processed[] = "-{$option}"; + $processed[] = $value; + } elseif (preg_match('/^-([a-zA-Z]{2,})$/', $arg, $matches)) { + $options = str_split($matches[1]); + foreach ($options as $opt) { + $processed[] = "-{$opt}"; + } + } else { + $processed[] = $arg; + } + } + $argv = $processed; } - private function getRegisterClassFunctionArgDef(ClassDef|InterfaceDef $classDef): string + public function genExternGlobalVars(string $file): void { - $depsCeList = $this->getRegisterClassFunctionCeList($classDef); - if (empty($depsCeList)) { - return ''; + $lines[] = '#include '; + $lines[] = PHP_EOL; + foreach ($this->globalVars as $name => $type) { + $lines[] = 'extern ' . self::TYPE_VAR . ' ' . $name . ';'; + } + + // property offset + foreach ($this->classes as $classDef) { + foreach ($classDef->properties as $propertyDef) { + $lines[] = 'extern uint32_t ' . self::PREFIX . $this->getPropertyOffset($propertyDef->name, $classDef->name, $classDef->namespace) . ';'; + } } - return 'zend_class_entry *'.implode(', zend_class_entry *', $depsCeList); + $literalStringsCount = count($this->literalStrings); + $lines[] = 'extern php::Var ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '];' . PHP_EOL; + + $classEntryCount = count($this->classMap); + $lines[] = 'extern zend_class_entry *' . self::PREFIX . self::CLASS_ENTRY_MAP . '[' . $classEntryCount . '];' . PHP_EOL; + + $code = implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL; + $this->writeFile($file, $code); } - protected function getClassCe(ClassLikeDef $classDef): string + public function genExtension(string $file): void + { + if ($this->buildMode == 'bin') { + if (!isset($this->nativeFunctions['main'])) { + $this->climate->red('When the build mode is a binary executable file, the `main()` function must be defined'); + exit(1); + } + } + $this->localHeaders = $this->argInfoHeaderFiles; + $this->genClassCeList(); + $code = $this->render('extension.cc.php'); + $this->writeFile($file, $code); + $this->formatCppCode($file); + $this->localHeaders = []; + } + + public function hasObjectFileCache(string $cppFile): bool { - return self::PREFIX.'class_entry_'.$classDef->getNamespacedName(); + if (!$this->enableCache or $this->climate->arguments->defined('force')) { + return false; + } + $objectFile = $this->getObjectFile($cppFile); + if (file_exists($objectFile) and filemtime($objectFile) > filemtime($cppFile)) { + return true; + } + + return false; } - public function setTargetName(string $name): void + public function compileFile(string $cppFile, string $objectFile): void { - if ($this->climate->arguments->defined('output')) { - $name = $this->climate->arguments->get('output'); + if ($this->hasObjectFileCache($cppFile)) { + $this->climate->darkGray('skip: ' . $cppFile . ', cache exists'); + + return; } - $name = str_replace(['-', '*'], '_', $name); - if (!preg_match('/^[a-zA-Z0-9_]+$/', $name)) { - $this->climate->red('The target name must be a valid identifier'); - exit(1); + $cmd = $this->cppCompiler . ' -c ' . $cppFile . ' -o ' . $objectFile; + $this->addCompilationOption($cmd, false); + $this->climate->comment($cmd); + shell_exec($cmd); + } + + public function build(array $objectFiles): void + { + $objectList = implode(' ', $objectFiles); + $targetFile = $this->targetName; + if ($this->buildMode == 'ext' and !str_ends_with($targetFile, '.so')) { + $targetFile .= '.so'; } - if (in_array($name, Constants::CPP_RESERVED_NAMES)) { - $this->climate->red('The target name must not be a reserved keyword'); - exit(1); + $linkCmd = $this->cppCompiler . ' ' . $objectList . ' -o ' . $targetFile; + $this->addCompilationOption($linkCmd, true); + $this->climate->comment($linkCmd); + shell_exec($linkCmd); + } + + public function genFunctionDeclaration(string $file): void + { + $code = '#include ' . PHP_EOL; + /** + * @var FunctionDef $func + */ + foreach ($this->nativeFunctions as $name => $func) { + $code .= 'extern ' . $func->returnType . ' ' . self::PREFIX . $name . '('; + $argInfoList = $func->argInfoList; + if ($argInfoList) { + $list = []; + if ($func->method) { + $list[] = 'php::Object &this_'; + } + foreach ($argInfoList as $argInfo) { + $arg = $argInfo->type . ' ' . $argInfo->name; + if ($argInfo->default) { + $arg .= ' = ' . $argInfo->default; + } + $list[] = $arg; + } + $code .= implode(', ', $list); + } + $code .= ');' . PHP_EOL; } - $this->targetName = $name; + + $code .= PHP_EOL; + foreach ($this->nativeConstants as $name => $constant) { + $code .= 'extern ' . $constant->type . ' ' . $name . ';' . PHP_EOL; + } + + $code .= 'extern zend_class_entry *php_get_class_entry(int class_id, const char *class_name);' . PHP_EOL; + + $this->writeFile($file, $code); + } + + public function getBuildMode(): string + { + return $this->buildMode; + } + + public function getArgInfoHeaderFile(string $stubFilenameWithoutExtension, bool $relative = false): string + { + $basename = self::PREFIX . basename($stubFilenameWithoutExtension); + $absPath = $this->getIncludeDir() . "/{$basename}_arginfo.h"; + if ($relative) { + return ltrim($this->removeCommonPrefix($this->getIncludeDir(), $absPath), '/'); + } + return $absPath; + } + + public function genIncludeHeaderFiles(): string + { + $headers = array_merge($this->globalHeaders, $this->localHeaders); + $lines = []; + foreach ($headers as $header) { + $lines[] = '#include <' . $header . '>'; + } + + return implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL; + } + + protected function getRegisterClassFunction(string $name): string + { + return self::PREFIX . 'register_class_' . $name; + } + + protected function getRegisterClassFunctionCeList(ClassDef|InterfaceDef $classDef): array + { + $list = []; + $parentCe = $this->getParentClassCe($classDef); + if ($parentCe !== '') { + $list = [$parentCe]; + } + // interface 没有 implements + if ($classDef instanceof InterfaceDef) { + return $list; + } + $implements = $this->getImplementCe($classDef); + + return array_merge($list, $implements); + } + + protected function getClassCe(ClassLikeDef $classDef): string + { + return self::PREFIX . 'class_entry_' . $classDef->getNamespacedName(); } protected function getFilesFromDir(string $path): array @@ -171,27 +366,27 @@ class Translator extends Preprocessor protected function parseProjectYaml(string $path): array { - $cfg = Yaml::parseFile($path); + $cfg = Yaml::parseFile($path); $projectDir = dirname($path); if (!empty($cfg['sources'])) { $sources = $cfg['sources']; - $list = []; + $list = []; foreach ($sources as $src) { $src = trim($src); - if ('/' != $src[0]) { - $absPath = $projectDir.'/'.$src; + if ($src[0] != '/') { + $absPath = $projectDir . '/' . $src; } else { $absPath = $src; } $realPath = realpath($absPath); if (!$realPath) { - $this->error('Source file not exists: `'.$src.'`'); + $this->error('Source file not exists: `' . $src . '`'); } if (is_file($realPath)) { $list[] = $realPath; } else { - $tmp = $this->getFilesFromDir($realPath); + $tmp = $this->getFilesFromDir($realPath); $list = array_merge($list, $tmp); } } @@ -219,39 +414,11 @@ class Translator extends Preprocessor return $list; } - public function getFiles(string $path): array - { - $realpath = realpath($path); - if (false === $realpath) { - exit("path not exists: $path\n"); - } - $path = $realpath; - - if (is_dir($path)) { - $list = $this->getFilesFromDir($path); - $targetName = basename($path); - $this->setTargetName($targetName); - } else { - $ext = pathinfo($path, PATHINFO_EXTENSION); - if ('yml' === $ext) { - $list = $this->parseProjectYaml($path); - } elseif ('php' === $ext) { - $list = [$path]; - $targetName = FileScanner::getFileName($path); - $this->setTargetName($targetName); - } else { - $this->error('Unsupported file type: '.$path); - } - } - - return $list; - } - protected function getInternalCeInfo(string $ce): array { return [ 'func' => 'php::getClassEntry', - 'args' => '"'.substr($ce, strlen(self::PREFIX.'class_entry_')).'"', + 'args' => '"' . substr($ce, strlen(self::PREFIX . 'class_entry_')) . '"', ]; } @@ -261,24 +428,14 @@ class Translator extends Preprocessor return ''; } - return self::PREFIX.'class_entry_'.$classDef->extends; - } - - private function getImplementCe(ClassDef $classDef): array - { - $list = []; - foreach ($classDef->implements as $interface) { - $list[] = self::PREFIX.'class_entry_'.$interface; - } - - return $list; + return self::PREFIX . 'class_entry_' . $classDef->extends; } protected function doConvert(string $phpCode): string { - $this->climate->info('convert: '.$this->file); + $this->climate->info('convert: ' . $this->file); - $ast = $this->parser->parse($phpCode); + $ast = $this->parser->parse($phpCode); $traverser = new NodeTraverser(); $traverser->addVisitor(new Visitor()); @@ -304,16 +461,16 @@ class Translator extends Preprocessor $cppCode .= $this->parseClass($v); break; case 'Stmt_Use': - $cppCode .= $this->parseUse($v).PHP_EOL; + $cppCode .= $this->parseUse($v) . PHP_EOL; break; case 'Stmt_Function': - $cppCode .= $this->parseFunction($v).PHP_EOL; + $cppCode .= $this->parseFunction($v) . PHP_EOL; break; case 'Stmt_Const': - $this->parseConstDef($v).PHP_EOL; + $this->parseConstDef($v) . PHP_EOL; break; case 'Stmt_Interface': - $this->parseInterface($v).PHP_EOL; + $this->parseInterface($v) . PHP_EOL; break; case 'Stmt_Nop': break; @@ -334,64 +491,7 @@ class Translator extends Preprocessor $cppCode .= $this->genFunctionWrapper($functionDef); } - return $this->genIncludeHeaderFiles().$cppCode; - } - - public function preprocessArgvAdvanced(): void - { - global $argv; - $processed = [$argv[0]]; - - for ($i = 1; $i < count($argv); ++$i) { - $arg = $argv[$i]; - if (preg_match('/^-([a-zA-Z])(.+)$/', $arg, $matches)) { - $option = $matches[1]; - $value = $matches[2]; - $processed[] = "-{$option}"; - $processed[] = $value; - } elseif (preg_match('/^-([a-zA-Z]{2,})$/', $arg, $matches)) { - $options = str_split($matches[1]); - foreach ($options as $opt) { - $processed[] = "-{$opt}"; - } - } else { - $processed[] = $arg; - } - } - $argv = $processed; - } - - public function genExternGlobalVars(string $file): void - { - $lines[] = '#include '; - $lines[] = PHP_EOL; - foreach ($this->globalVars as $name => $type) { - $lines[] = 'extern '.self::TYPE_VAR.' '.$name.';'; - } - - // property offset - foreach ($this->classes as $classDef) { - foreach ($classDef->properties as $propertyDef) { - $lines[] = 'extern uint32_t '.self::PREFIX.$this->getPropertyOffset($propertyDef->name, $classDef->name, $classDef->namespace).';'; - } - } - - $literalStringsCount = count($this->literalStrings); - $lines[] = 'extern php::Var '.self::LITERAL_STRINGS.'['.$literalStringsCount.'];'.PHP_EOL; - - $classEntryCount = count($this->classMap); - $lines[] = 'extern zend_class_entry *'.self::PREFIX.self::CLASS_ENTRY_MAP.'['.$classEntryCount.'];'.PHP_EOL; - - $code = implode(PHP_EOL, $lines).PHP_EOL.PHP_EOL; - $this->writeFile($file, $code); - } - - private function render(string $template): string - { - ob_start(); - include __DIR__.'/../template/'.$template; - - return ob_get_clean(); + return $this->genIncludeHeaderFiles() . $cppCode; } protected function genClassCeList(): void @@ -404,152 +504,61 @@ class Translator extends Preprocessor foreach ($this->interfaces as $interfaceDef) { $parent = $interfaceDef->extends; - $ce = $this->getClassCe($interfaceDef); - $deps = []; - - if ($parent) { - // 不存在的接口,说明可能是内置接口 - $tmpCe = $this->getParentClassCe($interfaceDef); - if (!isset($this->interfaces[$parent])) { - $sorter->add($tmpCe); - } - $deps[] = $tmpCe; - } - - $this->classCeInfo[$ce] = [ - 'deps' => $deps, - 'func' => $this->getRegisterClassFunction($interfaceDef->getNamespacedName()), - 'args' => $this->getRegisterClassFunctionArgs($interfaceDef), - 'argDef' => $this->getRegisterClassFunctionArgDef($interfaceDef), - ]; - $sorter->add($ce, $deps); - } + $ce = $this->getClassCe($interfaceDef); + $deps = []; - foreach ($this->classes as $classDef) { - $ce = $this->getClassCe($classDef); - $deps = []; - $parent = $classDef->extends; if ($parent) { - // 不存在的父类,说明可能是内置类 - $tmpCe = $this->getParentClassCe($classDef); - if (!isset($this->classes[$parent])) { - $sorter->add($tmpCe); - } - $deps[] = $tmpCe; - } - - $implements = $classDef->implements; - if ($implements) { - foreach ($implements as $interface) { - $tmpCe = self::PREFIX.'class_entry_'.$interface; - if (!isset($this->interfaces[$interface])) { - $sorter->add($tmpCe); - } - $deps[] = $tmpCe; - } - } - - $this->classCeInfo[$ce] = [ - 'deps' => $deps, - 'func' => $this->getRegisterClassFunction($classDef->getNamespacedName()), - 'args' => $this->getRegisterClassFunctionArgs($classDef), - 'argDef' => $this->getRegisterClassFunctionArgDef($classDef), - ]; - $sorter->add($ce, $deps); - } - - $this->classCeList = $sorter->sort(); - } - - public function genExtension(string $file): void - { - if ('bin' == $this->buildMode) { - if (!isset($this->nativeFunctions['main'])) { - $this->climate->red('When the build mode is a binary executable file, the `main()` function must be defined'); - exit(1); - } - } - $this->localHeaders = $this->argInfoHeaderFiles; - $this->genClassCeList(); - $code = $this->render('extension.cc.php'); - $this->writeFile($file, $code); - $this->formatCppCode($file); - $this->localHeaders = []; - } - - public function hasObjectFileCache(string $cppFile): bool - { - if (!$this->enableCache or $this->climate->arguments->defined('force')) { - return false; - } - $objectFile = $this->getObjectFile($cppFile); - if (file_exists($objectFile) and filemtime($objectFile) > filemtime($cppFile)) { - return true; - } - - return false; - } - - public function compileFile(string $cppFile, string $objectFile): void - { - if ($this->hasObjectFileCache($cppFile)) { - $this->climate->darkGray('skip: '.$cppFile.', cache exists'); - - return; - } - $cmd = $this->cppCompiler.' -c '.$cppFile.' -o '.$objectFile; - $this->addCompilationOption($cmd, false); - $this->climate->comment($cmd); - shell_exec($cmd); - } + // 不存在的接口,说明可能是内置接口 + $tmpCe = $this->getParentClassCe($interfaceDef); + if (!isset($this->interfaces[$parent])) { + $sorter->add($tmpCe); + } + $deps[] = $tmpCe; + } - public function build(array $objectFiles): void - { - $objectList = implode(' ', $objectFiles); - $targetFile = $this->targetName; - if ('ext' == $this->buildMode and !str_ends_with($targetFile, '.so')) { - $targetFile .= '.so'; + $this->classCeInfo[$ce] = [ + 'deps' => $deps, + 'func' => $this->getRegisterClassFunction($interfaceDef->getNamespacedName()), + 'args' => $this->getRegisterClassFunctionArgs($interfaceDef), + 'argDef' => $this->getRegisterClassFunctionArgDef($interfaceDef), + ]; + $sorter->add($ce, $deps); } - $linkCmd = $this->cppCompiler.' '.$objectList.' -o '.$targetFile; - $this->addCompilationOption($linkCmd, true); - $this->climate->comment($linkCmd); - shell_exec($linkCmd); - } - public function genFunctionDeclaration(string $file): void - { - $code = '#include '.PHP_EOL; - /** - * @var FunctionDef $func - */ - foreach ($this->nativeFunctions as $name => $func) { - $code .= 'extern '.$func->returnType.' '.self::PREFIX.$name.'('; - $argInfoList = $func->argInfoList; - if ($argInfoList) { - $list = []; - if ($func->method) { - $list[] = 'php::Object &this_'; + foreach ($this->classes as $classDef) { + $ce = $this->getClassCe($classDef); + $deps = []; + $parent = $classDef->extends; + if ($parent) { + // 不存在的父类,说明可能是内置类 + $tmpCe = $this->getParentClassCe($classDef); + if (!isset($this->classes[$parent])) { + $sorter->add($tmpCe); } - foreach ($argInfoList as $argInfo) { - $arg = $argInfo->type.' '.$argInfo->name; - if ($argInfo->default) { - $arg .= ' = '.$argInfo->default; + $deps[] = $tmpCe; + } + + $implements = $classDef->implements; + if ($implements) { + foreach ($implements as $interface) { + $tmpCe = self::PREFIX . 'class_entry_' . $interface; + if (!isset($this->interfaces[$interface])) { + $sorter->add($tmpCe); } - $list[] = $arg; + $deps[] = $tmpCe; } - $code .= implode(', ', $list); } - $code .= ');'.PHP_EOL; - } - $code .= PHP_EOL; - foreach ($this->nativeConstants as $name => $constant) { - $code .= 'extern '.$constant->type.' '.$name.';'.PHP_EOL; + $this->classCeInfo[$ce] = [ + 'deps' => $deps, + 'func' => $this->getRegisterClassFunction($classDef->getNamespacedName()), + 'args' => $this->getRegisterClassFunctionArgs($classDef), + 'argDef' => $this->getRegisterClassFunctionArgDef($classDef), + ]; + $sorter->add($ce, $deps); } - $code .= 'extern zend_class_entry *php_get_class_entry(int class_id, const char *class_name);'.PHP_EOL; - - $this->writeFile($file, $code); + $this->classCeList = $sorter->sort(); } protected function getMethodName(Node\Stmt\ClassMethod $v): string @@ -564,7 +573,7 @@ class Translator extends Preprocessor protected function parseNamespace(Node\Stmt\Namespace_ $node): string { - $ns = $this->parseIdentifier($node->name); + $ns = $this->parseIdentifier($node->name); $code = ''; $this->resetNamespace(); @@ -572,16 +581,16 @@ class Translator extends Preprocessor if ($this->useCppNamespace) { $ns = explode('\\', $ns); $ns = array_filter($ns, function ($v) { - return '' !== $v; + return $v !== ''; }); foreach ($ns as $name) { - $code .= 'namespace '.$name.' {'.PHP_EOL; + $code .= 'namespace ' . $name . ' {' . PHP_EOL; } - $ns_end = str_repeat('}', count($ns)); + $ns_end = str_repeat('}', count($ns)); $this->namespace = implode('::', $ns); } else { $this->namespace = $ns; - $ns_end = ''; + $ns_end = ''; } foreach ($node->stmts as $v2) { @@ -591,13 +600,13 @@ class Translator extends Preprocessor $code .= $this->parseClass($v2); break; case 'Stmt_Const': - $this->parseConstDef($v2).PHP_EOL; + $this->parseConstDef($v2) . PHP_EOL; break; case 'Stmt_Function': - $code .= $this->parseFunction($v2).PHP_EOL; + $code .= $this->parseFunction($v2) . PHP_EOL; break; case 'Stmt_Use': - $code .= $this->parseUse($v2).PHP_EOL; + $code .= $this->parseUse($v2) . PHP_EOL; break; default: abort($v2); @@ -611,21 +620,21 @@ class Translator extends Preprocessor protected function genStubFile(string $file): void { - $genStubCmd = PHP_BINARY.' '.$this->rootPath.'/bin/gen_stub.php -f '.$file; - $output = shell_exec($genStubCmd); - $this->climate->info('generate stub file: '.$file); + $genStubCmd = PHP_BINARY . ' ' . $this->rootPath . '/bin/gen_stub.php -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); + $headerFile = $this->getArgInfoHeaderFile($stubFilenameWithoutExtension, true); if (!str_contains($output, 'Saved')) { - $this->error("failed to generate arginfo header file: `$headerFile`, output: $output"); + $this->error("failed to generate arginfo header file: `{$headerFile}`, output: {$output}"); } $this->argInfoHeaderFiles[] = $headerFile; } protected function parseClass(Node\Stmt\Class_|Node\Stmt\Trait_ $class): string { - $this->class = $this->parseIdentifier($class->name); + $this->class = $this->parseIdentifier($class->name); $this->classDef = new ClassDef($this->class, $class->flags, $this->namespace); if ($class->extends) { $this->classDef->extends = $this->parseIdentifier($class->extends); @@ -638,8 +647,8 @@ class Translator extends Preprocessor } $this->classDef->implements = $this->parseIdentifierList($class->implements); - $className = $this->classDef->getNamespacedName(); - $this->classes[$className] = $this->classDef; + $className = $this->classDef->getNamespacedName(); + $this->classes[$className] = $this->classDef; $this->classesDefineInFile[$className] = $this->classDef; $methodCodes = []; @@ -671,28 +680,12 @@ class Translator extends Preprocessor return $code; } - public function getBuildMode(): string - { - return $this->buildMode; - } - - public function getArgInfoHeaderFile(string $stubFilenameWithoutExtension, bool $relative = false): string - { - $basename = self::PREFIX.basename($stubFilenameWithoutExtension); - $absPath = $this->getIncludeDir()."/{$basename}_arginfo.h"; - if ($relative) { - return ltrim($this->removeCommonPrefix($this->getIncludeDir(), $absPath), '/'); - } else { - return $absPath; - } - } - protected function genNativeMethod($methodCodes): string { - $code = ''; + $code = ''; $classDef = $this->classDef; foreach ($classDef->methods as $method) { - $code .= $methodCodes[$method->name].PHP_EOL; + $code .= $methodCodes[$method->name] . PHP_EOL; } $code .= PHP_EOL; @@ -701,74 +694,64 @@ class Translator extends Preprocessor protected function genWrapperFunctionArgs(string $fn, FunctionDef $functionDef): string { - $cppCode = ''; + $cppCode = ''; $callParams = ''; foreach ($functionDef->argInfoList as $k => $argInfo) { if ($argInfo->default) { - $argExpr = 'php::getCallArg('.$k.', '.$argInfo->default.')'; + $argExpr = 'php::getCallArg(' . $k . ', ' . $argInfo->default . ')'; } else { - $argExpr = 'php::getCallArg('.$k.')'; + $argExpr = 'php::getCallArg(' . $k . ')'; } $expr = $this->convertExprFromType($argInfo->type, $argExpr); - $cppCode .= $this->getIndent().$argInfo->type.' arg_'.$argInfo->name.' = '.$expr.';'.PHP_EOL; - $callParams .= 'arg_'.$argInfo->name.','; + $cppCode .= $this->getIndent() . $argInfo->type . ' arg_' . $argInfo->name . ' = ' . $expr . ';' . PHP_EOL; + $callParams .= 'arg_' . $argInfo->name . ','; } if ($functionDef->method) { - $callParams = $functionDef->argInfoList ? 'this_, '.rtrim($callParams, ',') : 'this_'; + $callParams = $functionDef->argInfoList ? 'this_, ' . rtrim($callParams, ',') : 'this_'; } else { $callParams = $functionDef->argInfoList ? rtrim($callParams, ',') : ''; } - if (self::TYPE_VOID !== $functionDef->returnType) { - $cppCode .= $this->getIndent().'auto retval = '.$fn.'('.$callParams.');'.PHP_EOL; - $cppCode .= $this->getIndent().'php::move(retval, return_value);'.PHP_EOL; + if ($functionDef->returnType !== self::TYPE_VOID) { + $cppCode .= $this->getIndent() . 'auto retval = ' . $fn . '(' . $callParams . ');' . PHP_EOL; + $cppCode .= $this->getIndent() . 'php::move(retval, return_value);' . PHP_EOL; } else { - $cppCode .= $this->getIndent().$fn.'('.$callParams.');'.PHP_EOL; + $cppCode .= $this->getIndent() . $fn . '(' . $callParams . ');' . PHP_EOL; } - $cppCode .= '}'.PHP_EOL.PHP_EOL; + $cppCode .= '}' . PHP_EOL . PHP_EOL; return $cppCode; } protected function genMethodWrapper(ClassDef $classDef, MethodDef $methodDef): string { - $name = $classDef->getNamespacedName(); - $cppCode = 'ZEND_METHOD('.$name.', '.$methodDef->name.'){'.PHP_EOL; - $cppCode .= $this->getIndent().self::TYPE_OBJECT.' this_(&execute_data->This);'.PHP_EOL; + $name = $classDef->getNamespacedName(); + $cppCode = 'ZEND_METHOD(' . $name . ', ' . $methodDef->name . '){' . PHP_EOL; + $cppCode .= $this->getIndent() . self::TYPE_OBJECT . ' this_(&execute_data->This);' . PHP_EOL; foreach ($classDef->properties as $property) { - if (self::TYPE_ARRAY === $property->type and $property->default and $property->default !== self::TYPE_ARRAY.'{}') { - $propOffset = self::PREFIX.$this->getPropertyOffset($property->name, $classDef->name, $classDef->namespace); - $cppCode .= $this->getIndent().'this_.getPropertyIndirect('.$propOffset.') = '.$property->default.';'.PHP_EOL; + if ($property->type === self::TYPE_ARRAY and $property->default and $property->default !== self::TYPE_ARRAY . '{}') { + $propOffset = self::PREFIX . $this->getPropertyOffset($property->name, $classDef->name, $classDef->namespace); + $cppCode .= $this->getIndent() . 'this_.getPropertyIndirect(' . $propOffset . ') = ' . $property->default . ';' . PHP_EOL; } } - $fn = self::PREFIX.$this->getNativeMethodName($classDef, $methodDef); + $fn = self::PREFIX . $this->getNativeMethodName($classDef, $methodDef); $cppCode .= $this->genWrapperFunctionArgs($fn, $methodDef->functionDef); return $cppCode; } - private function genFunctionWrapper(FunctionDef $functionDef): string - { - $name = $functionDef->name; - $cppCode = 'ZEND_FUNCTION('.$name.'){'.PHP_EOL; - $fn = self::PREFIX.$this->getNativeName($functionDef->name); - $cppCode .= $this->genWrapperFunctionArgs($fn, $functionDef); - - return $cppCode; - } - protected function getClassRegisterCeFunc(ClassDef|InterfaceDef $classDef): void { $cppCode = ''; - $name = $classDef->getNamespacedName(); + $name = $classDef->getNamespacedName(); $argsDef = $this->getRegisterClassFunctionArgDef($classDef); - $param = $this->getRegisterClassFunctionArgs($classDef); - $cppCode .= 'zend_class_entry *'.$this->getRegisterClassFunction($name).'('.$argsDef.') {'.PHP_EOL; - $cppCode .= $this->getIndent().'return register_class_'.$name.'('.$param.');'.PHP_EOL; - $cppCode .= '}'.PHP_EOL.PHP_EOL; + $param = $this->getRegisterClassFunctionArgs($classDef); + $cppCode .= 'zend_class_entry *' . $this->getRegisterClassFunction($name) . '(' . $argsDef . ') {' . PHP_EOL; + $cppCode .= $this->getIndent() . 'return register_class_' . $name . '(' . $param . ');' . PHP_EOL; + $cppCode .= '}' . PHP_EOL . PHP_EOL; } protected function genClassWrapper(ClassDef|InterfaceDef $classDef): string @@ -786,100 +769,6 @@ class Translator extends Preprocessor return $cppCode; } - private function genClassNative(): string - { - $code = 'class '.$this->class.' { '; - - $publicMethods = []; - $protectedMethods = []; - $privateMethods = []; - $publicConstants = []; - $protectedConstants = []; - $privateConstants = []; - $publicProperties = []; - $protectedProperties = []; - $privateProperties = []; - - foreach ($this->classDef->constants as $const) { - if ($const->flags & Modifiers::PUBLIC) { - $publicConstants[] = $const; - } - if ($const->flags & Modifiers::PROTECTED) { - $protectedConstants[] = $const; - } - if ($const->flags & Modifiers::PRIVATE) { - $privateConstants[] = $const; - } - } - foreach ($this->classDef->methods as $method) { - if ($method->flags & Modifiers::PUBLIC) { - $publicMethods[] = $method; - } - if ($method->flags & Modifiers::PROTECTED) { - $protectedMethods[] = $method; - } - if ($method->flags & Modifiers::PRIVATE) { - $privateMethods[] = $method; - } - } - foreach ($this->classDef->properties as $property) { - if ($property->flags & Modifiers::PUBLIC) { - $publicProperties[] = $property; - } - if ($property->flags & Modifiers::PROTECTED) { - $protectedProperties[] = $property; - } - if ($property->flags & Modifiers::PRIVATE) { - $privateProperties[] = $property; - } - } - - if ($privateConstants) { - $code .= 'private:'.PHP_EOL; - $code .= $this->genClassConstantList($privateConstants); - } - - if ($protectedConstants) { - $code .= 'protected:'.PHP_EOL; - $code .= $this->genClassConstantList($protectedConstants); - } - - if ($publicConstants) { - $code .= 'public:'.PHP_EOL; - $code .= $this->genClassConstantList($publicConstants); - } - - if ($privateProperties) { - $code .= 'private:'.PHP_EOL; - $code .= $this->genClassPropertyList($privateProperties); - } - - if ($protectedProperties) { - $code .= 'protected:'.PHP_EOL; - $code .= $this->genClassPropertyList($protectedProperties); - } - - if ($publicProperties) { - $code .= 'public:'.PHP_EOL; - $code .= $this->genClassPropertyList($publicProperties); - } - - $code .= '};'.PHP_EOL.PHP_EOL; - - return $code; - } - - public function genIncludeHeaderFiles(): string - { - $headers = array_merge($this->globalHeaders, $this->localHeaders); - $lines = []; - foreach ($headers as $header) { - $lines[] = '#include <'.$header.'>'; - } - - return implode(PHP_EOL, $lines).PHP_EOL.PHP_EOL; - } - /** * @param array $list */ @@ -887,7 +776,7 @@ class Translator extends Preprocessor { $code = ''; foreach ($list as $const) { - $code .= $this->getIndent().$this->genClassConstant($const); + $code .= $this->getIndent() . $this->genClassConstant($const); } return $code; @@ -895,7 +784,7 @@ class Translator extends Preprocessor protected function genClassConstant(ConstantDef $const): string { - return 'static const '.$const->type.' '.$const->name.';'.PHP_EOL; + return 'static const ' . $const->type . ' ' . $const->name . ';' . PHP_EOL; } /** @@ -905,7 +794,7 @@ class Translator extends Preprocessor { $code = ''; foreach ($list as $prop) { - $code .= $this->getIndent().$this->genClassProperty($prop); + $code .= $this->getIndent() . $this->genClassProperty($prop); } return $code; @@ -913,23 +802,23 @@ class Translator extends Preprocessor protected function genClassProperty(PropertyDef $prop): string { - $code = $prop->type.' '.$prop->name; + $code = $prop->type . ' ' . $prop->name; if ($prop->default) { - $code .= ' = '.$prop->default; + $code .= ' = ' . $prop->default; } - return $code.';'.PHP_EOL; + return $code . ';' . PHP_EOL; } protected function genFunction(string $name, string $returnType, array $args = [], array $lines = []): string { $_args = []; foreach ($args as $arg => $type) { - $_args[] = $type.' '.$arg; + $_args[] = $type . ' ' . $arg; } - $code = $returnType.' '.$name.'('.implode(', ', $_args).') {'.PHP_EOL; - $code .= implode(PHP_EOL, $lines).PHP_EOL; - $code .= '}'.PHP_EOL; + $code = $returnType . ' ' . $name . '(' . implode(', ', $_args) . ') {' . PHP_EOL; + $code .= implode(PHP_EOL, $lines) . PHP_EOL; + $code .= '}' . PHP_EOL; return $code; } @@ -937,13 +826,13 @@ class Translator extends Preprocessor protected function parseClassConstDef(Node\Stmt\ClassConst $v): void { $flags = $v->flags; - $type = $v->type ? $this->getTypeFromZendType($this->parseIdentifier($v->type)) : self::TYPE_VAR; + $type = $v->type ? $this->getTypeFromZendType($this->parseIdentifier($v->type)) : self::TYPE_VAR; foreach ($v->consts as $const) { $constName = $this->parseIdentifier($const->name); if (isset($this->classDef->constants[$constName])) { - $this->fatalError($const, 'Cannot redefine class constant '.$this->class.'::'.$constName); + $this->fatalError($const, 'Cannot redefine class constant ' . $this->class . '::' . $constName); } - $constInfo = new ConstantDef($constName, $flags, $type, $this->parseIdentifier($const->value)); + $constInfo = new ConstantDef($constName, $flags, $type, $this->parseIdentifier($const->value)); $this->classDef->constants[$constInfo->name] = $constInfo; } } @@ -951,14 +840,14 @@ class Translator extends Preprocessor protected function parsePropertyDef(Node\Stmt\Property $v): void { $flags = $v->flags; - $type = $v->type ? $this->getTypeFromZendType($this->parseIdentifier($v->type)) : self::TYPE_VAR; + $type = $v->type ? $this->getTypeFromZendType($this->parseIdentifier($v->type)) : self::TYPE_VAR; foreach ($v->props as $prop) { $propDef = new PropertyDef($this->parseIdentifier($prop->name), $flags, $type); if ($prop->default) { - if ('Expr_Array' == $prop->default->getType() and count($prop->default->items) > 0) { + if ($prop->default->getType() == 'Expr_Array' and count($prop->default->items) > 0) { $this->classDef->requireCtor = true; - $propDef->type = self::TYPE_ARRAY; + $propDef->type = self::TYPE_ARRAY; } $propDef->default = $this->parseIdentifier($prop->default); } @@ -968,17 +857,17 @@ class Translator extends Preprocessor protected function parseClassMethod(Node\Stmt\ClassMethod $v, array &$methodCodes): void { - $name = $this->getMethodName($v); - $this->method = $name; + $name = $this->getMethodName($v); + $this->method = $name; $methodCodes[$name] = $this->parseFunction($v); - $flags = $v->flags; + $flags = $v->flags; if (!($flags & Modifiers::PRIVATE) and !($flags & Modifiers::PROTECTED)) { $flags |= Modifiers::PUBLIC; } $methodDef = new MethodDef($flags, $name, $this->functionDef); $this->checkRequiredArgNum($name, $methodDef, $v); $this->classDef->methods[$name] = $methodDef; - $this->method = ''; + $this->method = ''; } protected function parseIdentifierList(array $implements): array @@ -993,52 +882,173 @@ class Translator extends Preprocessor protected function parseInterface(Node\Stmt\Interface_ $v): void { - $name = $this->parseIdentifier($v->name); - $this->interface = $name; - $this->interfaceDef = new InterfaceDef($name, $this->namespace); - $interfaceName = $this->interfaceDef->getNamespacedName(); - $this->interfaces[$interfaceName] = $this->interfaceDef; + $name = $this->parseIdentifier($v->name); + $this->interface = $name; + $this->interfaceDef = new InterfaceDef($name, $this->namespace); + $interfaceName = $this->interfaceDef->getNamespacedName(); + $this->interfaces[$interfaceName] = $this->interfaceDef; $this->interfacesDefineInFile[$interfaceName] = $this->interfaceDef; } protected function parseForeachObject(Foreach_ $node): string { - $obj = $this->parseIdentifier($node->expr); + $obj = $this->parseIdentifier($node->expr); $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, self::TYPE_OBJECT); $tmpArrayVar = $this->genTmpVarName(); $this->addLocalVar($tmpArrayVar, self::TYPE_ARRAY); - $code = 'if ('.$obj.'.instanceOf("IteratorAggregate")) {'.PHP_EOL; - $code .= $this->getIndent().$tmpVar.' = '.$obj.'.exec("getIterator");'.PHP_EOL.'}'.PHP_EOL; - $code .= 'else if ('.$obj.'.instanceOf("Iterator")) {'.PHP_EOL; - $code .= $this->getIndent().$tmpVar.' = '.$obj.';'.PHP_EOL.'}'.PHP_EOL; + $code = 'if (' . $obj . '.instanceOf("IteratorAggregate")) {' . PHP_EOL; + $code .= $this->getIndent() . $tmpVar . ' = ' . $obj . '.exec("getIterator");' . PHP_EOL . '}' . PHP_EOL; + $code .= 'else if (' . $obj . '.instanceOf("Iterator")) {' . PHP_EOL; + $code .= $this->getIndent() . $tmpVar . ' = ' . $obj . ';' . PHP_EOL . '}' . PHP_EOL; - $code .= 'if ('.$tmpVar.') {'.PHP_EOL; + $code .= 'if (' . $tmpVar . ') {' . PHP_EOL; - ++$this->indentLevel; - $code .= $this->getIndent().$tmpVar.'.exec("rewind");'.PHP_EOL; - $code .= $this->getIndent().'for (;'.$tmpVar.'.exec("valid"); '.$tmpVar.'.exec("next")) {'.PHP_EOL; - ++$this->indentLevel; + $this->indentLevel++; + $code .= $this->getIndent() . $tmpVar . '.exec("rewind");' . PHP_EOL; + $code .= $this->getIndent() . 'for (;' . $tmpVar . '.exec("valid"); ' . $tmpVar . '.exec("next")) {' . PHP_EOL; + $this->indentLevel++; $valueVar = $this->parseIdentifier($node->valueVar); $this->checkVar($node, $valueVar); - $code .= $this->getIndent().' '.$valueVar.' = '.$tmpVar.'.exec("current");'.PHP_EOL; + $code .= $this->getIndent() . ' ' . $valueVar . ' = ' . $tmpVar . '.exec("current");' . PHP_EOL; if ($node->keyVar) { $keyVar = $this->parseIdentifier($node->keyVar); $this->checkVar($node, $keyVar); - $code .= $this->getIndent().' '.$keyVar.' = '.$tmpVar.'.exec("key");'.PHP_EOL; + $code .= $this->getIndent() . ' ' . $keyVar . ' = ' . $tmpVar . '.exec("key");' . PHP_EOL; } $code .= $this->parseStmts($node->stmts); - $code .= '}'.PHP_EOL; - --$this->indentLevel; - $code .= $this->getIndent().'} else {'.PHP_EOL; - $code .= $this->getIndent().$tmpArrayVar.' = php::call("get_object_vars", {'.$obj.'});'.PHP_EOL; + $code .= '}' . PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '} else {' . PHP_EOL; + $code .= $this->getIndent() . $tmpArrayVar . ' = php::call("get_object_vars", {' . $obj . '});' . PHP_EOL; $code .= $this->parseForeachArray($node, $tmpArrayVar); - --$this->indentLevel; - $code .= '}'.PHP_EOL; + $this->indentLevel--; + $code .= '}' . PHP_EOL; + + return $code; + } + + private function getRegisterClassFunctionArgDef(ClassDef|InterfaceDef $classDef): string + { + $depsCeList = $this->getRegisterClassFunctionCeList($classDef); + if (empty($depsCeList)) { + return ''; + } + + return 'zend_class_entry *' . implode(', zend_class_entry *', $depsCeList); + } + + private function getImplementCe(ClassDef $classDef): array + { + $list = []; + foreach ($classDef->implements as $interface) { + $list[] = self::PREFIX . 'class_entry_' . $interface; + } + + return $list; + } + + private function render(string $template): string + { + ob_start(); + include __DIR__ . '/../template/' . $template; + + return ob_get_clean(); + } + + private function genFunctionWrapper(FunctionDef $functionDef): string + { + $name = $functionDef->name; + $cppCode = 'ZEND_FUNCTION(' . $name . '){' . PHP_EOL; + $fn = self::PREFIX . $this->getNativeName($functionDef->name); + $cppCode .= $this->genWrapperFunctionArgs($fn, $functionDef); + + return $cppCode; + } + + private function genClassNative(): string + { + $code = 'class ' . $this->class . ' { '; + + $publicMethods = []; + $protectedMethods = []; + $privateMethods = []; + $publicConstants = []; + $protectedConstants = []; + $privateConstants = []; + $publicProperties = []; + $protectedProperties = []; + $privateProperties = []; + + foreach ($this->classDef->constants as $const) { + if ($const->flags & Modifiers::PUBLIC) { + $publicConstants[] = $const; + } + if ($const->flags & Modifiers::PROTECTED) { + $protectedConstants[] = $const; + } + if ($const->flags & Modifiers::PRIVATE) { + $privateConstants[] = $const; + } + } + foreach ($this->classDef->methods as $method) { + if ($method->flags & Modifiers::PUBLIC) { + $publicMethods[] = $method; + } + if ($method->flags & Modifiers::PROTECTED) { + $protectedMethods[] = $method; + } + if ($method->flags & Modifiers::PRIVATE) { + $privateMethods[] = $method; + } + } + foreach ($this->classDef->properties as $property) { + if ($property->flags & Modifiers::PUBLIC) { + $publicProperties[] = $property; + } + if ($property->flags & Modifiers::PROTECTED) { + $protectedProperties[] = $property; + } + if ($property->flags & Modifiers::PRIVATE) { + $privateProperties[] = $property; + } + } + + if ($privateConstants) { + $code .= 'private:' . PHP_EOL; + $code .= $this->genClassConstantList($privateConstants); + } + + if ($protectedConstants) { + $code .= 'protected:' . PHP_EOL; + $code .= $this->genClassConstantList($protectedConstants); + } + + if ($publicConstants) { + $code .= 'public:' . PHP_EOL; + $code .= $this->genClassConstantList($publicConstants); + } + + if ($privateProperties) { + $code .= 'private:' . PHP_EOL; + $code .= $this->genClassPropertyList($privateProperties); + } + + if ($protectedProperties) { + $code .= 'protected:' . PHP_EOL; + $code .= $this->genClassPropertyList($protectedProperties); + } + + if ($publicProperties) { + $code .= 'public:' . PHP_EOL; + $code .= $this->genClassPropertyList($publicProperties); + } + + $code .= '};' . PHP_EOL . PHP_EOL; return $code; } diff --git a/src/Php/Unsupported.php b/src/Php/Unsupported.php index 97bf1179..77568817 100644 --- a/src/Php/Unsupported.php +++ b/src/Php/Unsupported.php @@ -1,4 +1,12 @@