TypePHP 编译器 https://swoole.com/aot/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

2317 lines
76 KiB

<?php
namespace PhpAot\Php;
use League\CLImate\CLImate;
use PhpAot\Php\Visitor;
use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Error;
use PhpParser\Node\NullableType;
use PhpParser\NodeTraverser;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter;
use SimplePie\Exception;
class Translator extends \PhpAot\Core\Translator
{
const string TYPE_VAR = 'php::Var';
const string TYPE_BOOL = 'php::Bool';
const string TYPE_INT = 'php::Int';
const string TYPE_FLOAT = 'php::Float';
const string TYPE_OBJECT = 'php::Object';
const string TYPE_ARRAY = 'php::Array';
const string TYPE_STR = 'php::Str';
const string TYPE_REF = 'php::Var';
const string VALUE_NAN = 'std::numeric_limits<double>::quiet_NaN()';
const string VALUE_INF = 'std::numeric_limits<double>::infinity()';
const string LITERAL_STRINGS = '_literal_strings';
const string EXPR_VARIABLE = 'Expr_Variable';
const string EXPR_ARRAY_DIM_FETCH = 'Expr_ArrayDimFetch';
private string $phpxDir = '~/workspace/projects/phpx';
protected string $lang = 'PHP';
private string $cppCompiler = 'g++';
private array $arguments = [];
private array $literalStrings = [];
private int $literalStringIndex = 0;
private int $tmpVarIndex = 0;
private array $zendTypeMap = [
'int' => self::TYPE_INT,
'float' => self::TYPE_FLOAT,
'bool' => self::TYPE_BOOL,
];
private array $headers = [
'phpx.h',
'phpx_helper.h',
'phpx_func.h',
'php_func_decl.h',
];
private array $reservedNames = [
'auto',
'break',
'case',
'catch',
'class',
'struct',
'const',
'continue',
'default',
'do',
'else',
'elseif',
'enum',
'extends',
'final',
'finally',
'for',
'function',
'global',
'if',
'int',
'double',
'float',
'false',
'for',
'if',
'int',
'new',
'null',
'or',
'and',
'private',
'protected',
'public',
'return',
'static',
'pipe',
];
private array $nativeFunctions = [];
private array $internalFunctions = [];
private int $optimizeLevel = 0;
private int $floatPrecision = 17;
private bool $debugInfo = true;
private bool $noLiteralStrings = false;
private bool $verbose = false;
private string $file;
private string $dir;
private string $namespace = '';
private array $uses = [];
private string $class = '';
private FunctionDef $functionDef;
private 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,
];
private array $localVars = [];
private array $objectWrappers = [];
const string PREFIX = 'php_';
private string $rootPath;
private int $debugLine = 0;
private CLImate $climate;
private array $beforeStmtLines = [];
private array $afterStmtLines = [];
public function __construct(string $rootPath)
{
$this->rootPath = $rootPath;
$climate = new CLImate;
$this->climate = $climate;
$climate->arguments->add([
'optimize' => [
'prefix' => 'O',
'longPrefix' => 'optimize',
'description' => 'Set the optimization level of the gcc compiler to 0 by default',
'required' => false,
'castTo' => 'int',
'defaultValue' => 0,
],
'output' => [
'prefix' => 'o',
'longPrefix' => 'output',
'description' => 'Output file',
],
'help' => [
'prefix' => 'h',
'longPrefix' => 'help',
'description' => 'Show help',
'noValue' => true,
],
'profile' => [
'longPrefix' => 'profile',
'description' => 'Enable performance profiling',
'required' => false,
'noValue' => true,
],
'noLiteralStrings' => [
'longPrefix' => 'no-literal-strings',
'description' => 'Disable literal strings optimization',
'required' => false,
'noValue' => true,
],
]);
$this->preprocessArgvAdvanced();
$climate->arguments->parse();
if ($climate->arguments->defined('help')) {
$this->showUsage();
exit(0);
}
// $this->noLiteralStrings = $climate->arguments->get('no-literal-strings');
$this->noLiteralStrings = true;
$this->optimizeLevel = $climate->arguments->get('optimize');
$this->internalFunctions = array_flip(get_defined_functions()['internal']);
}
function showUsage(): void
{
$climate = $this->climate;
$climate->bold()->green('PHP AOT Compiler v1.0.0');
$climate->br();
$climate->bold('USAGE:');
$climate->tab()->out('./bin/compiler.php <file/dir> [options]');
$climate->br();
$climate->bold('ARGUMENTS:');
$climate->tab()->out('<file> Input PHP file/directory to compile');
$climate->br();
$climate->bold('OPTIONS:');
$climate->tab()->out('-O <level> Optimization level (0-3, default: 0)');
$climate->tab()->out('-p, --profile Enable performance profiling');
$climate->tab()->out('-o, --output <file> Output binary name (default: input basename)');
$climate->tab()->out('-v, --verbose Verbose output');
$climate->tab()->out('-h, --help Show this help message');
$climate->tab()->out('--no-literal-strings Disable literal strings optimization');
$climate->br();
$climate->bold('EXAMPLES:');
$climate->tab()->out('./bin/compiler.php examples/hello.php');
$climate->tab()->out('./bin/compiler.php examples/bench.php -O2');
$climate->tab()->out('./bin/compiler.php examples/bench.php -O2 -p');
$climate->tab()->out('./bin/compiler.php examples/app.php -O3 -o myapp -v');
$climate->br();
}
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 genIncludeHeaderFiles(): string
{
$lines = [];
foreach ($this->headers as $header) {
$lines[] = '#include <' . $header . '>';
}
return implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL;
}
public function genExternGlobalVars(): string
{
$lines[] = PHP_EOL;
foreach ($this->globalVars as $name => $type) {
$lines[] = 'extern ' . self::TYPE_VAR . ' ' . $name . ';';
}
$literalStringsCount = count($this->literalStrings);
$lines[] = 'extern php::Var ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '];' . PHP_EOL;
return implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL;
}
public function setPhpxDir($dir): void
{
$this->phpxDir = $dir;
}
private function doConvert(string $phpCode): string
{
$this->climate->info('do convert: ' . $this->file);
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$ast = $parser->parse($phpCode);
$traverser = new NodeTraverser;
$prettyPrinter = new PrettyPrinter\Standard;
$traverser->addVisitor(new Visitor());
$stmts = $traverser->traverse($ast);
$this->indentLevel = 0;
$cppCode = '';
foreach($stmts as $v) {
$type = $v->getType();
switch ($type) {
case 'Stmt_Namespace':
$cppCode .= $this->parseNamespaceDef($v);
break;
case 'Stmt_Class':
$cppCode .= $this->parseClassDef($v);
break;
case 'Stmt_Function':
$cppCode .= $this->parseFunctionDef($v) . PHP_EOL;
break;
default:
abort($v);
}
}
// include + extern global vars + function impl
return $this->genIncludeHeaderFiles() . $this->genExternGlobalVars() . $cppCode;
}
public function convert(string $file): string
{
if (!file_exists($file)) {
throw new \Exception('File not exists: ' . $file);
}
$phpCode = file_get_contents($file);
$this->file = realpath($file);
$this->dir = dirname($this->file);
while (true) {
try {
return $this->doConvert($phpCode);
} catch (ReturnTypeChanged $e) {
// 某些情况,例如返回值变更,需要重新解析
$this->climate->cyan('Return type changed, retrying...');
continue;
}
}
}
public function save(string $code, string $file): void
{
file_put_contents($file, $code);
$this->formatCppCode($file);
}
public function getLine($node): int
{
return $node->getLine();
}
public function getType($node): string
{
return $node->getType();
}
private 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]);
}
private function resetScope(): void
{
$this->localVars = [];
$this->arguments = [];
$this->tmpVarIndex = 0;
}
private function parseFunctionDef($v): string
{
$this->resetScope();
$names[] = $this->parseIdentifier($v->name);
if ($this->class) {
$names[] = strtolower($this->class);
}
if ($this->namespace) {
$names[] = strtolower(str_replace('\\', '_', $this->namespace));
}
$name = implode('__', array_reverse($names));
if (isset($this->nativeFunctions[$name])) {
$this->functionDef = $this->nativeFunctions[$name];
} else {
$this->functionDef = new FunctionDef();
$this->functionDef->name = $name;
if ($v->returnType) {
$this->functionDef->returnType = $this->getTypeFromZendType($this->parseIdentifier($v->returnType));
} else {
$this->functionDef->returnType = 'void';
}
$this->parseParams($v->params);
$this->nativeFunctions[$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 . '(' . $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;
}
private function writeLog($msg)
{
if ($this->verbose) {
echo $msg . PHP_EOL;
}
}
private 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 . ']';
}
default:
abort($expr);
}
}
private function parseIdentifier(Node $expr): string
{
$type = $expr->getType();
switch ($type) {
case self::EXPR_VARIABLE:
return $this->escapeVarName($expr->name);
case 'Name':
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);
}
}
private function parseParams($params): void
{
$list = [];
$this->functionDef->argCountRequired = count($params);
foreach ($params as $param) {
$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->parseScalar($param->default);
}
$this->functionDef->argInfoList[] = $argInfo;
}
$this->functionDef->params = implode(', ', $list);
}
private function parseStmts(array $stmts): string
{
$lines = [];
foreach ($stmts as $v) {
$class = $v->getType();
$this->beforeStmtLines = [];
$this->afterStmtLines = [];
$this->writeLog('Line ' . $this->getLine($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':
$result = $this->parseFor($v);
break;
case 'Stmt_Foreach':
$result = $this->parseForeach($v);
break;
case 'Stmt_Switch':
$result = $this->parseSwitch($v);
break;
case 'Stmt_While':
$result = $this->parseWhile($v);
break;
case 'Stmt_Do':
$result = $this->parseDo($v);
break;
case 'Stmt_If':
$result = $this->parseIf($v);
break;
case 'Stmt_Break':
$result = $this->parseBreak($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;
}
private 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_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_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_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_Throw':
return $this->parseThrow($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':
return $this->parseMagicConstFile($expr);
case 'Scalar_MagicConst_Dir':
return $this->parseMagicConstDir($expr);
case 'Scalar_MagicConst_Line':
return $this->parseMagicConstLine($expr);
case 'Scalar_InterpolatedString':
return $this->parseInterpolatedString($expr);
case 'Expr_Cast_Int':
return $this->parseCastInt($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_Cast_Array':
return $this->parseCastArray($expr);
case 'Expr_Cast_Double':
return $this->parseCastDouble($expr);
case 'Expr_Exit':
return $this->parseExit($expr);
default:
abort($expr);
}
}
private 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->parseIdentifier($left->name);
return "$array.setProperty($propName, " . $this->trimBrackets($this->parseExpr($right)) . ")";
} 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);
}
private function parseFinallyAssign($left, $right): string
{
$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));
}
private function parseEcho(mixed $v): string
{
foreach ($v->exprs as $expr) {
$lines[] = 'php::echo(' . $this->parseExpr($expr) . ');';
}
return implode("\n" . $this->getIndent(), $lines);
}
private function isFloatStr(string $str)
{
return filter_var($str, FILTER_VALIDATE_FLOAT);
}
private function isIntStr(string $str)
{
return filter_var($str, FILTER_VALIDATE_INT);
}
private function isInternalFunction(string $fname): bool
{
return array_key_exists($fname, $this->internalFunctions);
}
private function isAssignOpConcat(string $op): bool
{
return $op === '.=';
}
/**
* 尽可能转为数字,优先级 浮点 > 整数 > 字符串
*/
private function parseNumericIdentifier($expr)
{
if ($expr->getType() === 'Scalar_String') {
if ($this->isFloatStr($expr->value)) {
return floatval($expr->value);
} else if ($this->isIntStr($expr->value)) {
return intval($expr->value);
} else if ($expr->value === '0') {
return 0;
}
}
return $this->parseIdentifier($expr);
}
private 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 . ')';
}
private function parseBinaryOpPlus(mixed $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '+');
}
private 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);
}
private function parseBinaryOpMul(mixed $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '*');
}
private function addLocalVar(string $name, string $type): void
{
$this->localVars[$name] = $type;
}
private function addLiteralString(string $value): int
{
$index = $this->literalStringIndex++;
$this->literalStrings[$value] = $index;
return $index;
}
private function addGlobalVar(string $name, string $type): void
{
$this->globalVars[$name] = $type;
}
private function hasVar(string $name): bool
{
return $this->hasLocalVar($name) || $this->hasGlobalVar($name);
}
private function hasLocalVar(string $name): bool
{
return isset($this->localVars[$name]);
}
private function resetReturnType(string $type): void
{
$this->functionDef->returnType = $type;
throw new ReturnTypeChanged;
}
private function detectVarType($var): string
{
$name = $this->parseIdentifier($var);
return $this->getVarType($name);
}
private 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($expr);
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;
}
private 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() .
'}';
}
private function parseType($type)
{
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;
default:
abort($type);
}
}
private function parseIncludes(): string
{
$list = [
$this->phpxDir . '/include',
'./',
];
$out = '$(php-config --includes) ';
foreach ($list as $li) {
$out .= '-I ' . $li . ' ';
}
return $out;
}
private function parseLdflags(): string
{
$list = [
'$(php-config --prefix)/lib',
$this->phpxDir . '/lib',
];
$out = '';
foreach ($list as $li) {
$out .= '-L ' . $li . ' ';
}
return $out;
}
private function parseLibs()
{
$list = [
'phpx',
'php',
];
$out = '';
foreach ($list as $li) {
$out .= '-l' . $li . ' ';
}
return $out;
}
private 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';
}
}
public function compileFile($file): void
{
$cmd = $this->cppCompiler . ' -c ' . $file . ' -o ' . $file . '.o ';
$this->addCompilationOption($cmd);
$this->climate->comment($cmd);
shell_exec($cmd);
}
public function compileBinary(string $targetFile, array $objectFiles): void
{
$this->genGlobalVars();
if ($this->climate->arguments->defined('output')) {
$targetFile = $this->climate->arguments->get('output');
}
$objectFile = implode(' ', $objectFiles);
$cmd = $this->cppCompiler . ' main.cc global_vars.cc ' . $objectFile . ' -o ' . $targetFile . ' ' . $this->parseLdflags() . $this->parseLibs();
$this->addCompilationOption($cmd);
$this->climate->comment($cmd);
shell_exec($cmd);
}
private function parseBinaryOpConcat(mixed $expr): string
{
$left = $this->parseIdentifier($expr->left);
$right = $this->parseIdentifier($expr->right);
return 'php::concat(' . $left . ', ' . $right . ')';
}
private 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);
if (!$this->hasGlobalVar($name)) {
$this->fatalError($left, 'Cannot assign to global variable in for loop');
}
if (!$this->hasVar($name)) {
$type = $this->detectExprType($expr->expr);
$this->addLocalVar($name, $type);
}
}
$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;
}
private function parseBinaryOpSmaller(mixed $expr): string
{
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '<'));
}
private function parsePreInc(mixed $expr): string
{
return '++' . $this->parseIdentifier($expr->var);
}
private function removeAssignOp(string $op): string
{
return str_replace('=', '', $op);
}
private 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) {
$type = $this->detectVarType($node->var);
$rightExprStr = $this->convertExprType($expr, $type, $this->detectExprType($node->expr));
if ($this->isAssignOpConcat($op)) {
return $var . '.append(' . $rightExprStr . ')';
} 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 属性设置
}
private function parseAssignOpConcat(mixed $expr): string
{
return $this->parseAssignOp($expr, '.=');
}
private function parseAssignOpPlus(mixed $expr): string
{
return $this->parseAssignOp($expr, '+=');
}
private function parseAssignOpMinus(mixed $expr): string
{
return $this->parseAssignOp($expr, '-=');
}
private function parseAssignOpMod(mixed $expr): string
{
return $this->parseAssignOp($expr, '%=');
}
private function parseAssignOpMul(mixed $expr): string
{
return $this->parseAssignOp($expr, '*=');
}
private function parseAssignOpDiv(mixed $expr): string
{
return $this->parseAssignOp($expr, '/=');
}
private function parseAssignOpBitwiseAnd(mixed $expr): string
{
return $this->parseAssignOp($expr, '&=');
}
private function fatalError(Node $node, string $msg): void
{
$this->climate->red("Fatal error: $msg in {$this->file}:" . $node->getStartLine());
debug_print_backtrace();
exit(255);
}
private 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) . ')';
}
private function parseArrayDimStore($array, $dim, $var): string
{
$id = $this->parseIdentifier($array);
return $id . '.offsetSet(' . $this->trimBrackets($dim) . ', ' . $this->trimBrackets($var) . ')';
}
private function parseBinaryOpShiftLeft($expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '<<');
}
private function parseBinaryOpShiftRight($expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '>>');
}
private function parseBinaryOpMod(mixed $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '%');
}
private function parseFuncCall(mixed $expr): string
{
$name = $this->parseIdentifier($expr->name);
if ($this->isNativeFunction($name)) {
return self::PREFIX . $name . '(' . $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;
}
}
if (empty($expr->args)) {
return 'php::call(' . $fn . ')';
} else {
return 'php::call(' . $fn . ', {' . $this->parseCallArgs($expr->args, $name) . '})';
}
}
private 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();';
} else if (!$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);
}
private function parseArg($arg): string
{
return $this->parseIdentifier($arg->value);
}
private function parsePostInc($expr): string
{
return $this->parseIdentifier($expr->var) . '++';
}
private function parsePostDec($expr): string
{
return $this->parseIdentifier($expr->var) . '--';
}
private function parseTernary(mixed $expr): string
{
$cond = $expr->cond;
$if = $expr->if;
$else = $expr->else;
return '(' . $this->parseExpr($cond) . ') ? (' . $this->parseExpr($if) . ') : (' . $this->parseExpr($else) . ')';
}
private function parseBinaryOpGreater(mixed $expr): string
{
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '>'));
}
private function parseBinaryOpPow(mixed $expr): string
{
$left = $this->parseIdentifier($expr->left);
$right = $this->parseIdentifier($expr->right);
return 'php::pow(' . $left . ', ' . $right . ')';
}
private function parsePreDec(mixed $expr): string
{
return '--' . $this->parseIdentifier($expr->var);
}
private function parseBinaryOpBitwiseAnd(mixed $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '&');
}
private function parseBinaryOpBitwiseOr(mixed $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '|');
}
private function parseBinaryOpBitwiseXor(mixed $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '^');
}
private function parseBitwiseNot(mixed $expr): string
{
$var = $this->parseIdentifier($expr->expr);
return '~' . $var;
}
private 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;
}
private function parseBinaryOpEqual(mixed $expr): string
{
return 'php::equals(' . $this->parseExpr($expr->left) . ', ' . $this->parseExpr($expr->right) . ')';
}
private function parseBinaryOpNotEqual(mixed $expr): string
{
return '!php::equals(' . $this->parseExpr($expr->left) . ', ' . $this->parseExpr($expr->right) . ')';
}
/**
* 逻辑比较的运算,必须返回 bool 类型
*/
private function parseBinaryOpLogicalAnd(Node $expr): string
{
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '&&'));
}
private function parseBinaryOpLogicalOr(Node $expr): string
{
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '||'));
}
private function parseBinaryOpLogicalXor(Node $expr): string
{
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '^'));
}
private function parseBooleanNot(Node $expr): string
{
$expr = $this->parseExpr($expr->expr);
return '!' . $expr;
}
private 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;
}
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;
}
private function trimBrackets(string $str): string
{
if ($this->isClosedCall($str, '')) {
return substr($str, 1, -1);
}
return $str;
}
private function convertIntExpr(string $expr): string
{
if (!$this->isClosedCall($expr, 'php::to_int')) {
return 'php::to_int(' . $this->trimBrackets($expr) . ')';
}
return $expr;
}
private 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);
}
private function convertStringExpr(string $expr): string
{
if (!$this->isClosedCall($expr, 'php::to_string')) {
return 'php::to_string(' . $this->trimBrackets($expr) . ')';
}
return $expr;
}
private function convertArrayExpr(string $expr): string
{
if (!$this->isClosedCall($expr, 'php::to_array')) {
return 'php::to_array(' . $this->trimBrackets($expr) . ')';
}
return $expr;
}
private function convertBoolExpr(string $expr): string
{
if (!$this->isClosedCall($expr, 'php::to_bool')) {
return 'php::to_bool(' . $this->trimBrackets($expr) . ')';
}
return $expr;
}
private function parseBinaryOpSmallerOrEqual(Node $expr): string
{
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '<='));
}
private function parseBinaryOpGreaterOrEqual(Node $expr): string
{
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '>='));
}
private function parsePrint(Node $expr): string
{
return 'php::echo(' . $this->parseExpr($expr->expr) . ')';
}
private function parseDo(Node $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;
}
private function parseBinaryOpIdentical(Node $expr): string
{
$left = $this->parseIdentifier($expr->left);
$right = $this->parseIdentifier($expr->right);
if ($right === 'nullptr') {
return $left . '.isNull()';
}
return 'php::same(' . $left . ', ' . $right . ')';
}
private function parseBinaryOpSpaceship(mixed $expr): string
{
$left = $this->parseIdentifier($expr->left);
$right = $this->parseIdentifier($expr->right);
return 'php::compare(' . $left . ', ' . $right . ')';
}
private function parseBinaryOpNotIdentical(mixed $expr): string
{
return '!(' . $this->parseBinaryOpIdentical($expr) . ')';
}
private function parseNew(Node $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) . ')';
}
}
private function parseClone(Node $expr): string
{
$var = $this->parseIdentifier($expr->expr);
return $var . '.clone()';
}
private function parseNamespaceDef(Node $node): string
{
$this->namespace = $this->parseIdentifier($node->name);
$code = '';
foreach($node->stmts as $v2) {
$type2 = $v2->getType();
switch ($type2) {
case 'Stmt_Class':
$code .= $this->parseClassDef($v2);
break;
case 'Stmt_Function':
$code .= $this->parseFunctionDef($v2) . PHP_EOL;
break;
case 'Stmt_Use':
foreach ($v2->uses as $use) {
$this->uses[] = $use->name->toString();
}
break;
default:
abort($v2);
}
}
$this->namespace = '';
$this->uses = [];
return $code;
}
private function parseClassDef(Node $v): string
{
$this->class = $this->parseIdentifier($v->name);
$code = '';
foreach($v->stmts as $v) {
$type = $v->getType();
switch ($type) {
case 'Stmt_ClassConst':
case 'Stmt_Property':
break;
case 'Stmt_ClassMethod':
$code .= $this->parseFunctionDef($v) . PHP_EOL;
break;
default:
abort($v);
}
}
$this->class = '';
return $code;
}
private function parseCastInt(Node $node): string
{
return $this->convertIntExpr($this->parseExpr($node->expr));
}
private function parseConstFetch(Node $expr): string
{
$name = $this->parseIdentifier($expr->name);
if ($name === 'null') {
return 'nullptr';
} elseif ($name === 'true') {
return 'true';
} elseif ($name === 'false') {
return 'false';
}
return 'php::constant("' . $name . '")';
}
private function parseUnaryMinus(Node $expr): string
{
$code = $this->parseExpr($expr->expr);
return '-' . $code;
}
private function parseUnaryPlus(mixed $expr)
{
return $this->parseExpr($expr->expr);
}
private function parseBinaryOpDiv(Node $expr): string
{
$left = $this->parseIdentifier($expr->left);
$right = $this->parseIdentifier($expr->right);
return $left . ' / (' . $right . ')';
}
private function parseBinaryOpMinus(Node $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '-');
}
private function parseInterpolatedString(Node $expr): string
{
$parts = $expr->parts;
$list = [];
foreach ($parts as $part) {
$list[] = $this->parseExpr($part);
}
return 'php::concat({' . implode(', ', $list) . '})';
}
private function escapeString($str): string
{
return addcslashes($str, "\\\"\n\r\t\v\f\0\x01..\x1f\x7f..\xff");
}
private function escapeVarName(string $name): string
{
if (in_array($name, $this->reservedNames)) {
return '_php__var__' . $name;
} else {
return $name;
}
}
private function unescapeVarName(string $name): string
{
return str_replace('_php__var__', '', $name);
}
private function parseInterpolatedStringPart(Node $expr): string
{
return '"' . $this->escapeString($expr->value) . '"';
}
private 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 '';
}
private function getArgInfo(string $funcName, int $index): ArgInfo
{
$funcDef = $this->nativeFunctions[$funcName];
return $funcDef->argInfoList[$index];
}
private function getReturnType(): string
{
return $this->functionDef->returnType;
}
private function getTypeConvertedArg($arg, $argInfo): string
{
$expr = $this->parseArg($arg);
$type = $this->detectExprType($arg->value);
return $this->convertExprType($expr, $argInfo->type, $type);
}
private 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;
}
private function parseExit(Node $node): string
{
return 'php::exit(' . $this->parseIdentifier($node->expr) . ')';
}
private 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);
}
private function parsePropertyFetch(Node $expr): string
{
return $this->convertToObject($expr->var) . '.getProperty("' . $this->parseIdentifier($expr->name) . '")';
}
private function parseAssignOpShiftRight(Node $node): string
{
$var = $this->parseIdentifier($node->var);
return $var . ' >>= ' . $this->parseIdentifier($node->expr);
}
private function parseMagicConstFile(mixed $expr): string
{
return '"' . $this->escapeString($this->file) . '"';
}
private function parseForeach(Node $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) {
// foreach 的 key/value 不能与全局变量同名
if ($this->hasGlobalVar($keyVar)) {
$this->fatalError($node->keyVar, 'Cannot redefine key variable: ' . $this->unescapeVarName($keyVar));
}
$code .= $this->getIndent() . self::TYPE_VAR . ' ' . $keyVar . ' = iter.key();' . PHP_EOL;
if (!$this->hasVar($keyVar)) {
$this->addLocalVar($keyVar, self::TYPE_VAR);
}
}
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 {
// foreach 的 key/value 不能与全局变量同名
if ($this->hasGlobalVar($valueVar)) {
$this->fatalError($node->valueVar, 'Cannot redefine value variable: ' . $this->unescapeVarName($valueVar));
}
$code .= $this->getIndent() . self::TYPE_VAR . ' ' . $valueVar . ' = iter.value();' . PHP_EOL;
if (!$this->hasVar($valueVar)) {
$this->addLocalVar($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 genTmpVarName(): string
{
return 'tmp_var_' . $this->tmpVarIndex++;
}
private function detectConstType($expr): string
{
$name = $this->parseIdentifier($expr->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;
}
private 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;
}
private 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);
}
private function parseEval(mixed $expr): string
{
return 'php::eval(' . $this->parseIdentifier($expr->expr) . ')';
}
private function parseInclude(mixed $expr): string
{
return 'php::include(' . $this->parseIdentifier($expr->expr) . ')';
}
private function parseMagicConstDir(mixed $expr): string
{
return '"' . $this->escapeString($this->dir) . '"';
}
private function parseBreak(mixed $v): string
{
$num = $v->num;
if ($num) {
$value = $this->parseIdentifier($num);
if ($value > 1) {
abort($v);
}
}
return 'break;';
}
private 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;
} else if (floor($value) == $value && abs($value) < 1e15) {
return number_format($value, 1, '.', '');
} else {
return sprintf('%.' . $this->floatPrecision . 'g', $value);
}
}
private 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);
}
}
}
private function parseCastArray(mixed $expr): string
{
return $this->convertArrayExpr($this->parseIdentifier($expr->expr));
}
private function hasGlobalVar($name): bool
{
return array_key_exists($name, $this->globalVars);
}
private function genGlobalVars(): void
{
$file = 'global_vars.cc';
$code = $this->genIncludeHeaderFiles();
$lines = [];
// 全局变量只能是 var 类型
foreach ($this->globalVars as $name => $type) {
$lines[] = self::TYPE_VAR . ' ' . $name . ';';
}
$code .= implode(PHP_EOL, $lines) . PHP_EOL;
$code .= PHP_EOL;
$literalStringsCount = count($this->literalStrings);
$code .= 'php::Var ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '] = {' . PHP_EOL;
$this->indentLevel++;
foreach ($this->literalStrings as $str => $index) {
$code .= $this->getIndent() . 'php::String{ZEND_STRL("' . $this->escapeString($str) . '"), true},' . PHP_EOL;
}
$this->indentLevel--;
$code .= '};' . PHP_EOL;
$code .= PHP_EOL;
$code .= 'void ' . self::PREFIX . 'unset_all_global_vars() {' . PHP_EOL;
$lines = [];
$this->indentLevel++;
foreach ($this->globalVars as $name => $type) {
$lines[] = $this->getIndent() . $name . '.unset();';
}
$this->indentLevel--;
$code .= implode(PHP_EOL, $lines) . PHP_EOL;
$code .= '}' . PHP_EOL;
file_put_contents($file, $code);
}
private function parseCastDouble(mixed $expr): string
{
return 'php::to_float(' . $this->parseIdentifier($expr->expr) . ')';
}
private function detectFuncCallReturnType($expr): string
{
$name = $expr->name;
$returnType = Reflection::getFunctionReturnType($name);
if ($returnType) {
return $this->getTypeFromZendType($returnType);
} else {
return self::TYPE_VAR;
}
}
private 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;
}
private 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;
}
public function genFunctionDeclaration(string $file): void
{
$code = '';
/**
* @var FunctionDef $func
*/
foreach ($this->nativeFunctions as $name => $func) {
$code .= 'extern ' . $func->returnType . ' ' . self::PREFIX . $name . '(';
$argInfoList = $func->argInfoList;
if ($argInfoList) {
$list = [];
foreach ($argInfoList as $argInfo) {
$arg = $argInfo->type . ' ' . $argInfo->name;
if ($argInfo->default) {
$arg .= ' = ' . $argInfo->default;
}
$list[] = $arg;
}
$code .= implode(', ', $list);
}
$code .= ');' . PHP_EOL;
}
file_put_contents($file, $code);
}
private function parseAssignRef(mixed $expr)
{
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);
}
}
}
private 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) . ')';
}
}
private function parseStaticCall(mixed $expr): string
{
$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) . '})';
}
}
private function parseMagicConstLine(Node $expr): int
{
return $expr->getStartLine();
}
private function parseThrow(mixed $expr): string
{
return 'php::throwException(' . $this->parseIdentifier($expr->expr). ')';
}
private 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;
}
private 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 (';
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;
}
}