parent
95c8a8e03d
commit
5fb2fc1ac5
10 changed files with 468 additions and 51 deletions
@ -1,3 +1,4 @@ |
||||
/.idea |
||||
/logs |
||||
/vendor |
||||
/tmp |
||||
|
||||
@ -0,0 +1,37 @@ |
||||
<?php |
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php'; |
||||
|
||||
use PhpAot\Php\Translator; |
||||
use PhpParser\Error; |
||||
use PhpParser\NodeTraverser; |
||||
use PhpParser\ParserFactory; |
||||
use PhpParser\PrettyPrinter; |
||||
|
||||
define('DEBUG', true); |
||||
|
||||
$traverser = new NodeTraverser; |
||||
$prettyPrinter = new PrettyPrinter\Standard; |
||||
|
||||
$traverser->addVisitor(new \PhpAot\Php\Visitor()); |
||||
|
||||
if (empty($argv[1])) { |
||||
die("php compiler.php [file]\n"); |
||||
} |
||||
|
||||
$code = file_get_contents($argv[1]); |
||||
|
||||
$parser = (new ParserFactory())->createForNewestSupportedVersion(); |
||||
try { |
||||
$ast = $parser->parse($code); |
||||
$stmts = $traverser->traverse($ast); |
||||
$translator = new Translator($stmts); |
||||
$translator->setIndent(' '); |
||||
$code = $translator->convert(); |
||||
$translator->save($code, './tmp/hello.cc'); |
||||
$translator->compileFile('./tmp/hello.cc'); |
||||
} catch (Error $error) { |
||||
echo "Parse error: {$error->getMessage()}\n"; |
||||
return; |
||||
} |
||||
|
||||
@ -0,0 +1,17 @@ |
||||
<?php |
||||
|
||||
function main(int $argc, array $argv): int |
||||
{ |
||||
$a = 1; |
||||
$b = 2; |
||||
$c = 3.12343; |
||||
$d = 'hello'; |
||||
$e = ['hello' => 1, 'world' => 33.43]; |
||||
|
||||
$argc = 999; |
||||
$a = $b * 13; |
||||
|
||||
echo "value:=" . ($a + $b); |
||||
|
||||
return $a * $b; |
||||
} |
||||
@ -0,0 +1,36 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Core; |
||||
|
||||
abstract class Translator |
||||
{ |
||||
protected int $indentLevel = 0; |
||||
protected string $indentStr = "\t"; |
||||
public string $mode = 'cli'; |
||||
protected string $lang; |
||||
|
||||
function setMode($mode): void |
||||
{ |
||||
$this->mode = $mode; |
||||
} |
||||
|
||||
function setIndent(string $indent): void |
||||
{ |
||||
$this->indentStr = $indent; |
||||
} |
||||
|
||||
public function getLang(): string |
||||
{ |
||||
return $this->lang; |
||||
} |
||||
|
||||
abstract public function getLine($node): int; |
||||
|
||||
abstract public function getType($node): string; |
||||
|
||||
protected function getIndent(): string |
||||
{ |
||||
return str_repeat($this->indentStr, $this->indentLevel); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,314 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php; |
||||
|
||||
use PhpParser\Node; |
||||
use PhpParser\Node\Expr\Variable; |
||||
use PhpParser\Node\Identifier; |
||||
|
||||
class Translator extends \PhpAot\Core\Translator |
||||
{ |
||||
protected array $stmts; |
||||
protected string $phpxDir = '~/workspace/phpx'; |
||||
protected string $lang = 'PHP'; |
||||
protected array $typeMap = []; |
||||
protected array $headers = [ |
||||
'phpx.h', |
||||
]; |
||||
|
||||
function __construct(array $stmts) |
||||
{ |
||||
$this->stmts = $stmts; |
||||
} |
||||
|
||||
function parseHeaders(): string |
||||
{ |
||||
$lines = []; |
||||
foreach ($this->headers as $header) { |
||||
$lines[] = '#include <' . $header . '>'; |
||||
} |
||||
return implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL; |
||||
} |
||||
|
||||
function setPhpxDir($dir): void |
||||
{ |
||||
$this->phpxDir = $dir; |
||||
} |
||||
|
||||
function convert() |
||||
{ |
||||
$code = ''; |
||||
$code .= $this->parseHeaders(); |
||||
$code .= $this->parseStmts($this->stmts); |
||||
|
||||
return $code; |
||||
} |
||||
|
||||
function save($code, $file) |
||||
{ |
||||
file_put_contents($file, $code); |
||||
} |
||||
|
||||
|
||||
function getLine($node): int |
||||
{ |
||||
return $node->getLine(); |
||||
} |
||||
|
||||
function getType($node): string |
||||
{ |
||||
return $node->getType(); |
||||
} |
||||
|
||||
private function parseFunctionDef($v) |
||||
{ |
||||
$name = $this->parseIdentifier($v->name); |
||||
$return = $this->parseIdentifier($v->returnType); |
||||
$params = $this->parseParams($v->params); |
||||
$code = $return . ' ' . $name . '(' . $params . ') {' . PHP_EOL; |
||||
$this->indentLevel++; |
||||
$stmts = $this->parseStmts($v->stmts); |
||||
$this->indentLevel--; |
||||
$code .= $stmts; |
||||
$code .= "}"; |
||||
|
||||
return $code; |
||||
} |
||||
|
||||
protected function parseIdentifier($node) |
||||
{ |
||||
$type = $node->getType(); |
||||
switch ($type) { |
||||
case 'Identifier': |
||||
case 'Expr_Variable': |
||||
return $node->name; |
||||
case 'Scalar_Int': |
||||
case 'Scalar_Float': |
||||
return $node->value; |
||||
case 'Scalar_String': |
||||
return '"' . $node->value . '"'; |
||||
case 'Expr_Array': |
||||
return $this->parseArray($node); |
||||
case 'Expr_BinaryOp_Mul': |
||||
return '(' . $this->parseBinaryOpMul($node) . ')'; |
||||
case 'Expr_BinaryOp_Concat': |
||||
return '(' . $this->parseBinaryOpConcat($node) . ')'; |
||||
case 'Expr_BinaryOp_Plus': |
||||
return '(' . $this->parseBinaryOpPlus($node) . ')'; |
||||
default: |
||||
debug($node); |
||||
} |
||||
} |
||||
|
||||
private function parseParams($params) |
||||
{ |
||||
$list = []; |
||||
foreach ($params as $param) { |
||||
$type = $this->parseType($param->type); |
||||
$name = $this->parseIdentifier($param->var); |
||||
$list[] = $type . ' ' . $name; |
||||
$this->typeMap[$name] = $type; |
||||
} |
||||
return implode(', ', $list); |
||||
} |
||||
|
||||
private function parseStmts(array $stmts) |
||||
{ |
||||
$lines = []; |
||||
foreach ($stmts as $v) { |
||||
$class = $v->getType(); |
||||
switch ($class) { |
||||
case 'Stmt_Function': |
||||
$lines[] = $this->parseFunctionDef($v); |
||||
break; |
||||
case 'Stmt_Expression': |
||||
$lines[] = $this->parseExpr($v->expr) . ';'; |
||||
break; |
||||
case 'Stmt_Echo': |
||||
$lines[] = $this->parseEcho($v) . ';'; |
||||
break; |
||||
case 'Stmt_Return': |
||||
$lines[] = $this->parseReturn($v) . ';'; |
||||
break; |
||||
default: |
||||
debug($v); |
||||
} |
||||
} |
||||
$code = ''; |
||||
foreach ($lines as $line) { |
||||
$code .= $this->getIndent() . $line . PHP_EOL; |
||||
} |
||||
return $code; |
||||
} |
||||
|
||||
private function parseExpr(mixed $expr) |
||||
{ |
||||
$type = $expr->getType(); |
||||
switch ($type) { |
||||
case 'Expr_Assign': |
||||
return $this->parseAssign($expr); |
||||
case 'Expr_BinaryOp_Plus': |
||||
return $this->parseBinaryOpPlus($expr); |
||||
case 'Expr_BinaryOp_Mul': |
||||
return $this->parseBinaryOpMul($expr); |
||||
case 'Expr_BinaryOp_Concat': |
||||
return $this->parseBinaryOpConcat($expr); |
||||
default: |
||||
debug($expr); |
||||
} |
||||
} |
||||
|
||||
private function parseAssign(mixed $v) |
||||
{ |
||||
$var = $this->parseIdentifier($v->var); |
||||
$expr = $this->parseIdentifier($v->expr); |
||||
|
||||
if (!isset($this->typeMap[$var])) { |
||||
$type = $this->detectType($v->var, $v->expr); |
||||
$this->typeMap[$var] = $type; |
||||
return $type . ' ' . $var . ' = ' . $expr; |
||||
} else { |
||||
return $var . ' = ' . $expr; |
||||
} |
||||
} |
||||
|
||||
private function parseEcho(mixed $v) |
||||
{ |
||||
return 'php::echo(' . $this->parseExprs($v->exprs) . ')'; |
||||
} |
||||
|
||||
private function parseExprs($exprs) |
||||
{ |
||||
$code = ''; |
||||
foreach ($exprs as $expr) { |
||||
$code .= $this->parseExpr($expr); |
||||
} |
||||
return $code; |
||||
} |
||||
|
||||
private function parseBinaryOpPlus(mixed $expr) |
||||
{ |
||||
$left = $this->parseIdentifier($expr->left); |
||||
$right = $this->parseIdentifier($expr->right); |
||||
|
||||
return $left . ' + ' . $right; |
||||
} |
||||
|
||||
private function parseReturn(mixed $v) |
||||
{ |
||||
return 'return ' . $this->parseExpr($v->expr); |
||||
} |
||||
|
||||
private function parseBinaryOpMul(mixed $expr) |
||||
{ |
||||
$left = $this->parseIdentifier($expr->left); |
||||
$right = $this->parseIdentifier($expr->right); |
||||
|
||||
return $left . ' * ' . $right; |
||||
} |
||||
|
||||
private function detectType($var, $expr) |
||||
{ |
||||
$exprType = $expr->getType(); |
||||
switch ($exprType) { |
||||
case 'Scalar_Int': |
||||
return 'zend_long'; |
||||
case 'Scalar_Float': |
||||
return 'double'; |
||||
case 'Scalar_String': |
||||
return 'php::Variant'; |
||||
case 'Expr_Array': |
||||
return 'php::Array'; |
||||
default: |
||||
debug($expr); |
||||
} |
||||
} |
||||
|
||||
private function parseArray($node) |
||||
{ |
||||
$items = $node->items; |
||||
$list = []; |
||||
$this->indentLevel++; |
||||
foreach ($items as $item) { |
||||
if ($item->key) { |
||||
$list[] = $this->getIndent() . '{ php::Variant(' . $this->parseIdentifier($item->key) . '), php::Variant(' . $this->parseIdentifier($item->value) . ') }'; |
||||
} else { |
||||
$list[] = $this->getIndent() . 'php::Variant(' . $this->parseIdentifier($item->value) . ')'; |
||||
} |
||||
} |
||||
$this->indentLevel--; |
||||
return '{' . PHP_EOL . |
||||
implode(', ' . PHP_EOL, $list) . PHP_EOL . |
||||
$this->getIndent() . |
||||
'}'; |
||||
} |
||||
|
||||
private function parseType($type) |
||||
{ |
||||
$name = $type->name; |
||||
switch ($name) { |
||||
case 'int': |
||||
return 'zend_long'; |
||||
case 'array': |
||||
return 'php::Array'; |
||||
case 'float': |
||||
return 'double'; |
||||
default: |
||||
debug($type); |
||||
} |
||||
} |
||||
|
||||
private function parseIncludes() |
||||
{ |
||||
$list = [ |
||||
$this->phpxDir . '/include', |
||||
]; |
||||
$out = '$(php-config --includes) '; |
||||
foreach ($list as $li) { |
||||
$out .= '-I ' . $li . ' '; |
||||
} |
||||
return $out; |
||||
} |
||||
|
||||
private function parseLdflags() |
||||
{ |
||||
$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) |
||||
{ |
||||
$cmd = 'g++ -c ' . $file . ' -o ' . $file . '.o ' . $this->parseIncludes() . $this->parseLdflags() . $this->parseLibs(); |
||||
echo $cmd . PHP_EOL; |
||||
shell_exec($cmd); |
||||
} |
||||
|
||||
private function parseBinaryOpConcat(mixed $expr) |
||||
{ |
||||
$left = $this->parseIdentifier($expr->left); |
||||
$right = $this->parseIdentifier($expr->right); |
||||
|
||||
return $left . ' + ' . $right; |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,9 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php; |
||||
|
||||
use PhpParser\NodeVisitorAbstract; |
||||
|
||||
class Visitor extends NodeVisitorAbstract |
||||
{ |
||||
} |
||||
@ -0,0 +1,37 @@ |
||||
<?php |
||||
|
||||
use PhpAot\Core\Translator; |
||||
|
||||
function debug($v) |
||||
{ |
||||
/** |
||||
* @var $translator Translator |
||||
*/ |
||||
global $translator; |
||||
$lang = $translator->getLang(); |
||||
if ($translator->mode == 'cli') { |
||||
echo 'Error: Unsupported ' . $lang . ' Syntax, Line: ' . $translator->getLine($v) . ', Type: ' . $translator->getType($v) . PHP_EOL; |
||||
if (DEBUG) { |
||||
debug_print_backtrace(); |
||||
var_dump($v); |
||||
} |
||||
} else { |
||||
header('Content-Type: application/json'); |
||||
echo json_encode($v, JSON_PRETTY_PRINT); |
||||
} |
||||
die; |
||||
} |
||||
|
||||
function if_empty_debug($if_expr, $v) |
||||
{ |
||||
if (empty($if_expr)) { |
||||
debug($v); |
||||
} |
||||
} |
||||
|
||||
function if_not_empty_debug($if_expr, $v) |
||||
{ |
||||
if (!empty($if_expr)) { |
||||
debug($v); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue