::quiet_NaN()'; public const string VALUE_INF = 'std::numeric_limits::infinity()'; public const string LITERAL_STRINGS = '_literal_strings'; 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 = '__'; 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 array $zendTypeMap = [ 'int' => self::TYPE_INT, 'float' => self::TYPE_FLOAT, 'bool' => self::TYPE_BOOL, ]; protected array $reservedNames; protected array $unsupportedFunctions = [ 'compact', 'extract' ]; protected array $globalHeaders = [ 'phpx.h', 'phpx_helper.h', 'phpx_func.h', '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 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 array $useNamespaces = []; protected array $useFunctions = []; protected string $class = ''; /** * @var array */ protected array $classes = []; /** * @var array */ protected array $classesDefineInFile = []; protected FunctionDef $functionDef; protected ClassDef $classDef; protected array $globalVars = [ '_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, ]; protected array $localVars = []; protected array $varTypes = []; 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 $inSwitch = false; protected bool $stubFile = false; protected bool $stubFileIncluded = false; protected Parser $parser; public function __construct(string $rootPath) { $this->rootPath = $rootPath; $this->parser = (new ParserFactory())->createForNewestSupportedVersion(); // $this->prettyPrinter = new PrettyPrinter\Standard; $this->setBuildDir($rootPath . '/build'); $climate = new CLImate(); $this->climate = $climate; // $this->noLiteralStrings = $climate->arguments->get('no-literal-strings'); $this->noLiteralStrings = true; } public function setPhpxDir($dir): void { $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); $this->formatCppCode($file); } public function getLine($node): int { return $node->getLine(); } public function getType($node): string { 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; } public function isNativeFunction(string $name): bool { return isset($this->nativeFunctions[$name]); } protected function resetScope(): void { $this->localVars = []; $this->arguments = []; $this->tmpVarIndex = 0; } protected function resetFile(): void { $this->indentLevel = 0; $this->strictTypes = false; $this->classesDefineInFile = []; } protected function resetNamespace(): void { $this->useNamespaces = []; $this->useFunctions = []; $this->namespace = ''; } protected function getFunctionName(Node $v): string { $names[] = $this->escapeFunction($this->parseIdentifier($v->name)); if ($this->namespace) { $names[] = $this->escapeNamespace($this->namespace); } if ($this->class) { $names[] = $this->escapeClass($this->class); } return implode(self::NAMESPACE_SEPARATOR, array_reverse($names)); } protected function parseFunctionDeclaration(string $name, Node\FunctionLike $v): FunctionDef { $functionDef = new FunctionDef(); $this->functionDef = $functionDef; $functionDef->name = $name; if ($v->returnType) { $functionDef->returnType = $this->getTypeFromZendType($this->parseIdentifier($v->returnType)); } else { // .stub 存根定义 C++ Native 函数,必须设置返回值类型 if ($this->stubFile) { throw new Exception('No return type for ' . $name); } $functionDef->returnType = 'void'; } $this->parseParams($v->params); return $functionDef; } protected function parseFunction(Node\FunctionLike $v): string { $this->resetScope(); $name = $this->getFunctionName($v); if (isset($this->nativeFunctions[$name])) { $this->functionDef = $this->nativeFunctions[$name]; } else { $this->nativeFunctions[$name] = $this->parseFunctionDeclaration($name, $v); if (isset($this->redoAfterDeclare[$name])) { unset($this->redoAfterDeclare[$name]); $this->climate->cyan('Received redo request, retrying...'); throw new RedoException(); } } foreach ($this->functionDef->argInfoList as $argInfo) { $this->arguments[$argInfo->name] = $argInfo->type; if (!$this->hasLocalVar($argInfo->name)) { $this->addLocalVar($argInfo->name, $argInfo->type); } } if ($this->class) { $this->arguments['this_'] = self::TYPE_OBJECT; } 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"; 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) . '"'; } else { $index = $this->literalStrings[$expr->value] ?? $this->addLiteralString($expr->value); 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: 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); default: return $this->parseExpr($expr); } } protected function parseParams($params): void { $list = []; $this->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)); } $type = $this->parseType($param->type); $name = $this->parseIdentifier($param->var); $list[] = $type . ' ' . $name; $argInfo = new ArgInfo(); $argInfo->name = $name; $argInfo->type = $type; if (isset($param->default)) { $this->functionDef->argCountRequired = count($list) - 1; $argInfo->default = $this->parseIdentifier($param->default); } $this->functionDef->argInfoList[] = $argInfo; } $this->functionDef->params = implode(', ', $list); } protected function parseStmts(array $stmts): string { $lines = []; foreach ($stmts as $v) { $class = $v->getType(); $this->beforeStmtLines = []; $this->afterStmtLines = []; $this->writeLog('Line ' . $this->getLine($v) . ': ' . $class); $lines[] = $this->getIndent() . '// ' . $class . ' [' . $v->getStartLine() . ':' . $v->getEndLine() . ']'; 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 = false; break; case 'Stmt_Foreach': $this->inLoop = true; $result = $this->parseForeach($v); $this->inLoop = false; break; case 'Stmt_Switch': $this->inSwitch = true; $result = $this->parseSwitch($v); $this->inSwitch = false; break; case 'Stmt_While': $this->inLoop = true; $result = $this->parseWhile($v); $this->inLoop = false; break; case 'Stmt_Do': $this->inLoop = true; $result = $this->parseDo($v); $this->inLoop = false; 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 = 'continue;'; break; case 'Stmt_Nop': $result = '// pass'; 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; default: abort($v); } $lines = array_merge($lines, $this->beforeStmtLines); $this->beforeStmtLines = []; $lines[] = $result; $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) { var_dump($expr); } switch ($type) { case 'Expr_Isset': return $this->parseIsset($expr); case 'Expr_Empty': return $this->parseEmpty($expr); case 'Expr_Assign': return $this->parseAssign($expr); case 'Expr_AssignRef': return $this->parseAssignRef($expr); case 'Expr_Print': return $this->parsePrint($expr); case 'Expr_BinaryOp_Equal': return $this->parseBinaryOpEqual($expr); case 'Expr_BinaryOp_NotEqual': return $this->parseBinaryOpNotEqual($expr); case 'Expr_BinaryOp_Identical': return $this->parseBinaryOpIdentical($expr); case 'Expr_BinaryOp_NotIdentical': return $this->parseBinaryOpNotIdentical($expr); case 'Expr_BooleanNot': return $this->parseBooleanNot($expr); case 'Expr_BinaryOp_Plus': return $this->parseBinaryOpPlus($expr); case 'Expr_BinaryOp_Div': return $this->parseBinaryOpDiv($expr); case 'Expr_BinaryOp_Smaller': return $this->parseBinaryOpSmaller($expr); case 'Expr_BinaryOp_SmallerOrEqual': return $this->parseBinaryOpSmallerOrEqual($expr); case 'Expr_BinaryOp_GreaterOrEqual': return $this->parseBinaryOpGreaterOrEqual($expr); case 'Expr_BinaryOp_Spaceship': return $this->parseBinaryOpSpaceship($expr); case 'Expr_PreInc': return $this->parsePreInc($expr); case 'Expr_PostInc': return $this->parsePostInc($expr); case 'Expr_PreDec': return $this->parsePreDec($expr); case 'Expr_PostDec': return $this->parsePostDec($expr); case 'Expr_AssignOp_Plus': return $this->parseAssignOpPlus($expr); case 'Expr_AssignOp_Minus': return $this->parseAssignOpMinus($expr); case 'Expr_AssignOp_Mul': return $this->parseAssignOpMul($expr); case 'Expr_AssignOp_Div': return $this->parseAssignOpDiv($expr); case 'Expr_AssignOp_Mod': return $this->parseAssignOpMod($expr); case 'Expr_AssignOp_Concat': return $this->parseAssignOpConcat($expr); case 'Expr_AssignOp_ShiftRight': return $this->parseAssignOpShiftRight($expr); case 'Expr_AssignOp_BitwiseAnd': return $this->parseAssignOpBitwiseAnd($expr); case 'Expr_AssignOp_BitwiseXor': return $this->parseAssignOpBitwiseXor($expr); case 'Expr_AssignOp_Pow': return $this->parseAssignOpPow($expr); case 'Expr_BinaryOp_Mul': return $this->parseBinaryOpMul($expr); case 'Expr_BinaryOp_Concat': return $this->parseBinaryOpConcat($expr); case 'Expr_BinaryOp_Greater': return $this->parseBinaryOpGreater($expr); case 'Expr_BinaryOp_LogicalAnd': case 'Expr_BinaryOp_BooleanAnd': return $this->parseBinaryOpLogicalAnd($expr); case 'Expr_BinaryOp_LogicalOr': case 'Expr_BinaryOp_BooleanOr': return $this->parseBinaryOpLogicalOr($expr); case 'Expr_BinaryOp_LogicalXor': return $this->parseBinaryOpLogicalXor($expr); case 'Expr_BinaryOp_Minus': return $this->parseBinaryOpMinus($expr); case 'Expr_Array': return $this->parseArray($expr); case self::EXPR_ARRAY_DIM_FETCH: return $this->parseArrayDimFetch($expr); case 'Expr_PropertyFetch': return $this->parsePropertyFetch($expr); case 'Expr_BinaryOp_ShiftLeft': return $this->parseBinaryOpShiftLeft($expr); case 'Expr_BinaryOp_ShiftRight': return $this->parseBinaryOpShiftRight($expr); case 'Expr_BinaryOp_BitwiseAnd': return $this->parseBinaryOpBitwiseAnd($expr); case 'Expr_BinaryOp_BitwiseOr': return $this->parseBinaryOpBitwiseOr($expr); case 'Expr_BinaryOp_BitwiseXor': return $this->parseBinaryOpBitwiseXor($expr); case 'Expr_BitwiseNot': return $this->parseBitwiseNot($expr); case 'Expr_BinaryOp_Mod': return $this->parseBinaryOpMod($expr); case 'Expr_BinaryOp_Pow': return $this->parseBinaryOpPow($expr); case 'Expr_Ternary': return $this->parseTernary($expr); case 'Expr_FuncCall': return $this->parseFuncCall($expr); case 'Expr_MethodCall': return $this->parseMethodCall($expr); case 'Expr_StaticCall': return $this->parseStaticCall($expr); case 'Expr_StaticPropertyFetch': return $this->parseStaticPropertyFetch($expr); case 'Expr_Include': return $this->parseInclude($expr); case 'Expr_Eval': return $this->parseEval($expr); case 'Expr_New': return $this->parseNew($expr); case 'Expr_Clone': return $this->parseClone($expr); case 'Expr_Instanceof': return $this->parseInstanceof($expr); case 'Expr_Throw': return $this->parseThrow($expr); case 'Expr_ShellExec': return $this->parseShellExec($expr); case 'Name_FullyQualified': return $expr->name; case 'Scalar_Int': case 'Scalar_Float': case 'Scalar_String': case self::EXPR_VARIABLE: return $this->parseIdentifier($expr); case 'Scalar_MagicConst_File': case 'Scalar_MagicConst_Dir': case 'Scalar_MagicConst_Line': case 'Scalar_MagicConst_Function': case 'Scalar_MagicConst_Class': return $this->parseMagicConst($expr); case 'Scalar_InterpolatedString': return $this->parseInterpolatedString($expr); case 'Expr_Cast_Int': return $this->parseCastInt($expr); case 'Expr_Cast_Double': return $this->parseCastDouble($expr); case 'Expr_Cast_Bool': return $this->parseCastBool($expr); case 'Expr_Cast_String': return $this->parseCastString($expr); case 'Expr_Cast_Array': return $this->parseCastArray($expr); case 'Expr_Cast_Object': return $this->parseCastObject($expr); case 'Expr_ConstFetch': return $this->parseConstFetch($expr); case 'Expr_UnaryMinus': return $this->parseUnaryMinus($expr); case 'Expr_UnaryPlus': return $this->parseUnaryPlus($expr); case 'InterpolatedStringPart': return $this->parseInterpolatedStringPart($expr); case 'Expr_ErrorSuppress': return $this->parseErrorSuppress($expr); case 'Expr_Exit': return $this->parseExit($expr); default: abort($expr); } } protected function parseAssign(Node $v): string { $left = $v->var; $right = $v->expr; if ($left->getType() === self::EXPR_ARRAY_DIM_FETCH) { $array = $this->parseIdentifier($left->var); $code = ''; // 这是 PHP 的初始化+赋值写法,需要先创建数组 if (!$this->hasVar($array) and $left->var->getType() === self::EXPR_VARIABLE) { $this->addLocalVar($array, self::TYPE_ARRAY); } $value = $this->trimBrackets($this->parseExpr($right)); if ($left->dim === null) { return $code . "$array.offsetSet(php::null, $value)"; } else { $dim = $this->trimBrackets($this->parseIdentifier($left->dim)); return $code . "$array.offsetSet($dim, $value)"; } } elseif ($left->getType() === 'Expr_PropertyFetch') { $array = $this->parseIdentifier($left->var); $propName = $this->identifierToStr($left->name); return "$array.setProperty($propName, " . $this->trimBrackets($this->parseExpr($right)) . ")"; } elseif ($left->getType() === 'Expr_StaticPropertyFetch') { $class = $this->identifierToStr($left->class); $propName = $this->identifierToStr($left->name); $value = $this->trimBrackets($this->parseExpr($right)); return "php::setStaticProperty($class, $propName, $value)"; } elseif ($right->getType() === 'Expr_Assign') { $chain[] = $left; while ($right->getType() === 'Expr_Assign') { $chain[] = $right->var; $right = $right->expr; } // 翻转赋值链 $chain = array_reverse($chain); // 取最后一个变量作为第一行的 left,右值为表达式 $left = array_shift($chain); $list[] = $this->parseFinallyAssign($left, $right); /** * 构造赋值链 * a = b = c = d = (expr) -> d = (expr); c = d; b = c; a = b; */ $right = $left; foreach ($chain as $left) { $list[] = $this->getIndent() . $this->parseFinallyAssign($left, $right); $right = $left; } return implode(";\n" . $this->getIndent(), $list); } return $this->parseFinallyAssign($left, $right); } protected function parseFinallyAssign($left, $right): string { if ($left instanceof Node\Expr\List_) { $items = $left->items; $code = '{'; $this->indentLevel++; $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, self::TYPE_VAR); $code .= $this->getIndent() . $tmpVar . ' = ' . $this->parseExpr($right) . '; '; foreach ($items as $k => $item) { if (!$item) { continue; } if ($item instanceof Node\Expr\ArrayItem) { $var = $this->parseIdentifier($item->value); if (!$this->hasVar($var)) { $this->addLocalVar($var, self::TYPE_VAR); } $code .= "$var = $tmpVar.offsetGet($k); "; } else { abort($item); } } $this->indentLevel--; return $code . '}'; } $var = $this->parseIdentifier($left); $expr = $this->parseExpr($right); if (!$this->hasVar($var)) { $type = $this->detectExprType($right); $this->addLocalVar($var, $type); } return $var . ' = ' . $this->convertExprType($expr, $this->detectExprType($left), $this->detectExprType($right)); } protected function parseEcho(mixed $v): string { foreach ($v->exprs as $expr) { $lines[] = 'php::echo(' . $this->parseExpr($expr) . ');'; } return implode("\n" . $this->getIndent(), $lines); } protected function isFloatStr(string $str): bool { return filter_var($str, FILTER_VALIDATE_FLOAT) !== false; } protected function isIntStr(string $str): bool { return filter_var($str, FILTER_VALIDATE_INT) !== false; } protected function isBoolStr(string $str): bool { return $str === 'true' || $str === 'false'; } protected function isInternalFunction(string $fname): bool { return array_key_exists($fname, $this->internalFunctions); } protected function isAssignOpConcat(string $op): bool { return $op === '.='; } protected function isAssignOpPow(string $op): bool { return $op === '**='; } /** * 尽可能转为数字,优先级 浮点 > 整数 > 字符串 */ protected function parseNumericIdentifier($expr) { if ($expr->getType() === 'Scalar_String') { if ($this->isFloatStr($expr->value)) { return floatval($expr->value); } elseif ($this->isIntStr($expr->value)) { return intval($expr->value); } elseif ($expr->value === '0') { return 0; } } return $this->parseIdentifier($expr); } protected function parseBinaryOp($left, $right, $op): string { // 运算逻辑,优先转为数字 $leftExpr = $this->parseNumericIdentifier($left); $rightExpr = $this->parseNumericIdentifier($right); $leftType = $this->detectExprType($left); $rightType = $this->detectExprType($right); if ($leftType === self::TYPE_FLOAT) { $rightExpr = $this->convertExprType($rightExpr, self::TYPE_FLOAT, $rightType); } elseif ($rightType === self::TYPE_FLOAT) { $leftExpr = $this->convertExprType($leftExpr, $leftType, self::TYPE_FLOAT); } elseif ($leftType === self::TYPE_INT) { $rightExpr = $this->convertExprType($rightExpr, self::TYPE_INT, $rightType); } elseif ($rightType === self::TYPE_INT) { $leftExpr = $this->convertExprType($leftExpr, $leftType, self::TYPE_INT); } return '((' . $leftExpr . ') ' . $op . ' (' . $rightExpr . '))'; } protected function parseBinaryOpPlus(mixed $expr): string { return $this->parseBinaryOp($expr->left, $expr->right, '+'); } protected function parseReturn(mixed $v): string { if ($v->expr === null) { return 'return;'; } // 实际函数的返回值 $type = $this->detectExprType($v->expr); $expr = $this->parseExpr($v->expr); // 函数定义时没有声明返回值,但函数体中有返回值,修改为实际的返回值类型 if ($this->getReturnType() === 'void') { $this->resetReturnType($type); } return 'return ' . $this->convertExprType($expr, $this->getReturnType(), $type); } protected function parseBinaryOpMul(mixed $expr): string { return $this->parseBinaryOp($expr->left, $expr->right, '*'); } protected function addLocalVar(string $name, string $type): void { $this->localVars[$name] = $type; } protected function addLiteralString(string $value): int { $index = $this->literalStringIndex++; $this->literalStrings[$value] = $index; return $index; } protected function addGlobalVar(string $name, string $type): void { $this->globalVars[$name] = $type; } protected function hasVar(string $name): bool { return $this->hasLocalVar($name) || $this->hasGlobalVar($name); } protected function hasLocalVar(string $name): bool { return isset($this->localVars[$name]); } protected function resetReturnType(string $type): void { $this->functionDef->returnType = $type; // 返回值变更,需要重新解析 $this->climate->cyan('Return type changed, retrying...'); throw new RedoException(); } protected function detectVarType($var): string { $name = $this->parseIdentifier($var); return $this->getVarType($name); } protected function detectExprType($expr): string { $exprType = $expr->getType(); if ($this->debugLine === $expr->getLine()) { var_dump($exprType); } switch ($exprType) { case 'Expr_Cast_Int': case 'Scalar_Int': return self::TYPE_INT; case 'Expr_Cast_Float': case 'Expr_Cast_Double': case 'Scalar_Float': return self::TYPE_FLOAT; case 'Expr_Cast_Bool': case 'Scalar_Bool': return self::TYPE_BOOL; case 'Expr_Array': return 'php::Array'; case 'Expr_BinaryOp_Plus': case 'Expr_BinaryOp_Minus': case 'Expr_BinaryOp_Mul': case 'Expr_BinaryOp_Div': case 'Expr_BinaryOp_Mod': case 'Expr_BinaryOp_Pow': case 'Expr_BinaryOp_ShiftLeft': case 'Expr_BinaryOp_ShiftRight': case 'Expr_BinaryOp_BitwiseAnd': case 'Expr_BinaryOp_BitwiseOr': case 'Expr_BinaryOp_BitwiseXor': case 'Expr_BinaryOp_BooleanAnd': $leftType = $this->detectExprType($expr->left); $rightType = $this->detectExprType($expr->right); if ($leftType === self::TYPE_FLOAT || $rightType === self::TYPE_FLOAT) { return self::TYPE_FLOAT; } if ($leftType === self::TYPE_INT || $rightType === self::TYPE_INT) { return self::TYPE_INT; } break; case 'Expr_FuncCall': $name = $this->parseIdentifier($expr->name); if ($this->isNativeFunction($name)) { return $this->nativeFunctions[$name]->returnType; } return $this->detectFuncCallReturnType($name); case 'Expr_New': return self::TYPE_OBJECT; case 'Expr_Assign': return $this->detectVarType($expr->var); case self::EXPR_VARIABLE: return $this->detectVarType($expr); case 'Expr_ConstFetch': return $this->detectConstType($expr); case 'Scalar_String': return self::TYPE_STR; default: break; } return self::TYPE_VAR; } protected function parseArray($node): string { $items = $node->items; // 优化代码风格,空数组直接返回{},否则会产生一些空洞内容 if (count($items) === 0) { return self::TYPE_ARRAY .'{}'; } $assocArray = false; foreach ($items as $item) { if ($item->key) { $assocArray = true; break; } } $list = []; $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 ($key === '0L') { $key = 'php::zero'; } $list[] = $this->getIndent() . '{ ' . $key . ', ' . self::TYPE_VAR . '(' . $value . ') }'; } else { $list[] = $this->getIndent() . self::TYPE_VAR . '(' . $value . ')'; } } $this->indentLevel--; return self::TYPE_ARRAY . '{' . PHP_EOL . implode(', ' . PHP_EOL, $list) . PHP_EOL . $this->getIndent() . '}'; } protected function parseType($type): string { if ($type == null) { return self::TYPE_VAR; } if ($type instanceof NullableType) { return self::TYPE_VAR; } $name = $type->name; switch ($name) { case 'int': return self::TYPE_INT; case 'array': return 'php::Array'; case 'float': return self::TYPE_FLOAT; case 'bool': return self::TYPE_BOOL; case 'string': return self::TYPE_STR; default: $this->varTypes[$name] = $name; return self::TYPE_OBJECT; } } protected function parseIncludes(): string { $list = [ $this->phpxDir . '/include', $this->getBuildDir() . '/include', ]; $out = '$(php-config --includes) '; foreach ($list as $li) { $out .= '-I ' . $li . ' '; } return $out; } protected function parseLdflags(): string { $list = [ '$(php-config --prefix)/lib', $this->phpxDir . '/lib', ]; $out = ''; foreach ($list as $li) { $out .= '-L ' . $li . ' '; } return $out; } protected function parseLibs(): string { $list = [ 'phpx', 'php', ]; $out = ''; foreach ($list as $li) { $out .= '-l' . $li . ' '; } return $out; } protected function addCompilationOption(string &$cmd): void { $cmd .= $this->parseIncludes(); $cmd .= ' -O' . $this->optimizeLevel; $cmd .= ' -g'; if ($this->climate->arguments->defined('profile')) { $cmd .= ' -lprofiler'; $cmd .= ' -DPPROF_ON=1'; } } protected function parseBinaryOpConcat(mixed $expr): string { $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); return 'php::concat(' . $left . ', ' . $right . ')'; } protected function parseFor(mixed $v): string { $init = $v->init; $cond = $v->cond; $loop = $v->loop; $stmts = $v->stmts; $code = ''; $list_expr = []; foreach ($init as $expr) { $list_expr[] = $this->parseExpr($expr); } $list_expr[] = ''; $code .= implode(";\n" . $this->getIndent(), $list_expr); $list_cond = []; foreach ($cond as $expr) { if ($expr->getType() === 'Expr_Assign') { $left = $expr->var; $name = $this->parseIdentifier($left); $type = $this->detectExprType($expr->expr); $code .= $type . ' ' . $name . ' = ' . '(' . $this->parseIdentifier($expr->expr) . ');'; } $list_cond[] = $this->parseExpr($expr); } $code .= 'for (;'; $code .= implode(', ', $list_cond); $code .= '; '; $list_loop = []; foreach ($loop as $expr) { $list_loop[] = $this->parseExpr($expr); } $code .= implode(', ', $list_loop); $code .= ') {' . PHP_EOL; $this->indentLevel++; $code .= $this->parseStmts($stmts); $this->indentLevel--; $code .= $this->getIndent() . '}' . PHP_EOL; return $code; } protected function parseBinaryOpSmaller(mixed $expr): string { return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '<')); } protected function parsePreInc(mixed $expr): string { return '++' . $this->parseIdentifier($expr->var); } protected function removeAssignOp(string $op): string { return str_replace('=', '', $op); } protected function parseAssignOp(mixed $node, string $op): string { $var = $this->parseIdentifier($node->var); $expr = $this->parseIdentifier($node->expr); $leftExprType = $node->var->getType(); if ($leftExprType === self::EXPR_VARIABLE) { if (!$this->hasVar($var)) { $this->fatalError($node->var, 'Cannot assign to undefined variable'); } $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 . ' = ' . $this->convertVarType($var, $powExpr); } else { return $var . ' ' . $op . ' ' . $rightExprStr; } } elseif ($leftExprType === self::EXPR_ARRAY_DIM_FETCH) { /** * $count[$r] -= 1; * 需要转为下面语句: * $tmp_var = $count[$r] - 1; * $count[$r] = $tmp_var; */ $type = $this->detectVarType($node->var); $rightType = $this->detectExprType($node->expr); $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, $rightType); $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) . ');'; } else { $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 . ')'; } // TODO 属性设置 } protected function parseAssignOpConcat(mixed $expr): string { return $this->parseAssignOp($expr, '.='); } protected function parseAssignOpPlus(mixed $expr): string { return $this->parseAssignOp($expr, '+='); } protected function parseAssignOpMinus(mixed $expr): string { return $this->parseAssignOp($expr, '-='); } protected function parseAssignOpMod(mixed $expr): string { return $this->parseAssignOp($expr, '%='); } protected function parseAssignOpMul(mixed $expr): string { return $this->parseAssignOp($expr, '*='); } protected function parseAssignOpDiv(mixed $expr): string { return $this->parseAssignOp($expr, '/='); } protected function parseAssignOpBitwiseAnd(mixed $expr): string { return $this->parseAssignOp($expr, '&='); } protected function parseAssignOpPow(mixed $expr): string { return $this->parseAssignOp($expr, '**='); } protected function fatalError(Node $node, string $msg): void { $this->climate->red("Fatal error: $msg in {$this->file}:" . $node->getStartLine()); debug_print_backtrace(); exit(255); } protected function parseArrayDimFetch($node): string { $var = $this->parseIdentifier($node->var); if ($node->dim === null) { $this->fatalError($node, 'Unsupported operand types: null + array'); } $dim = $this->parseIdentifier($node->dim); return $var . '.offsetGet(' . $this->trimBrackets($dim) . ')'; } protected function parseArrayDimStore($array, $dim, $var): string { $id = $this->parseIdentifier($array); return $id . '.offsetSet(' . $this->trimBrackets($dim) . ', ' . $this->trimBrackets($var) . ')'; } protected function parseBinaryOpShiftLeft($expr): string { return $this->parseBinaryOp($expr->left, $expr->right, '<<'); } protected function parseBinaryOpShiftRight($expr): string { return $this->parseBinaryOp($expr->left, $expr->right, '>>'); } protected function parseBinaryOpMod(mixed $expr): string { return $this->parseBinaryOp($expr->left, $expr->right, '%'); } /** * 查找原生函数 * @param string $fname * @return bool */ protected function findNativeFunction(string $fname): string|false { $possibleFunctionNames = [$this->escapeFunction($fname),]; if ($this->namespace) { $possibleFunctionNames[] = $this->escapeNamespace($this->namespace) . self::NAMESPACE_SEPARATOR . $fname; } if (isset($this->useFunctions[$fname])) { $possibleFunctionNames[] = $this->escapeNamespace($this->useFunctions[$fname]) . self::NAMESPACE_SEPARATOR . $fname; } foreach ($possibleFunctionNames as $name) { // 在预处理阶段检测到函数声明,但是未定义,说明在当前文件,但是顺序错误 if (isset($this->functionDeclInFile[$name]) and $this->functionDeclInFile[$name] === $this->file and !$this->isNativeFunction($name)) { $this->redoAfterDeclare[$name] = true; return $name; } if ($this->isNativeFunction($name)) { return $name; } } return false; } protected function parseFuncCall(Node\Expr\FuncCall $expr, bool $silent = false): string { if ($expr->name->getType() === self::EXPR_VARIABLE) { $fn = $this->parseIdentifier($expr->name); $name = ''; } elseif ($expr->name->getType() === 'Name') { $name = $this->parseIdentifier($expr->name); $nativeFn = $this->findNativeFunction($name); if ($nativeFn) { return self::PREFIX . $nativeFn . '(' . $this->parseCallArgs($expr->args, $name) . ')'; } if ($this->isInternalFunction($name)) { $fn = 'php::' . $name; } else { $fn = '"' . $name . '"'; } if ($name === 'strlen' or $name === 'sizeof' or $name === 'count') { return 'php::len(' . $this->parseIdentifier($expr->args[0]->value) . ')'; } if (count($expr->args) == 1) { switch ($name) { case 'intval': return $this->convertIntExpr($this->parseExpr($expr->args[0]->value)); case 'floatval': return $this->convertFloatExpr($this->parseExpr($expr->args[0]->value)); case 'boolval': return $this->convertBoolExpr($this->parseExpr($expr->args[0]->value)); default: break; } } } else { $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, self::TYPE_VAR); $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) . '})'; } } protected function parseCallArgs(array $args, string $funcName = '', string $className = ''): string { if (!$className) { $nativeFunction = $this->isNativeFunction($funcName); } else { $nativeFunction = false; } $list_args = []; foreach ($args as $i => $arg) { if ($arg->value->getType() === self::EXPR_VARIABLE) { $name = $this->parseIdentifier($arg->value); // 调用了不存在的变量,可能是引用 if (!$this->hasVar($name)) { $this->addLocalVar($name, self::TYPE_REF); $this->beforeStmtLines[] = $name . ' = php::newReference();'; } elseif (!$nativeFunction and $funcName) { $funcArg = Reflection::getFunctionParameter($funcName, $i); // 需要引用类型的参数,使用临时变量作为引用,并替换掉实际的参数 if ($funcArg and $funcArg->isPassedByReference()) { $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, self::TYPE_REF); $this->beforeStmtLines[] = $tmpVar . ' = ' . $this->parseExpr($arg->value) . '.toReference();'; $this->afterStmtLines[] = $name . ' = *' . $tmpVar . ';'; $list_args[] = $tmpVar; continue; } } } // 不支持变长参数展开的语法,例如:array_merge(...$arr) if ($arg->unpack) { $this->fatalError($arg, "The syntax for variable parameter expansion is not supported"); } if ($nativeFunction) { $argInfo = $this->getArgInfo($funcName, $i); $list_args[] = $this->getTypeConvertedArg($arg, $argInfo); } else { $list_args[] = $this->parseArg($arg); } } return implode(', ', $list_args); } protected function parseArg($arg): string { return $this->parseIdentifier($arg->value); } protected function parsePostInc($expr): string { return $this->parseIdentifier($expr->var) . '++'; } protected function parsePostDec($expr): string { return $this->parseIdentifier($expr->var) . '--'; } protected function parseTernary(mixed $expr): string { $cond = $expr->cond; $if = $expr->if; $else = $expr->else; return '(' . $this->parseExpr($cond) . ') ? (' . $this->parseExpr($if) . ') : (' . $this->parseExpr($else) . ')'; } protected function parseBinaryOpGreater(mixed $expr): string { return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '>')); } protected function parseBinaryOpPow(mixed $expr): string { $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); return 'php::pow(' . $left . ', ' . $right . ')'; } protected function parsePreDec(mixed $expr): string { return '--' . $this->parseIdentifier($expr->var); } protected function parseBinaryOpBitwiseAnd(mixed $expr): string { return $this->parseBinaryOp($expr->left, $expr->right, '&'); } protected function parseBinaryOpBitwiseOr(mixed $expr): string { return $this->parseBinaryOp($expr->left, $expr->right, '|'); } protected function parseBinaryOpBitwiseXor(mixed $expr): string { return $this->parseBinaryOp($expr->left, $expr->right, '^'); } protected function parseBitwiseNot(mixed $expr): string { $var = $this->parseIdentifier($expr->expr); return '~' . $var; } protected function parseIf(mixed $v): string { $cond = $this->parseExpr($v->cond); $code = 'if (' . $cond . ') {' . PHP_EOL; $this->indentLevel++; $code .= $this->parseStmts($v->stmts); $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 .= $this->parseStmts($elseif->stmts); $this->indentLevel--; $code .= $this->getIndent() . '}'; } } if ($v->else) { $code .= ' else {' . PHP_EOL; $this->indentLevel++; $code .= $this->parseStmts($v->else->stmts); $this->indentLevel--; $code .= $this->getIndent() . '}'; } return $code . PHP_EOL; } protected function parseBinaryOpEqual(mixed $expr): string { 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) . ')'; } /** * 逻辑比较的运算,必须返回 bool 类型 */ protected function parseBinaryOpLogicalAnd(Node $expr): string { return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '&&')); } protected function parseBinaryOpLogicalOr(Node $expr): string { return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '||')); } protected function parseBinaryOpLogicalXor(Node $expr): string { return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '^')); } protected function parseBooleanNot(Node $expr): string { $expr = $this->parseExpr($expr->expr); return '!' . $expr; } protected function parseWhile(Node $v): string { $cond = $this->parseExpr($v->cond); $stmts = $v->stmts; $code = 'while (' . $cond . ') {' . PHP_EOL; $this->indentLevel++; $code .= $this->parseStmts($stmts); $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 ($bracketCount === 0) { return $i === $length - 1; } } } return false; } protected function trimBrackets(string $str): string { if ($this->isClosedCall($str, '')) { return substr($str, 1, -1); } return $str; } protected function convertIntExpr(string $expr): string { if (!$this->isClosedCall($expr, 'php::to_int')) { return 'php::to_int(' . $this->trimBrackets($expr) . ')'; } return $expr; } protected function convertFloatExpr(string $expr): string { if (!$this->isClosedCall($expr, 'php::to_float')) { return 'php::to_float(' . $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::to_string')) { return 'php::to_string(' . $this->trimBrackets($expr) . ')'; } return $expr; } protected function convertObjectExpr(string $expr): string { if (!$this->isClosedCall($expr, 'php::to_object')) { return 'php::to_object(' . $this->trimBrackets($expr) . ')'; } return $expr; } protected function convertArrayExpr(string $expr): string { if (!$this->isClosedCall($expr, 'php::to_array')) { return 'php::to_array(' . $this->trimBrackets($expr) . ')'; } return $expr; } protected function convertBoolExpr(string $expr): string { if (!$this->isClosedCall($expr, 'php::to_bool')) { return 'php::to_bool(' . $this->trimBrackets($expr) . ')'; } return $expr; } protected function parseBinaryOpSmallerOrEqual(Node\Expr\BinaryOp\SmallerOrEqual $expr): string { return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '<=')); } protected function parseBinaryOpGreaterOrEqual(Node\Expr\BinaryOp\GreaterOrEqual $expr): string { return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '>=')); } protected function parsePrint(Node\Expr\Print_ $expr): string { return 'php::echo(' . $this->parseExpr($expr->expr) . ')'; } protected function parseDo(Node\Stmt\Do_ $v): string { $stmts = $v->stmts; $cond = $this->parseExpr($v->cond); $code = 'do {' . PHP_EOL; $this->indentLevel++; $code .= $this->parseStmts($stmts); $this->indentLevel--; $code .= $this->getIndent() . '} while (' . $cond . ');' . PHP_EOL; return $code; } protected function parseBinaryOpIdentical(Node\Expr\BinaryOp $expr): string { $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); if ($right === 'nullptr') { return $left . '.isNull()'; } return 'php::same(' . $left . ', ' . $right . ')'; } protected function parseBinaryOpSpaceship(Node\Expr\BinaryOp\Spaceship $expr): string { $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); return 'php::compare(' . $left . ', ' . $right . ')'; } protected function parseBinaryOpNotIdentical(Node\Expr\BinaryOp $expr): string { return '!(' . $this->parseBinaryOpIdentical($expr) . ')'; } protected function parseNew(Node\Expr\New_ $expr): string { $className = $this->parseIdentifier($expr->class); $args = $expr->args; if (empty($args)) { return 'php::newObject("' . $className . '")'; } else { return 'php::newObject("' . $className . '", ' . $this->parseCallArgs($args) . ')'; } } protected function parseClone(Node\Expr\Clone_ $expr): string { $var = $this->parseIdentifier($expr->expr); return $var . '.clone()'; } protected function parseInstanceof(Node\Expr\Instanceof_ $expr): string { $var = $this->parseIdentifier($expr->expr); return $var . '.instanceOf(' . $this->identifierToStr($expr->class) . ')'; } protected function parseCastInt(Node\Expr\Cast\Int_ $node): string { return $this->convertIntExpr($this->parseExpr($node->expr)); } protected function parseCastString(Node\Expr\Cast\String_ $node): string { return $this->convertStringExpr($this->parseExpr($node->expr)); } protected function parseCastBool(Node\Expr\Cast\Bool_ $node): string { return $this->convertBoolExpr($this->parseExpr($node->expr)); } protected function parseCastObject(Node\Expr\Cast\Object_ $node): string { return $this->convertObjectExpr($this->parseExpr($node->expr)); } protected function parseConstFetch(Node\Expr\ConstFetch $expr): string { 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 ($name === 'null') { return 'nullptr'; } elseif ($name === 'true') { return 'true'; } elseif ($name === 'false') { return 'false'; } elseif ($name === 'PHP_EOL') { return '"' . $this->escapeString(PHP_EOL) . '"'; } return 'php::constant("' . $name . '")'; } protected function parseUnaryMinus(Node $expr): string { $code = $this->parseExpr($expr->expr); return '-' . $code; } protected function parseUnaryPlus(mixed $expr) { return $this->parseExpr($expr->expr); } protected function parseBinaryOpDiv(Node\Expr\BinaryOp\Div $expr): string { $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); return $left . ' / (' . $right . ')'; } protected function parseBinaryOpMinus(Node\Expr\BinaryOp\Minus $expr): string { return $this->parseBinaryOp($expr->left, $expr->right, '-'); } protected function parseInterpolatedString(Node\Scalar\InterpolatedString $expr): string { $parts = $expr->parts; $list = []; foreach ($parts as $part) { $list[] = $this->parseExpr($part); } return 'php::concat({' . implode(', ', $list) . '})'; } protected function escapeString(string $str): string { return addcslashes($str, "\\\"\n\r\t\v\f\0\x01..\x1f\x7f..\xff"); } protected function escapeVarName(string $name): string { if (in_array($name, Constants::CPP_RESERVED_NAMES)) { return '_php__var__' . $name; } elseif ($name === 'this') { return 'this_'; } else { return $name; } } protected function escapeNamespace(string $ns): string { return str_replace('\\', self::NAMESPACE_SEPARATOR, strtolower($ns)); } protected function escapeFunction(string $fname): string { return strtolower($fname); } protected function escapeClass(string $class): string { return $class; } protected function unescapeVarName(string $name): string { return str_replace('_php__var__', '', $name); } protected function parseInterpolatedStringPart(Node $expr): string { return '"' . $this->escapeString($expr->value) . '"'; } protected function parseGlobal(Node $v): string { foreach ($v->vars as $v) { $name = $this->escapeVarName($v->name); if (!$this->hasGlobalVar($name)) { $this->addGlobalVar($name, self::TYPE_VAR); } } return ''; } protected function getArgInfo(string $funcName, int $index): ArgInfo { $funcDef = $this->nativeFunctions[$funcName]; return $funcDef->argInfoList[$index]; } protected function getReturnType(): string { return $this->functionDef->returnType; } protected function getTypeConvertedArg($arg, $argInfo): string { $expr = $this->parseArg($arg); $type = $this->detectExprType($arg->value); return $this->convertExprType($expr, $argInfo->type, $type); } protected function convertExprType(string $expr, $leftType, $rightType): string { if ($leftType === self::TYPE_FLOAT or $rightType === self::TYPE_FLOAT) { return $this->convertFloatExpr($expr); } if ($leftType === self::TYPE_INT or $rightType === self::TYPE_INT) { return $this->convertIntExpr($expr); } if ($leftType === self::TYPE_BOOL or $rightType === self::TYPE_BOOL) { return $this->convertBoolExpr($expr); } return $expr; } protected function parseExit(Node $node): string { return 'php::exit(' . $this->parseIdentifier($node->expr) . ')'; } protected function parseUnset(Node $node): string { $vars = $node->vars; $lines = []; foreach ($vars as $var) { $type = $var->getType(); 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 ($type === self::EXPR_VARIABLE) { $name = $this->parseIdentifier($var); $lines[] = "$name.unset();"; } else { abort($var); } } return implode(PHP_EOL . $this->getIndent(), $lines); } protected function parsePropertyFetch(Node $expr): string { return $this->convertToObject($expr->var) . '.getProperty("' . $this->parseIdentifier($expr->name) . '")'; } protected function parseAssignOpShiftRight(Node $node): string { $var = $this->parseIdentifier($node->var); return $var . ' >>= ' . $this->parseIdentifier($node->expr); } protected function parseAssignOpBitwiseXor(Node $node): string { $var = $this->parseIdentifier($node->var); return $var . ' ^= ' . $this->parseIdentifier($node->expr); } protected function parseMagicConst(MagicConst $expr): string { switch ($expr->getType()) { case 'Scalar_MagicConst_Dir': return '"' . $this->escapeString($this->dir) . '"'; case 'Scalar_MagicConst_File': return '"' . $this->escapeString($this->file) . '"'; case 'Scalar_MagicConst_Line': return (string)$expr->getStartLine(); case 'Scalar_MagicConst_Function': return '"' . $this->escapeString($this->functionDef->name) . '"'; case 'Scalar_MagicConst_Class': return '"' . $this->escapeString($this->classDef->name) . '"'; default: abort($expr); } } protected function parseForeach(Foreach_ $node): string { if ($node->byRef) { $this->fatalError($node, 'Cannot use & with foreach'); } if ($node->keyVar) { $keyVar = $this->parseIdentifier($node->keyVar); } $valueVar = $this->parseIdentifier($node->valueVar); $iteratorVar = $this->genTmpVarName(); $stmts = $node->stmts; $code = ''; $expr = $this->parseIdentifier($node->expr); $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 .= self::TYPE_VAR . ' ' . $this->getIndent() . ' ' . $keyVar . ' = iter.key();' . PHP_EOL; } if ($node->valueVar->getType() == self::EXPR_ARRAY_DIM_FETCH) { $array = $this->parseIdentifier($node->valueVar->var); 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());"; } else { $code .= self::TYPE_VAR . ' ' . $this->getIndent() . ' ' . $valueVar . ' = iter.value();' . PHP_EOL; } $code .= $this->parseStmts($stmts); $this->indentLevel--; $code .= $this->getIndent() . '}'; return $code; } protected function formatCppCode(string $file): void { $cmd = 'cd ' . $this->rootPath . ' && clang-format -i ' . $file; $this->writeLog('formatting ' . $file . '...'); $this->writeLog($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 ($name === 'true') { return self::TYPE_BOOL; } if ($name === 'false') { return self::TYPE_BOOL; } if ($name === 'NAN' or $name === 'INF') { return self::TYPE_FLOAT; } return self::TYPE_VAR; } protected function parseSwitch(mixed $v): string { $cond = $v->cond; $tmp_var = $this->genTmpVarName(); $type = $this->detectExprType($cond); $var_def = $type . ' ' . $tmp_var . ' = ' . $this->parseExpr($cond) . ';' . PHP_EOL; // 保存作用域,switch 可能会解析失败,在这个过程中会增加变量,需重置 $localVars = $this->localVars; 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; } else { $condType = $case->cond->getType(); if ($condType !== 'Scalar_Int' and $condType !== 'Scalar_Float') { $this->localVars = $localVars; goto _fail; } $code .= $this->getIndent() . 'case ' . $this->parseScalar($case->cond) . ': {' . PHP_EOL; } $this->indentLevel++; $code .= $this->parseStmts($case->stmts); $this->indentLevel--; $code .= $this->getIndent() . '}' . PHP_EOL; } $this->indentLevel--; $code .= $this->getIndent() . '}'; return $var_def . $code; } _fail: $code = 'do {' . PHP_EOL; $this->indentLevel++; foreach ($v->cases as $case) { if (empty($case->cond)) { $code .= $this->getIndent() . 'else {' . PHP_EOL; } else { $code .= $this->getIndent() . 'if (' . $tmp_var.'=='. $this->parseIdentifier($case->cond) . ') {' . PHP_EOL; } $this->indentLevel++; $code .= $this->parseStmts($case->stmts); $this->indentLevel--; $code .= $this->getIndent() . '}' . PHP_EOL; } $this->indentLevel--; $code .= $this->getIndent() . '} while (0);'; return $var_def . $code; } protected function parseStatic(mixed $v): string { $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) . ';'; } else { $list[] = 'static ' . self::TYPE_VAR . ' ' . $this->parseIdentifier($var->var) . ';'; } } return implode(PHP_EOL . $this->getIndent(), $list); } protected function parseEval(mixed $expr): string { return 'php::eval(' . $this->parseIdentifier($expr->expr) . ')'; } protected function parseInclude(mixed $expr): string { return 'php::include(' . $this->parseIdentifier($expr->expr) . ')'; } protected function parseBreak(mixed $v): string { if (!$this->inLoop and !$this->inSwitch) { $this->fatalError($v, 'Cannot break outside loop'); } $num = $v->num; if ($num) { $value = $this->parseIdentifier($num); if ($value > 1) { $this->fatalError($v, 'Cannot break more than 1 level'); } } return 'break;'; } protected function parseScalarFloat(Node $expr): string { $value = $expr->value; 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) { return number_format($value, 1, '.', ''); } else { return sprintf('%.' . $this->floatPrecision . 'g', $value); } } protected function parseIsset(mixed $expr) { $vars = $expr->vars; foreach ($vars as $var) { $type = $var->getType(); if ($type === self::EXPR_VARIABLE) { return $this->hasVar($var->name) ? 'true' : 'false'; } elseif ($type === self::EXPR_ARRAY_DIM_FETCH) { return $this->parseIdentifier($var->var) . ".offsetExists(" . $this->parseIdentifier($var->dim) . ')'; } else { abort($var); } } } protected function parseEmpty(mixed $expr): string { return 'php::empty(' . $this->parseExpr($expr->expr) . ')'; } protected function parseCastArray(mixed $expr): string { return $this->convertArrayExpr($this->parseIdentifier($expr->expr)); } protected function hasGlobalVar($name): bool { return array_key_exists($name, $this->globalVars); } protected function parseCastDouble(mixed $expr): string { return $this->convertFloatExpr($this->parseIdentifier($expr->expr)); } protected function detectFuncCallReturnType(string $name): string { $returnType = Reflection::getFunctionReturnType($name); if ($returnType) { return $this->getTypeFromZendType($returnType); } else { return self::TYPE_VAR; } } protected function convertVarType($var, $expr): string { if ($this->hasVar($var)) { $type = $this->getVarType($var); if ($type === self::TYPE_FLOAT) { return $this->convertFloatExpr($expr); } if ($type === self::TYPE_INT) { return $this->convertIntExpr($expr); } if ($type === self::TYPE_BOOL) { return $this->convertBoolExpr($expr); } } return $expr; } protected function convertToObject(Node $object): string { $id = $this->parseIdentifier($object); if (!$this->hasVar($id)) { $this->addLocalVar($id, self::TYPE_OBJECT); return $id; } $type = $this->getVarType($id); if ($type === self::TYPE_OBJECT) { return $id; } if (isset($this->objectWrappers[$id])) { return $this->objectWrappers[$id]; } $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, self::TYPE_OBJECT); $this->beforeStmtLines[] = $this->getIndent() . $tmpVar . ' = ' . $id . ';'; $this->objectWrappers[$id] = $tmpVar; return $tmpVar; } protected function parseAssignRef(Node\Expr\AssignRef $expr): string { if ($expr->var->getType() === self::EXPR_VARIABLE) { if ($expr->expr->getType() === self::EXPR_VARIABLE) { return $this->parseIdentifier($expr->var) . ' = &' . $this->parseIdentifier($expr->expr); } elseif ($expr->expr->getType() === self::EXPR_ARRAY_DIM_FETCH) { return $this->parseIdentifier($expr->var) . ' = ' . $this->parseIdentifier($expr->expr); } } abort($expr); } protected function parseMethodCall(mixed $expr): string { $object = $this->convertToObject($expr->var); $method = $this->parseIdentifier($expr->name); if (empty($expr->args)) { return $object . '.exec("' . $method . '")'; } else { return $object . '.exec("' . $method . '", ' . $this->parseCallArgs($expr->args) . ')'; } } protected function identifierToStr(Node $node, bool $require = true): string { $id = $this->parseIdentifier($node); if ($node->getType() === self::EXPR_VARIABLE) { if ($require) { $this->requireVar($node, $id); } return $id; } else { return '"'. $id . '"'; } } protected function requireVar($node, string $var): void { if (!$this->hasVar($var)) { $this->fatalError($node, 'The variable `' . $var . '` is not defined'); } } protected function parseStaticCall(mixed $expr): string { if ($expr->class->getType() === self::EXPR_VARIABLE or $expr->name->getType() === self::EXPR_VARIABLE) { $fn = 'php::concat({' . $this->identifierToStr($expr->class). ', "::", ' . $this->identifierToStr($expr->name) . '})'; } else { $class = $this->parseIdentifier($expr->class); $method = $this->parseIdentifier($expr->name); $fn = '"'. $class . '::' . $method . '"'; } if (empty($expr->args)) { return 'php::call(' . $fn . ')'; } else { 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) . ')'; } protected function parseThrow(mixed $expr): string { if ($expr->expr->getType() != self::EXPR_VARIABLE 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). ')'; } protected function parseTryCatch(mixed $v): string { $code = 'zend_try {'; $stmts = $v->stmts; $code .= PHP_EOL; $this->indentLevel++; $code .= $this->parseStmts($stmts); $this->indentLevel--; $code .= $this->getIndent() . '}' . PHP_EOL; $catches = $v->catches; $finally = $v->finally; $exVar = $this->genTmpVarName(); $this->addLocalVar($exVar, self::TYPE_OBJECT); $code .= 'zend_catch {' . PHP_EOL; if ($catches) { $code .= $this->getIndent() . $exVar . ' = php::catchException();' . PHP_EOL; $this->indentLevel++; foreach ($catches as $catch) { $code .= $this->parseCatch($catch, $exVar); } $this->indentLevel--; } $code .= '}' . PHP_EOL . 'zend_end_try();' . PHP_EOL; if ($finally) { $code .= $this->parseStmts($finally->stmts); $code .= PHP_EOL; } $code .= 'if (' . $exVar . ') {' . PHP_EOL . $this->getIndent() . 'php::throwException(' . $exVar . ');' . PHP_EOL . $this->getIndent() . '}'; return $code; } protected function parseCatch(mixed $catch, string $exVar): string { $types = $catch->types; $var = $this->parseIdentifier($catch->var); if (!$this->hasVar($var)) { $this->addLocalVar($var, self::TYPE_OBJECT); } $code = $this->getIndent() . $var . ' = ' . $exVar . ';' . PHP_EOL; $code .= $this->getIndent() . 'if (' . $var . ' && '; foreach ($types as $type) { $code .= 'php::instanceOf(' . $var . ', "' . $this->parseIdentifier($type) . '")'; } $code .= ') {' . PHP_EOL; $this->indentLevel++; $code .= $this->parseStmts($catch->stmts); $code .= $this->getIndent() . "$exVar.unset();" . PHP_EOL; $this->indentLevel--; $code .= $this->getIndent() . '}'; return $code; } protected function parseShellExec(mixed $expr): string { 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 . ';'; } protected function parseLabel(Node $v): string { $this->fatalError($v, 'Label statement is not supported'); return $v->name->name . ':'; } protected function parseConstDef(mixed $v2): string { foreach ($v2->consts as $const) { $name = $this->parseIdentifier($const->name); $value = $this->parseIdentifier($const->value); $this->addConstant($name, $value); } return ''; } protected function addConstant(string $name, string $value): void { $constInfo = new stdClass(); $constInfo->value = $value; $constInfo->type = $this->detectStrValueType($value); $this->nativeConstants[$name] = $constInfo; } protected function hasConstant(string $name): bool { return isset($this->nativeConstants[$name]); } protected function getConstant(string $name): string { return $this->nativeConstants[$name]->value; } protected function getConstantType(string $name): string { return $this->nativeConstants[$name]->type; } protected function detectStrValueType(mixed $constant): string { if ($this->isIntStr($constant)) { return self::TYPE_INT; } if ($this->isFloatStr($constant)) { return self::TYPE_FLOAT; } if ($this->isBoolStr($constant)) { return self::TYPE_BOOL; } return self::TYPE_VAR; } protected function isArrayVar($var): bool { return $var->getType() == self::EXPR_VARIABLE and $this->hasVar($var->name) and $this->getVarType($var->name) == self::TYPE_ARRAY; } protected function setBuildDir(string $string): void { $this->buildDir = $string; if (!is_dir($this->buildDir)) { mkdir($this->buildDir, 0777, true); } } protected function isStubFile(string $file): bool { return str_ends_with($file, '.stub.php'); } /** * @param string $file * @return string * @throws \Exception */ protected function loadFile(string $file): string { if (!file_exists($file)) { throw new \Exception('File not exists: ' . $file); } $phpCode = file_get_contents($file); if (!$phpCode) { throw new \Exception('Can not read file: ' . $file); } $this->file = realpath($file); $this->dir = dirname($this->file); $this->stubFile = $this->isStubFile($file); return $phpCode; } /** * @param string $file * @param string $content * @return void */ protected 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); $value = $this->parseIdentifier($declare->value); if ($key === 'ticks') { $this->fatalError($v, 'declare(ticks=1) is not 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)); } } protected function parseUse(mixed $v2): string { $code = ''; if ($this->useCppNamespace) { foreach ($v2->uses as $use) { $code .= 'using ' . str_replace('\\', '::', $use->name->toString()) . ';' . PHP_EOL; } } else { foreach ($v2->uses as $use) { $id = $this->parseIdentifier($use->name); if ($use->type == Node\Stmt\Use_::TYPE_NORMAL) { $this->useNamespaces[] = $id; } else { $rpos = strrpos($id, '\\'); $fn = substr($id, $rpos + 1); $ns = substr($id, 0, $rpos); // fn => namespace $this->useFunctions[$fn] = $ns; } } } return $code; } protected function parseErrorSuppress(Node\Expr\ErrorSuppress $expr): string { if ($expr->expr instanceof Node\Expr\FuncCall) { return $this->parseFuncCall($expr->expr, true); } abort($expr); } }