diff --git a/.gitignore b/.gitignore index 06d9d978..237beca4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.idea /logs /vendor +/tmp diff --git a/bin/compiler.php b/bin/compiler.php new file mode 100644 index 00000000..b3550567 --- /dev/null +++ b/bin/compiler.php @@ -0,0 +1,37 @@ +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; +} + diff --git a/composer.json b/composer.json index 308d75fd..6e9ce853 100644 --- a/composer.json +++ b/composer.json @@ -5,6 +5,9 @@ "autoload": { "psr-4": { "PhpAot\\": "src" - } + }, + "files": [ + "src/functions.php" + ] } } diff --git a/conv.php b/conv.php index 46ecb316..5fdfbdc6 100644 --- a/conv.php +++ b/conv.php @@ -3,6 +3,7 @@ if ($argc < 2) { die("Usage: php conv.php [python-file]\n"); } +require __DIR__ . '/vendor/autoload.php'; define('DEBUG', getenv('PY2PHP_DEBUG')); define('STEP', getenv('PY2PHP_STEP')); @@ -16,37 +17,6 @@ if ($json->_type != 'Module') { echo "invalid python module\n"; } -function debug($v) -{ - global $translator; - if ($translator->mode == 'cli') { - echo 'Error: Unsupported Python Syntax, Line: ' . $v->lineno . ', Type: ' . $v->_type . 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); - } -} - - error_reporting(E_ERROR); // web or cli if (!empty($argv[2])) { diff --git a/examples/hello.php b/examples/hello.php new file mode 100644 index 00000000..c0f13535 --- /dev/null +++ b/examples/hello.php @@ -0,0 +1,17 @@ + 1, 'world' => 33.43]; + + $argc = 999; + $a = $b * 13; + + echo "value:=" . ($a + $b); + + return $a * $b; +} diff --git a/src/Core/Translator.php b/src/Core/Translator.php new file mode 100644 index 00000000..6b2a9c66 --- /dev/null +++ b/src/Core/Translator.php @@ -0,0 +1,36 @@ +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); + } + +} diff --git a/src/Php/Translator.php b/src/Php/Translator.php new file mode 100644 index 00000000..de67dd3f --- /dev/null +++ b/src/Php/Translator.php @@ -0,0 +1,314 @@ +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; + } +} + diff --git a/src/Php/Visitor.php b/src/Php/Visitor.php new file mode 100644 index 00000000..0bfb99a4 --- /dev/null +++ b/src/Php/Visitor.php @@ -0,0 +1,9 @@ +builtinTypes = array_flip($this->builtinTypes); } - function setMode($mode) - { - $this->mode = $mode; - } - - function setIndent(string $indent) - { - $this->indentStr = $indent; - } - function prepare($root) { foreach ($root->body as $body) { @@ -69,6 +57,16 @@ class Translator } } + function getLine($node): int + { + return $node->lineno; + } + + function getType($node): string + { + return $node->_type; + } + function parseAttribute($attr) { switch ($attr->_type) { @@ -482,11 +480,6 @@ class Translator return $code . $this->getIndent() . PHP_EOL . $this->getIndent() . '}'; } - private function getIndent() - { - return str_repeat($this->indentStr, $this->indentLevel); - } - function parseFunctionDef($node) { $name = $node->name; diff --git a/src/functions.php b/src/functions.php new file mode 100644 index 00000000..4f7e5b7e --- /dev/null +++ b/src/functions.php @@ -0,0 +1,37 @@ +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); + } +}