diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..4edbaa62 --- /dev/null +++ b/.clang-format @@ -0,0 +1,111 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 8 +UseTab: Never \ No newline at end of file diff --git a/bin/bootstrap.php b/bin/bootstrap.php new file mode 100644 index 00000000..a7171d77 --- /dev/null +++ b/bin/bootstrap.php @@ -0,0 +1,5 @@ +setIndent(' '); $code = $translator->convert($file); $info = pathinfo($file); diff --git a/bin/linker.php b/bin/linker.php index 10ed39e5..de113c8a 100644 --- a/bin/linker.php +++ b/bin/linker.php @@ -1,7 +1,5 @@ compileBinary($info['filename'], $objectFile); diff --git a/examples/binary-tree.php b/examples/binary-tree.php index 9db2cd7c..f7c374a6 100644 --- a/examples/binary-tree.php +++ b/examples/binary-tree.php @@ -1,5 +1,4 @@ self::TYPE_INT, 'float' => self::TYPE_FLOAT, @@ -36,19 +39,21 @@ class Translator extends \PhpAot\Core\Translator private array $internalFunctions = []; private int $optimizeLevel = 5; - private bool $verbose = false; + private bool $verbose = true; private string $file; private string $dir; private string $namespace = ''; private array $uses = []; private string $class = ''; - private string $returnType = ''; + private FunctionDef $functionDef; const PREFIX = 'php_'; + private string $rootPath; - public function __construct() + public function __construct(string $rootPath) { + $this->rootPath = $rootPath; } public function parseHeaders(): string @@ -65,12 +70,9 @@ class Translator extends \PhpAot\Core\Translator $this->phpxDir = $dir; } - public function convert(string $file) + private function doConvert(string $phpCode) { $parser = (new ParserFactory())->createForNewestSupportedVersion(); - $phpCode = file_get_contents($file); - $this->file = realpath($file); - $this->dir = dirname($this->file); $ast = $parser->parse($phpCode); $traverser = new NodeTraverser; @@ -79,6 +81,7 @@ class Translator extends \PhpAot\Core\Translator $traverser->addVisitor(new Visitor()); $stmts = $traverser->traverse($ast); + $this->indentLevel = 0; $cppCode = $this->parseHeaders(); foreach($stmts as $v) { @@ -100,9 +103,26 @@ class Translator extends \PhpAot\Core\Translator return $cppCode; } - public function save($code, $file) + public function convert(string $file): string + { + $phpCode = file_get_contents($file); + $this->file = realpath($file); + $this->dir = dirname($this->file); + + while (true) { + try { + return $this->doConvert($phpCode); + } catch (ReturnTypeChanged $e) { + // 某些情况,例如返回值变更,需要重新解析 + continue; + } + } + } + + public function save(string $code, string $file): void { file_put_contents($file, $code); + $this->formatCppCode($file); } @@ -131,10 +151,16 @@ class Translator extends \PhpAot\Core\Translator return isset($this->internalFunctions[$name]); } - private function parseFunctionDef($v): string + private function resetScope(): void { $this->scope = []; - $def = new FunctionDef(); + $this->definitions = []; + $this->tmpVarIndex = 0; + } + + private function parseFunctionDef($v): string + { + $this->resetScope(); $names[] = $this->parseIdentifier($v->name); if ($this->class) { $names[] = strtolower($this->class); @@ -144,22 +170,30 @@ class Translator extends \PhpAot\Core\Translator } $name = implode('__', array_reverse($names)); - - if ($v->returnType) { - $this->returnType = $this->getZendType($this->parseIdentifier($v->returnType)); + if (isset($this->internalFunctions[$name])) { + $this->functionDef = $this->internalFunctions[$name]; } else { - $this->returnType = 'void'; + $this->functionDef = new FunctionDef(); + $this->functionDef->name = $name; + if ($v->returnType) { + $this->functionDef->returnType = $this->getZendType($this->parseIdentifier($v->returnType)); + } else { + $this->functionDef->returnType = 'void'; + } + $this->internalFunctions[$name] = $this->functionDef; } - $def->name = $name; - $def->returnType = $this->returnType; - $this->internalFunctions[$name] = $def; - - $params = $this->parseParams($v->params, $def); - $code = $this->returnType . ' ' . self::PREFIX . $name . '(' . $params . ') {' . PHP_EOL; + $params = $this->parseParams($v->params); $this->indentLevel++; $stmts = $this->parseStmts($v->stmts); $this->indentLevel--; + + $code = $this->getReturnType() . ' ' . self::PREFIX . $name . '(' . $params . ') {' . PHP_EOL; + $this->indentLevel++; + foreach ($this->definitions as $definition) { + $code .= $this->getIndent() . $definition . PHP_EOL; + } + $this->indentLevel--; $code .= $stmts; $code .= "}\n"; @@ -187,15 +221,17 @@ class Translator extends \PhpAot\Core\Translator return $expr->getAttribute('rawValue'); case 'Scalar_String': return '"' . $this->escapeString($expr->value) . '"'; + case 'Expr_ConstFetch': + return $this->parseConstFetch($expr); default: return $this->parseExpr($expr); } } - private function parseParams($params, FunctionDef $def): string + private function parseParams($params): string { $list = []; - $def->argumentCountRequired = count($params); + $this->functionDef->argumentCountRequired = count($params); foreach ($params as $param) { $type = $this->parseType($param->type); $name = $this->parseIdentifier($param->var); @@ -206,10 +242,10 @@ class Translator extends \PhpAot\Core\Translator $argInfo->name = $name; $argInfo->type = $type; if (isset($param->default)) { - $def->argumentCountRequired = count($list) - 1; + $this->functionDef->argumentCountRequired = count($list) - 1; $argInfo->default = $param->default->getAttribute('rawValue'); } - $def->arguments[] = $argInfo; + $this->functionDef->arguments[] = $argInfo; } return implode(', ', $list); } @@ -233,6 +269,9 @@ class Translator extends \PhpAot\Core\Translator case 'Stmt_For': $lines[] = $this->parseFor($v); break; + case 'Stmt_Foreach': + $lines[] = $this->parseForeach($v); + break; case 'Stmt_While': $lines[] = $this->parseWhile($v); break; @@ -259,6 +298,7 @@ class Translator extends \PhpAot\Core\Translator } } $code = ''; + foreach ($lines as $line) { $code .= $this->getIndent() . $line . PHP_EOL; } @@ -398,8 +438,12 @@ class Translator extends \PhpAot\Core\Translator $this->addVar($array, self::TYPE_VAR); $code .= self::TYPE_ARRAY . " $array;\n" . $this->getIndent(); } - $dim = $this->parseIdentifier($left->dim); - return $code . "$array.offsetSet($dim, " . $this->parseExpr($right) . ")"; + if ($left->dim === null) { + return $code . "$array.offsetSet(php::null, " . $this->parseExpr($right) . ")"; + } else { + $dim = $this->parseIdentifier($left->dim); + return $code . "$array.offsetSet($dim, " . $this->parseExpr($right) . ")"; + } } elseif ($left->getType() === 'Expr_PropertyFetch') { $array = $this->parseIdentifier($left->var); $propName = $this->parseIdentifier($left->name); @@ -429,19 +473,45 @@ class Translator extends \PhpAot\Core\Translator } } - private function parseBinaryOpPlus(mixed $expr): string + private function parseBinaryOp($left, $right, $op): string { - $left = $this->parseIdentifier($expr->left); - $right = $this->parseIdentifier($expr->right); + $leftExpr = $this->parseIdentifier($left); + $rightExpr = $this->parseIdentifier($right); + + $leftType = $this->detectType($left); + $rightType = $this->detectType($right); + + if ($leftType === self::TYPE_FLOAT) { + $rightExpr = $this->convertExprType($rightExpr, self::TYPE_FLOAT, $rightType); + } elseif ($rightType === self::TYPE_FLOAT) { + $leftExpr = $this->convertExprType($leftExpr, self::TYPE_FLOAT, $leftType); + } elseif ($leftType === self::TYPE_INT) { + $rightExpr = $this->convertExprType($rightExpr, self::TYPE_INT, $rightType); + } elseif ($rightType === self::TYPE_INT) { + $leftExpr = $this->convertExprType($leftExpr, self::TYPE_INT, $leftType); + } else { + $leftExpr = $this->convertExprType($leftExpr, self::TYPE_INT, $leftType); + $rightExpr = $this->convertExprType($rightExpr, self::TYPE_INT, $rightType); + } - return $left . ' + ' . $right; + return $leftExpr . ' ' .$op .' ' . $rightExpr; + } + + private function parseBinaryOpPlus(mixed $expr): string + { + return $this->parseBinaryOp($expr->left, $expr->right, '+'); } private function parseReturn(mixed $v): string { + // 实际函数的返回值 $type = $this->detectType($v->expr); $expr = $this->parseExpr($v->expr); - return 'return ' . $this->convertExprType($expr, $this->returnType, $type); + // 函数定义时没有声明返回值,但函数体中有返回值,修改为实际的返回值类型 + if ($this->getReturnType() === 'void') { + $this->resetReturnType($type); + } + return 'return ' . $this->convertExprType($expr, $this->getReturnType(), $type); } private function parseBinaryOpMul(mixed $expr): string @@ -462,6 +532,12 @@ class Translator extends \PhpAot\Core\Translator return isset($this->scope[$name]); } + private function resetReturnType(string $type): void + { + $this->functionDef->returnType = $type; + throw new ReturnTypeChanged; + } + private function detectType($expr): string { $exprType = $expr->getType(); @@ -507,7 +583,8 @@ class Translator extends \PhpAot\Core\Translator if ($item->key) { $list[] = $this->getIndent() . '{ ' . $this->parseIdentifier($item->key) . ', php::Variant(' . $this->parseIdentifier($item->value) . ') }'; } else { - $list[] = $this->getIndent() . 'php::Variant(' . $this->parseIdentifier($item->value) . ')'; + $value = $this->parseIdentifier($item->value); + $list[] = $this->getIndent() . 'php::Variant(' . $value . ')'; } } $this->indentLevel--; @@ -708,16 +785,16 @@ class Translator extends \PhpAot\Core\Translator { $name = $this->parseIdentifier($expr->name); if ($this->isInternalFunction($name)) { - return self::PREFIX . $name . '(' . $this->parseArgs($expr->args, $name) . ')'; + return self::PREFIX . $name . '(' . $this->parseCallArgs($expr->args, $name) . ')'; } if (empty($expr->args)) { return 'php::call("' . $name . '")'; } else { - return 'php::call("' . $name . '", {' . $this->parseArgs($expr->args, $name) . '})'; + return 'php::call("' . $name . '", {' . $this->parseCallArgs($expr->args, $name) . '})'; } } - private function parseArgs($args, string $funcName): string + private function parseCallArgs($args, string $funcName): string { $internalFunction = $this->isInternalFunction($funcName); $list_args = []; @@ -728,6 +805,14 @@ class Translator extends \PhpAot\Core\Translator } else { $list_args[] = $this->parseArg($arg); } + + // 调用了不存在的变量,可能是引用 + if ($arg->value->getType() === 'Expr_Variable') { + $name = $this->parseIdentifier($arg->value); + if (!$this->hasVar($name)) { + $this->definitions[] = self::TYPE_VAR . ' ' . $name . ' = php::newReference();'; + } + } } return implode(', ', $list_args); } @@ -904,13 +989,28 @@ class Translator extends \PhpAot\Core\Translator return $code; } + private function convertIntExpr(string $expr): string + { + return 'php::to_int(' . $expr . ')'; + } + + private function convertFloatExpr(string $expr): string + { + return 'php::to_float(' . $expr . ')'; + } + + private function convertBoolExpr(string $expr): string + { + return 'php::to_bool(' . $expr . ')'; + } + private function optimizeBinaryOpCompare(string $left, string $right, string $op): string { if ($this->getDetectedType($left) == self::TYPE_INT and $this->getDetectedType($right) == self::TYPE_VAR) { - return $left . ' ' . $op . ' ' . $right . '.toInt()'; + return $left . ' ' . $op . ' ' . $this->convertIntExpr($right); } if ($this->getDetectedType($left) == self::TYPE_FLOAT and $this->getDetectedType($right) == self::TYPE_VAR) { - return $left . ' ' . $op . ' ' . $right . '.toFloat()'; + return $left . ' ' . $op . ' ' . $this->convertFloatExpr($right); } return ''; } @@ -960,6 +1060,10 @@ class Translator extends \PhpAot\Core\Translator $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); + if ($right === 'nullptr') { + return $left . '.isNull()'; + } + return 'php::same(' . $left . ', ' . $right . ')'; } @@ -970,7 +1074,7 @@ class Translator extends \PhpAot\Core\Translator if (empty($args)) { return 'php::newObject("' . $className . '")'; } else { - return 'php::newObject("' . $className . '"(' . $this->parseArgs($args) . ')'; + return 'php::newObject("' . $className . '"(' . $this->parseCallArgs($args) . ')'; } } @@ -1036,7 +1140,11 @@ class Translator extends \PhpAot\Core\Translator private function parseConstFetch(Node $expr) { - return $this->parseIdentifier($expr->name); + $name = $this->parseIdentifier($expr->name); + if ($name === 'null') { + return 'nullptr'; + } + return $name; } private function parseUnaryMinus(Node $expr): string @@ -1096,6 +1204,11 @@ class Translator extends \PhpAot\Core\Translator return $funcDef->arguments[$index]; } + private function getReturnType(): string + { + return $this->functionDef->returnType; + } + private function getTypeConvertedArg($arg, $argInfo): string { $expr = $this->parseArg($arg); @@ -1106,13 +1219,13 @@ class Translator extends \PhpAot\Core\Translator private function convertExprType(string $expr, $leftType, $rightType): string { if ($leftType === self::TYPE_INT && $rightType !== self::TYPE_INT) { - return '(' . $expr . ').toInt()'; + return $this->convertIntExpr($expr); } if ($leftType === self::TYPE_FLOAT && $rightType !== self::TYPE_FLOAT) { - return '(' . $expr . ').toFloat()'; + return $this->convertFloatExpr($expr); } if ($leftType === self::TYPE_BOOL && $rightType !== self::TYPE_BOOL) { - return '(' . $expr . ').toBool()'; + return $this->convertBoolExpr($expr); } return $expr; } @@ -1154,11 +1267,55 @@ class Translator extends \PhpAot\Core\Translator private function parseAssignOpShiftRight(Node $node): string { $var = $this->parseIdentifier($node->var); - return $var . ' >> ' . $this->parseIdentifier($node->expr); + return $var . ' >>= ' . $this->parseIdentifier($node->expr); } private function parseMagicConstFile(mixed $expr): string { return '"' . $this->escapeString($this->file) . '"'; } + + private function parseForeach(Node $node) + { + $expr = $this->parseIdentifier($node->expr); + if ($node->keyVar) { + $keyVar = $this->parseIdentifier($node->keyVar); + } + $valueVar = $this->parseIdentifier($node->valueVar); + + $iteratorVar = $this->addTmpVar(); + + $stmts = $node->stmts; + $code = ''; + + $code .= self::TYPE_ARRAY . " $iteratorVar = " . $expr . ';' . PHP_EOL; + + $code .= 'for (auto iter = ' . $iteratorVar . '.begin(); iter != ' . $iteratorVar . '.end(); ++iter) {' . PHP_EOL; + $this->indentLevel++; + if ($node->keyVar) { + $code .= $this->getIndent() . self::TYPE_VAR . ' ' . $keyVar . ' = iter.key();' . PHP_EOL; + $this->addVar($keyVar, self::TYPE_VAR); + } + $code .= $this->getIndent() . self::TYPE_VAR . ' ' . $valueVar . ' = iter.value();' . PHP_EOL; + $this->addVar($valueVar, self::TYPE_VAR); + $code .= $this->parseStmts($stmts); + $this->indentLevel--; + + $code .= $this->getIndent() . '}'; + + return $code; + } + + private function formatCppCode(string $file): void + { + $cmd = 'cd ' . $this->rootPath . ' && clang-format -i ' . $file; + $this->writeLog('formatting ' . $file . '...'); + $this->writeLog($cmd); + shell_exec($cmd); + } + + private function addTmpVar(): string + { + return 'tmp_var_' . $this->tmpVarIndex++; + } }