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.
 
 

1539 lines
49 KiB

<?php
namespace PhpAot\Php;
use PhpAot\Php\Visitor;
use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Error;
use PhpParser\NodeTraverser;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter;
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 VALUE_NAN = 'std::numeric_limits<double>::quiet_NaN()';
const string VALUE_INF = 'std::numeric_limits<double>::infinity()';
private string $phpxDir = '~/workspace/projects/phpx';
protected string $lang = 'PHP';
private array $scope = [];
/**
* 插入到语句之前
* @var array
*/
private array $beforeStmtLines = [];
private int $tmpVarIndex = 0;
// 需要插入到函数头部的变量定义代码
private array $definitions = [];
private array $zendTypeMap = [
'int' => self::TYPE_INT,
'float' => self::TYPE_FLOAT,
'bool' => self::TYPE_BOOL,
];
private array $headers = [
'phpx.h',
'phpx_func.h'
];
private array $internalFunctions = [];
private int $optimizeLevel = 5;
private bool $verbose = false;
private string $file;
private string $dir;
private string $namespace = '';
private array $uses = [];
private string $class = '';
private FunctionDef $functionDef;
const string PREFIX = 'php_';
private string $rootPath;
private int $debugLine = 0;
// 回归赋值
private bool $regressionDetection = false;
public function __construct(string $rootPath)
{
$this->rootPath = $rootPath;
}
public function parseHeaders(): string
{
$lines = [];
foreach ($this->headers as $header) {
$lines[] = '#include <' . $header . '>';
}
return implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL;
}
public function setPhpxDir($dir): void
{
$this->phpxDir = $dir;
}
private function doConvert(string $phpCode)
{
$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 = $this->parseHeaders();
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);
}
}
return $cppCode;
}
public function convert(string $file): string
{
$phpCode = file_get_contents($file);
$this->file = realpath($file);
$this->dir = dirname($this->file);
while (true) {
try {
return $this->doConvert($phpCode);
} catch (ReturnTypeChanged $e) {
// 某些情况,例如返回值变更,需要重新解析
continue;
}
}
}
public function save(string $code, string $file): void
{
file_put_contents($file, $code);
$this->formatCppCode($file);
}
public function getLine($node): int
{
return $node->getLine();
}
public function getType($node): string
{
return $node->getType();
}
private function getDetectedType($name)
{
return $this->scope[$name] ?? 'php::Variant';
}
public function getZendType(string $type): string
{
return $this->zendTypeMap[$type] ?? 'zval *';
}
public function isInternalFunction(string $name): bool
{
return isset($this->internalFunctions[$name]);
}
private function resetScope(): void
{
$this->scope = [];
$this->definitions = [];
$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->internalFunctions[$name])) {
$this->functionDef = $this->internalFunctions[$name];
} else {
$this->functionDef = new FunctionDef();
$this->functionDef->name = $name;
if ($v->returnType) {
$this->functionDef->returnType = $this->getZendType($this->parseIdentifier($v->returnType));
} else {
$this->functionDef->returnType = 'void';
}
$this->internalFunctions[$name] = $this->functionDef;
}
$params = $this->parseParams($v->params);
$this->indentLevel++;
$stmts = $this->parseStmts($v->stmts);
$this->indentLevel--;
$code = $this->getReturnType() . ' ' . self::PREFIX . $name . '(' . $params . ') {' . PHP_EOL;
$this->indentLevel++;
foreach ($this->definitions as $definition) {
$code .= $this->getIndent() . $definition . PHP_EOL;
}
$this->indentLevel--;
$code .= $stmts;
$code .= "}\n";
return $code;
}
private function writeLog($msg)
{
if ($this->verbose) {
echo $msg . PHP_EOL;
}
}
private function parseScalarValue($expr): string
{
return $expr->getAttribute('rawValue');
}
private function parseIdentifier(Node $expr)
{
$type = $expr->getType();
switch ($type) {
case 'Name':
case 'Identifier':
case 'Expr_Variable':
return $expr->name;
case 'Scalar_Int':
return $expr->value . 'L';
case 'Scalar_Float':
return $this->parseScalarFloat($expr);
case 'Scalar_String':
return '"' . $this->escapeString($expr->value) . '"';
case 'Expr_ConstFetch':
return $this->parseConstFetch($expr);
case 'Expr_Assign':
if ($this->regressionDetection) {
$var = $this->parseIdentifier($expr->var);
$this->beforeStmtLines[] = $this->parseExpr($expr) . ";\n";
return '(' . $var . ')';
}
return $this->parseExpr($expr);
default:
return $this->parseExpr($expr);
}
}
private function parseParams($params): string
{
$list = [];
$this->functionDef->argumentCountRequired = count($params);
foreach ($params as $param) {
$type = $this->parseType($param->type);
$name = $this->parseIdentifier($param->var);
$list[] = $type . ' ' . $name;
$this->scope[$name] = $type;
$argInfo = new ArgInfo();
$argInfo->name = $name;
$argInfo->type = $type;
if (isset($param->default)) {
$this->functionDef->argumentCountRequired = count($list) - 1;
$argInfo->default = $param->default->getAttribute('rawValue');
}
$this->functionDef->arguments[] = $argInfo;
}
return implode(', ', $list);
}
private function parseStmts(array $stmts): string
{
$lines = [];
$scope = $this->scope;
foreach ($stmts as $v) {
$class = $v->getType();
$this->beforeStmtLines = [];
$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;
default:
abort($v);
}
if ($this->beforeStmtLines) {
$lines = array_merge($lines, $this->beforeStmtLines);
}
$lines[] = $result;
}
// 恢复作用域,清除表达式解析中产生的局部变量
$this->scope = $scope;
$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_Assign':
return $this->parseAssign($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_ShiftRight':
return $this->parseAssignOpShiftRight($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 'Expr_ArrayDimFetch':
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_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 'Name_FullyQualified':
return $expr->name;
case 'Scalar_Int':
case 'Scalar_Float':
case 'Scalar_String':
case 'Expr_Variable':
return $this->parseIdentifier($expr);
case 'Scalar_MagicConst_File':
return $this->parseMagicConstFile($expr);
case 'Scalar_MagicConst_Dir':
return $this->parseMagicConstDir($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_Exit':
return $this->parseExit($expr);
default:
abort($expr);
}
}
private function parseAssign(Node $v): string
{
$left = $v->var;
$right = $v->expr;
if ($left->getType() === 'Expr_ArrayDimFetch') {
$array = $this->parseIdentifier($left->var);
$code = '';
// 这是 PHP 的初始化+赋值写法,需要先创建数组
if (!$this->hasVar($array)) {
$this->addVar($array, self::TYPE_VAR);
$code .= self::TYPE_ARRAY . " $array;\n" . $this->getIndent();
}
if ($left->dim === null) {
return $code . "$array.offsetSet(php::null, " . $this->parseExpr($right) . ")";
} else {
$dim = $this->parseIdentifier($left->dim);
return $code . "$array.offsetSet($dim, " . $this->parseExpr($right) . ")";
}
} elseif ($left->getType() === 'Expr_PropertyFetch') {
$array = $this->parseIdentifier($left->var);
$propName = $this->parseIdentifier($left->name);
return "$array.setProperty(\"$propName\", " . $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->addVar($var, $type);
// 右值变量可能是常量获取,虽然可以推断为数值类型,但实际返回依然为 Var ,需要强制转换
return $type . ' ' . $var . ' = ' . $this->convertExprType($expr, $type, self::TYPE_VAR);
} else {
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 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, self::TYPE_FLOAT, $leftType);
} elseif ($leftType === self::TYPE_INT) {
$rightExpr = $this->convertExprType($rightExpr, self::TYPE_INT, $rightType);
} elseif ($rightType === self::TYPE_INT) {
$leftExpr = $this->convertExprType($leftExpr, self::TYPE_INT, $leftType);
}
return $leftExpr . ' ' . $op . ' ' . $rightExpr;
}
private function parseBinaryOpPlus(mixed $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '+');
}
private function parseReturn(mixed $v): string
{
// 实际函数的返回值
$type = $this->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 addVar(string $name, string $type): void
{
$this->scope[$name] = $type;
}
private function hasVar(string $name)
{
return isset($this->scope[$name]);
}
private function resetReturnType(string $type): void
{
$this->functionDef->returnType = $type;
throw new ReturnTypeChanged;
}
private function detectVarType($var): string
{
$name = $this->parseIdentifier($var);
if ($this->hasVar($name)) {
return $this->scope[$name];
}
return self::TYPE_VAR;
}
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 'Scalar_Float':
return self::TYPE_FLOAT;
case 'Scalar_Bool':
return self::TYPE_BOOL;
case 'Expr_Array':
return 'php::Array';
case 'Expr_BinaryOp_Plus':
case 'Expr_BinaryOp_Minus':
$leftType = $this->detectExprType($expr->left);
$rightType = $this->detectExprType($expr->right);
if ($leftType === self::TYPE_INT || $rightType === self::TYPE_INT) {
return self::TYPE_INT;
}
break;
case 'Expr_FuncCall':
$name = $this->parseIdentifier($expr->name);
if ($this->isInternalFunction($name)) {
return $this->internalFunctions[$name]->returnType;
}
break;
case 'Expr_New':
return self::TYPE_OBJECT;
case 'Expr_Assign':
$var = $this->parseIdentifier($expr->var);
if ($this->hasVar($var)) {
return $this->scope[$var];
}
break;
case '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 .'{}';
}
$list = [];
$this->indentLevel++;
foreach ($items as $item) {
if ($item->key) {
$list[] = $this->getIndent() . '{ ' . $this->parseIdentifier($item->key) . ', ' .
self::TYPE_VAR . '(' . $this->parseIdentifier($item->value) . ') }';
} else {
$value = $this->parseIdentifier($item->value);
$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;
}
$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;
}
public function compileFile($file): void
{
$cmd = 'g++ -c ' . $file . ' -o ' . $file . '.o ' . $this->parseIncludes() . ' -O' . $this->optimizeLevel;
echo $cmd . PHP_EOL;
shell_exec($cmd);
}
public function compileBinary($targetFile, $objectFile): void
{
$cmd = 'g++ main.cc ' . $objectFile . ' -o ' . $targetFile . ' ' . $this->parseIncludes() . $this->parseLdflags() . $this->parseLibs();
$cmd .= ' -O' . $this->optimizeLevel;
echo $cmd . PHP_EOL;
shell_exec($cmd);
}
private function parseBinaryOpConcat(mixed $expr): string
{
$this->regressionDetection = true;
$left = $this->parseIdentifier($expr->left);
$right = $this->parseIdentifier($expr->right);
$this->regressionDetection = false;
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);
$code .= 'for (;';
$list_cond = [];
foreach ($cond as $expr) {
$list_cond[] = $this->parseExpr($expr);
}
$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->parseBinaryOp($expr->left, $expr->right, '<');
}
private function parsePreInc(mixed $expr): string
{
return '++' . $this->parseIdentifier($expr->var);
}
private function parseAssignOpPlus(mixed $expr): string
{
$var = $this->parseIdentifier($expr->var);
return $var . ' += (' . $this->parseIdentifier($expr->expr) . ')';
}
private function parseArrayDimFetch($node): string
{
$var = $this->parseIdentifier($node->var);
$dim = $this->parseIdentifier($node->dim);
return $var . '.offsetGet(' . $dim . ')';
}
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 parseAssignOpMinus(mixed $expr): string
{
$var = $this->parseIdentifier($expr->var);
return $var . ' -= ' . $this->parseIdentifier($expr->expr);
}
private function parseAssignOpMul(mixed $expr): string
{
$var = $this->parseIdentifier($expr->var);
return $var . ' *= ' . $this->parseIdentifier($expr->expr);
}
private function parseAssignOpDiv(mixed $expr): string
{
$var = $this->parseIdentifier($expr->var);
return $var . ' /= ' . $this->parseIdentifier($expr->expr);
}
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->isInternalFunction($name)) {
return self::PREFIX . $name . '(' . $this->parseCallArgs($expr->args, $name) . ')';
}
if (empty($expr->args)) {
return 'php::call("' . $name . '")';
} else {
return 'php::call("' . $name . '", {' . $this->parseCallArgs($expr->args, $name) . '})';
}
}
private function parseCallArgs($args, string $funcName): string
{
$internalFunction = $this->isInternalFunction($funcName);
$list_args = [];
foreach ($args as $i => $arg) {
if ($internalFunction) {
$argInfo = $this->getArgInfo($funcName, $i);
$list_args[] = $this->getTypeConvertedArg($arg, $argInfo);
} else {
$list_args[] = $this->parseArg($arg);
}
// 调用了不存在的变量,可能是引用
if ($arg->value->getType() === 'Expr_Variable') {
$name = $this->parseIdentifier($arg->value);
if (!$this->hasVar($name)) {
$this->definitions[] = self::TYPE_VAR . ' ' . $name . ' = php::newReference();';
}
}
}
return implode(', ', $list_args);
}
private function parseArg($arg)
{
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->parseBinaryOp($expr->left, $expr->right, '>');
}
private function parseAssignOpMod(mixed $expr): string
{
$var = $this->parseIdentifier($expr->var);
return $var . ' % ' . $this->parseIdentifier($expr->expr);
}
private function parseBinaryOpPow(mixed $expr): string
{
$left = $this->parseIdentifier($expr->left);
$right = $this->parseIdentifier($expr->right);
return '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 $this->parseBinaryOp($expr->left, $expr->right, '==');
}
private function parseBinaryOpNotEqual(mixed $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '!=');
}
private function parseBinaryOpLogicalAnd(Node $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '&&');
}
private function parseBinaryOpLogicalOr(Node $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '||');
}
private function parseBinaryOpLogicalXor(Node $expr): string
{
return $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;
}
private function convertIntExpr(string $expr): string
{
return 'php::to_int(' . $expr . ')';
}
private function convertFloatExpr(string $expr): string
{
return 'php::to_float(' . $expr . ')';
}
private function convertBoolExpr(string $expr): string
{
return 'php::to_bool(' . $expr . ')';
}
private function parseBinaryOpSmallerOrEqual(Node $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '<=');
}
private function parseBinaryOpGreaterOrEqual(Node $expr): string
{
return $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
{
$code = $this->parseExpr($node->expr);
return '(' . $code . ').toInt()';
}
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 parseInterpolatedStringPart(Node $expr): string
{
return '"' . $this->escapeString($expr->value) . '"';
}
private function parseGlobal(Node $v): string
{
$out = [];
foreach ($v->vars as $v) {
$out[] = self::TYPE_VAR . ' ' . $v->name . ' = php::global("' . $v->name . '");';
$this->addVar($v->name, self::TYPE_VAR);
}
return implode(PHP_EOL . $this->getIndent(), $out);
}
private function getArgInfo(string $funcName, int $index): ArgInfo
{
$funcDef = $this->internalFunctions[$funcName];
return $funcDef->arguments[$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 && $rightType !== self::TYPE_FLOAT) {
return $this->convertFloatExpr($expr);
}
if ($leftType === self::TYPE_INT && $rightType !== self::TYPE_INT) {
return $this->convertIntExpr($expr);
}
if ($leftType === self::TYPE_BOOL && $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 === 'Expr_ArrayDimFetch') {
$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 === '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->parseIdentifier($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)
{
$expr = $this->parseIdentifier($node->expr);
if ($node->keyVar) {
$keyVar = $this->parseIdentifier($node->keyVar);
}
$valueVar = $this->parseIdentifier($node->valueVar);
$iteratorVar = $this->addTmpVar();
$stmts = $node->stmts;
$code = '';
$code .= self::TYPE_ARRAY . " $iteratorVar = " . $expr . ';' . PHP_EOL;
$code .= 'for (auto iter = ' . $iteratorVar . '.begin(); iter != ' . $iteratorVar . '.end(); ++iter) {' . PHP_EOL;
$this->indentLevel++;
if ($node->keyVar) {
$code .= $this->getIndent() . self::TYPE_VAR . ' ' . $keyVar . ' = iter.key();' . PHP_EOL;
$this->addVar($keyVar, self::TYPE_VAR);
}
if ($node->valueVar->getType() == 'Expr_ArrayDimFetch') {
$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 .= $this->getIndent() . self::TYPE_VAR . ' ' . $valueVar . ' = iter.value();' . PHP_EOL;
$this->addVar($valueVar, self::TYPE_VAR);
}
$code .= $this->parseStmts($stmts);
$this->indentLevel--;
$code .= $this->getIndent() . '}';
return $code;
}
private function formatCppCode(string $file): void
{
$cmd = 'cd ' . $this->rootPath . ' && clang-format -i ' . $file;
$this->writeLog('formatting ' . $file . '...');
$this->writeLog($cmd);
shell_exec($cmd);
}
private function addTmpVar(): string
{
return 'tmp_var_' . $this->tmpVarIndex++;
}
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->addTmpVar();
$type = $this->detectExprType($cond);
$var_def = $type . ' ' . $tmp_var . ' = ' . $this->parseExpr($cond) . ';' . PHP_EOL;
// 保存作用域,switch 可能会解析失败,在这个过程中会增加变量,需重置
$scope = $this->scope;
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->scope = $scope;
goto _fail;
}
$code .= $this->getIndent() . 'case ' . $this->parseScalarValue($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) {
$type = $this->detectExprType($var->default);
$list[] = 'static ' . $type . ' ' . $this->parseIdentifier($var->var) . ' = ' . $this->parseIdentifier($var->default) . ';';
}
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
{
if (is_nan($expr->value)) {
return self::VALUE_NAN;
} elseif (is_infinite($expr->value)) {
return $expr->value > 0 ? self::VALUE_INF : '-' . self::VALUE_INF;
} else {
return number_format($expr->value, 1, '.', '');
}
}
}