From 95c8a8e03d85c5d95464c758b73fe3690fb833af Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 16 Jan 2024 15:33:05 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + composer.json | 10 + composer.lock | 67 +++ conv.php | 943 +------------------------------------ src/Python/Translator.php | 945 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 1024 insertions(+), 942 deletions(-) create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 src/Python/Translator.php diff --git a/.gitignore b/.gitignore index 5ba56c97..06d9d978 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /.idea /logs +/vendor diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..308d75fd --- /dev/null +++ b/composer.json @@ -0,0 +1,10 @@ +{ + "require": { + "nikic/php-parser": "^5.0" + }, + "autoload": { + "psr-4": { + "PhpAot\\": "src" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..9978b998 --- /dev/null +++ b/composer.lock @@ -0,0 +1,67 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "4fb14cfb0edc28c424dffe3bebbb888a", + "packages": [ + { + "name": "nikic/php-parser", + "version": "v5.0.0", + "dist": { + "type": "zip", + "url": "https://mirrors.cloud.tencent.com/repository/composer/nikic/php-parser/v5.0.0/nikic-php-parser-v5.0.0.zip", + "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2024-01-07T17:17:35+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/conv.php b/conv.php index eac48ad3..46ecb316 100644 --- a/conv.php +++ b/conv.php @@ -46,947 +46,6 @@ function if_not_empty_debug($if_expr, $v) } } -class Translator -{ - private array $keywords = ['abs', 'aiter', 'all', 'anext', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']; - private array $keywordsMap = []; - private int $indentLevel = 0; - private string $indentStr = "\t"; - public string $mode; - private array $definedFunctions = []; - private array $builtinTypes = [ - 'ArithmeticError', - 'AssertionError', - 'AttributeError', - 'BaseException', - 'BaseExceptionGroup', - 'BlockingIOError', - 'BrokenPipeError', - 'BufferError', - 'BytesWarning', - 'ChildProcessError', - 'ConnectionAbortedError', - 'ConnectionError', - 'ConnectionRefusedError', - 'ConnectionResetError', - 'DeprecationWarning', - 'EOFError', - 'Ellipsis', - 'EncodingWarning', - 'EnvironmentError', - 'Exception', 'ExceptionGroup', 'False', - 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', - 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', - 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', - 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', - 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', - 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', - 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', - 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', - 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', - 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', - 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError' - ]; - - function __construct() - { - $this->keywordsMap = array_flip($this->keywords); - $this->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) { - if ($body->_type == 'FunctionDef') { - $this->definedFunctions[$body->name] = 1; - } - } - } - - function parseAttribute($attr) - { - switch ($attr->_type) { - case 'Name': - return '$' . $attr->id; - case 'Attribute': - return $this->parseAttribute($attr->value) . '->' . $attr->attr; - case 'Constant': - return 'PyCore::str("' . $attr->s . '")'; - case 'Call': - return $this->parseCall($attr); - case 'Subscript': - return $this->parseSubscript($attr); - case 'BinOp': - return $this->parseBinOp($attr); - default: - var_dump(__METHOD__, __LINE__); - debug($attr); - } - } - - function parseFunc($fn) - { - switch ($fn->_type) { - case 'Name': - $id = $fn->id; - if (isset($this->keywordsMap[$id])) { - return 'PyCore::' . $id; - } elseif (isset($this->definedFunctions[$id])) { - return $id; - } elseif (isset($this->builtinTypes[$id])) { - return '$builtins->' . $id; - } else { - return '$' . $id; - } - case 'Attribute': - return $this->parseAttribute($fn->value) . '->' . $fn->attr; - default: - debug($fn); - } - } - - function parseCall($call): string - { - $fn = $this->parseFunc($call->func); - if_empty_debug($fn, $call); - $args = $call->args; - $kwargs = $call->keywords; - - $args_list = []; - - foreach ($args as $arg) { - $args_list[] = $this->parseValue($arg); - } - - foreach ($kwargs as $arg) { - $name = $arg->arg; - $args_list[] = $name . ': ' . $this->parseValue($arg->value); - } - - if (empty($args_list)) { - return "$fn()"; - } else { - return "$fn(" . implode(', ', $args_list) . ")"; - } - } - - static function valueToRepr($v, $python = false) - { - if (is_string($v)) { - $v = str_replace( - ["\\", "\n", "\r", "\t", "\v", "\x00", "\""], - ["\\\\", "\\n", "\\r", "\\t", "\\v", "\\x00", "\\\""], - $v); - return "\"$v\""; - } elseif ($v === []) { - return '[]'; - } elseif (is_numeric($v)) { - if ($python) { - if (is_infinite($v)) { - return "float('inf')"; - } elseif (is_nan($v)) { - return "float('nan')"; - } - } - return strval($v); - } elseif (is_bool($v)) { - return $python ? ($v ? 'True' : 'False') : ($v ? 'true' : 'false'); - } elseif (is_null($v)) { - return $python ? 'None' : 'null'; - } elseif (is_array($v)) { - return var_export($v, true); - } else { - return $python ? 'None' : 'null'; - } - } - - function parseConstant($value): string - { - return self::valueToRepr($value->value); - } - - function parseTuple($tuple) - { - $ids = []; - foreach ($tuple->dims as $dim) { - $ids[] = $this->parseTarget($dim); - } - return '[' . implode(', ', $ids) . ']'; - } - - function parseListComp($listComp) - { - $code = ''; - $this->indentLevel++; - $code .= $this->getIndent() . '$___ = [];' . PHP_EOL; - $recipient = '$___'; - $generators = $this->parseGenerators($listComp->generators, $recipient, $captures); - $code .= $this->getIndent() . $generators; - $code .= $this->getIndent() . 'return $___;' . PHP_EOL; - $this->indentLevel--; - $code .= $this->getIndent() . '})(' . implode(',', $captures) . ')'; - return '(function(' . implode(',', $captures) . ') {' . PHP_EOL . $code; - } - - function parseValue($value) - { - switch ($value->_type) { - case 'Call': - return $this->parseCall($value); - case 'Constant': - return $this->parseConstant($value); - case 'Attribute': - return $this->parseAttribute($value); - case 'Name': - return $this->parseName($value); - case 'List': - return $this->parseList($value); - case 'Dict': - return $this->parseDict($value); - case 'Set': - return $this->parseSet($value); - case 'Tuple': - return $this->parseTuple($value); - case 'ListComp': - return $this->parseListComp($value); - case 'DictComp': - return $this->parseDictComp($value); - case 'BinOp': - return $this->parseBinOp($value); - case 'JoinedStr': - return $this->parseJoinedStr($value); - case 'Subscript': - return $this->parseSubscript($value); - case 'FormattedValue': - return $this->parseFormattedValue($value); - case 'UnaryOp': - return $this->parseTarget($value); - case 'BoolOp': - return $this->parseBoolOp($value); - case 'Compare': - return $this->parseTest($value); - case 'IfExp': - return $this->parseIfExp($value); - case 'Yield': - return $this->parseYield($value); - case 'Lambda': - return $this->parseLambda($value); - case 'GeneratorExp': - return $this->parseGeneratorExp($value); - default: - debug($value); - break; - } - } - - function parseSlice($slice, $op = 'Load') - { - if ($slice->_type == 'Slice') { - $_args[] = $slice->lower ? $this->parseTarget($slice->lower) : 'null'; - $_args[] = $slice->upper ? $this->parseTarget($slice->upper) : 'null'; - $_args[] = $slice->step ? $this->parseTarget($slice->step) : 'null'; - $target = 'PyCore::slice(' . implode(', ', $_args) . ')'; - } else { - $target = $this->parseTarget($slice); - } - if ($op == 'Store') { - return $slice->id . '->__setitem__(' . $target . ', $__value)'; - } elseif ($op == 'Del') { - return $slice->id . '->__delitem__(' . $target . ')'; - } else { - return $slice->id . '->__getitem__(' . $target . ')'; - } - } - - function parseImportFrom($node) - { - $module = $node->module; - $imports = []; - foreach ($node->names as $name) { - $type = $name->name; - $as = empty($name->asname) ? $type : $name->asname; - $imports[] = "\$$as = PyCore::import('$module')->$type"; - } - - return implode(';' . PHP_EOL, $imports); - } - - function parseImport($node) - { - $name = $node->names[0]; - $module = $name->name; - $as = empty($name->asname) ? $module : $name->asname; - return "\$$as = PyCore::import('$module')"; - } - - function parseTarget($target) - { - switch ($target->_type) { - case 'Name': - return '$' . $target->id; - case 'UnaryOp': - $operand = $this->parseTarget($target->operand); - switch ($target->op->_type) { - case 'USub': - return '-' . $operand; - case 'Not': - return '!' . $operand; - default: - debug($target); - break; - } - break; - case 'Tuple': - return $this->parseTuple($target); - case 'Subscript': - return $this->parseSubscript($target); - case 'IfExp': - return $this->parseIfExp($target); - case 'Attribute': - return $this->parseAttribute($target); - case 'Constant': - return $this->parseConstant($target); - case 'List': - return $this->parseList($target); - case 'Set': - return $this->parseSet($target); - case 'Call': - return $this->parseCall($target); - case 'BinOp': - return $this->parseBinOp($target); - case 'Compare': - return $this->parseTest($target); - case 'Slice': - return $this->parseSlice($target); - case 'BoolOp': - return $this->parseBoolOp($target); - case 'ListComp': - return $this->parseListComp($target); - default: - debug($target); - break; - } - } - - function parseIfExp($exp) - { - $test = $this->parseTest($exp->test); - $orelse = $this->parseValue($exp->orelse); - $body = $this->parseValue($exp->body); - return $test . ' ? ' . $body . ' : ' . $orelse; - } - - function parseExpr($node) - { - switch ($node->_type) { - case 'Expr': - if (is_string($node->value->value)) { - return '/** ' . $node->value->value . ' */' . PHP_EOL; - } else { - return $this->parseValue($node->value) . ';'; - } - default: - return $this->parseValue($node->value) . ';'; - } - } - - function parseArguments($args): string - { - $names = []; - foreach ($args->args as $arg) { - $names[] = '$' . $arg->arg; - } - if ($args->vararg) { - $names[] = '...$' . $args->vararg->arg; - } - return implode(', ', $names); - } - - function parseReturn($node) - { - return 'return ' . $this->parseValue($node->value); - } - - function parseTest($test) - { - $comparator = null; - $left = $test->left ? $this->parseValue($test->left) : ''; - if ($test->ops) { - $ops = $test->ops; - if (count($ops) != 1) { - debug($test); - } - $op = $ops[0]->_type; - $comparators = $test->comparators; - $comparator = $comparators[0]; - } elseif ($test->op) { - $op = $test->op->_type; - } else { - $op = ''; - } - - switch ($op) { - case 'Is': - case 'Eq': - return $left . ' == ' . $this->parseValue($comparator); - case 'NotEq': - return $left . ' != ' . $this->parseValue($comparator); - case 'In': - return $this->parseValue($comparator) . '->__contains__(' . $left . ')'; - case 'NotIn': - return '!' . $this->parseValue($comparator) . '->__contains__(' . $left . ')'; - case 'Gt': - return $left . ' > ' . $this->parseValue($comparator); - case 'Lt': - return $left . ' < ' . $this->parseValue($comparator); - case 'GtE': - return $left . ' >= ' . $this->parseValue($comparator); - case 'LtE': - return $left . ' <= ' . $this->parseValue($comparator); - case 'Not': - return '!' . $this->parseValue($test->operand); - case 'Or': - return $this->parseOr($test->values); - case 'And': - return $this->parseAnd($test->values); - default: - return $this->parseTarget($test); - } - } - - function parseIf($node) - { - $expr = $this->parseTest($node->test); - $this->indentLevel++; - $body = $this->parseBody($node->body); - $orelse = empty($node->orelse) ? '' : $this->parseBody($node->orelse); - $this->indentLevel--; - return 'if (' . $expr . ') {' . - PHP_EOL . $body . - PHP_EOL . $this->getIndent() . '}' . - ($orelse ? ' else {' . PHP_EOL . $orelse . PHP_EOL . $this->getIndent() . '}' : '') . - PHP_EOL; - } - - function parseIter($iter) - { - switch ($iter->_type) { - case 'Call': - return $this->parseCall($iter); - case 'List': - return $this->parseList($iter); - case 'Set': - return $this->parseSet($iter); - case 'Dict': - return $this->parseDict($iter); - case 'Name': - return $this->parseName($iter); - case 'Subscript': - return $this->parseSubscript($iter); - default: - debug($iter); - } - } - - function parseSubscript($node) - { - $code = ''; - if ($node->value) { - $code .= $this->parseValue($node->value); - } - if ($node->slice) { - $code .= $this->parseSlice($node->slice, $node->ctx->_type); - } - return $code; - } - - function parseFor($node) - { - $target = $this->parseTarget($node->target); - $iter = $this->parseIter($node->iter); - - $code = '$__iter = PyCore::iter(' . $iter . ');' . PHP_EOL; - $code .= $this->getIndent() . 'while($current = PyCore::next($__iter)) {' . PHP_EOL; - - $this->indentLevel++; - $code .= $this->getIndent() . $target . ' = $current;' . PHP_EOL; - $code .= $this->parseBody($node->body); - $this->indentLevel--; - - return $code . $this->getIndent() . PHP_EOL . $this->getIndent() . '}'; - } - - private function getIndent() - { - return str_repeat($this->indentStr, $this->indentLevel); - } - - function parseFunctionDef($node) - { - $name = $node->name; - $args = $this->parseArguments($node->args); - $fn = PHP_EOL . $this->getIndent() . 'function ' . $name . '(' . $args . ') {' . PHP_EOL; - $this->indentLevel++; - $fn .= $this->parseBody($node->body) . PHP_EOL;; - $this->indentLevel--; - $fn .= $this->getIndent() . '}' . PHP_EOL . PHP_EOL; - - return $fn; - } - - private function addLine($line, array &$lines) - { - if ($this->mode == 'cli' && STEP) { - echo $line . PHP_EOL; - } - $lines[] = $line; - } - - private function parseLine($node, &$lines) - { - switch ($node->_type) { - case 'ImportFrom': - $line = $this->parseImportFrom($node) . ';'; - break; - case 'Assign': - $target = $this->parseTarget($node->targets[0]); - $value = $this->parseValue($node->value); - if ($node->targets[0]->_type == 'Subscript') { - $this->addLine('$__value = ' . $value . ';', $lines); - $line = "$target;"; - } else { - $line = "$target = $value;"; - } - break; - case 'AugAssign': - $target = $this->parseTarget($node->target); - $value = $this->parseValue($node->value); - $line = "$target += $value;"; - break; - case 'Import': - $line = $this->parseImport($node) . ';'; - break; - case 'Expr': - $line = $this->parseExpr($node); - break; - case 'FunctionDef': - $line = $this->parseFunctionDef($node); - break; - case 'Return': - $line = $this->parseReturn($node) . ';'; - break; - case 'If': - $line = $this->parseIf($node); - break; - case 'For': - $line = $this->parseFor($node); - break; - case 'While': - $line = $this->parseWhile($node); - break; - case 'Try': - $line = $this->parseTry($node); - break; - case 'Raise': - $line = $this->parseRaise($node) . ';'; - break; - case 'Subscript': - $line = $this->parseSubscript($node) . ';'; - break; - case 'With': - $line = $this->parseWith($node); - break; - case 'Compare': - $line = $this->parseTest($node); - break; - case 'Delete': - $line = $this->parseDelete($node); - break; - case 'Continue': - $line = $this->parseContinue($node) . ';'; - break; - case 'Break': - $line = $this->parseBreak($node) . ';'; - break; - case 'Global': - $line = $this->parseGlobal($node) . ';'; - break; - case 'Pass': - $line = $this->parsePass($node) . ';'; - break; - case 'BinOp': - $line = $this->parseBinOp($node) . ';'; - break; - case 'IfExp': - $line = $this->parseIfExp($node) . ';'; - break; - case 'Tuple': - $line = $this->parseTuple($node) . ';'; - break; - case 'Lambda': - $line = $this->parseLambda($node) . ';'; - break; - case 'Call': - $line = $this->parseCall($node) . ';'; - break; - default: - debug($node); - $line = ''; - break; - } - $this->addLine($line, $lines); - } - - function parseBody($tree) - { - $lines = []; - foreach ($tree as $node) { - $this->parseLine($node, $lines); - } - foreach ($lines as &$line) { - $line = $this->getIndent() . $line; - } - return implode(PHP_EOL, $lines); - } - - function convert($tree) - { - $this->prepare($tree); - $output = 'parseBody($tree->body); - echo $output; - echo PHP_EOL; - } - - private function parseGenerators($generators, $recipient, &$captures) - { - $captures = []; - $code = ''; - - foreach ($generators as $k => $generator) { - $target = $this->parseTarget($generator->target); - if ($generator->iter->_type == 'Name') { - $name = '$' . $generator->iter->id; - $captures[] = $name; - } else { - $name = '$___iter'; - $code .= $this->getIndent() . '$___iter = ' . $this->parseIter($generator->iter) . ';' . PHP_EOL; - } - $code .= $this->getIndent() . 'foreach(' . $name . ' as $___i => ' . $target . ') {' . PHP_EOL; - $this->indentLevel++; - if (count($generators) === 1) { - $code .= $this->getIndent() . $recipient . '[] = ' . $target . ';' . PHP_EOL; - } else { - $code .= $this->getIndent() . $recipient . '[$___i][' . $k . '] = ' . $target . ';' . PHP_EOL; - } - $this->indentLevel--; - $code .= $this->getIndent() . '}' . PHP_EOL; - } - return $code; - } - - private function parseBinOp($value) - { - $op = $value->op->_type; - $left = $this->parseTarget($value->left); - $right = $this->parseValue($value->right); - switch ($op) { - case 'Mod': - return $left . ' % ' . $right; - case 'Add': - return $left . ' + ' . $right; - case 'Sub': - return $left . ' - ' . $right; - case 'Mult': - return $left . ' * ' . $right; - case 'Div': - return $left . ' / ' . $right; - default: - return '$operator->' . strtolower($op) . '(' . $left . ' , ' . $right . ')'; - } - } - - private function parseJoinedStr($value) - { - $list = []; - foreach ($value->values as $v) { - $list[] = $this->parseValue($v); - } - return implode(' . ', $list); - } - - private function parseList($target) - { - $values = []; - foreach ($target->elts as $e) { - $values[] = $this->parseValue($e); - } - return 'new PyList([' . implode(', ', $values) . '])'; - } - - private function parseYield($value) - { - return 'yield ' . $this->parseValue($value->value) . ';'; - } - - - private function parseWith(mixed $node) - { - $items = $node->items; - $body = $node->body; - $code = ''; - $finally_code = ''; - - $parseItem = function ($call, $target) use (&$code, &$finally_code) { - $call = $this->parseCall($call); - $target = empty($target) ? '$__' : $this->parseTarget($target); - $code .= $target . '__object = ' . $call . ';' . PHP_EOL;; - $code .= $target . ' = ' . $target . '__object->__enter__();' . PHP_EOL; - $this->indentLevel++; - $finally_code .= $this->getIndent() . $target . '__object->__exit__();' . PHP_EOL; - $this->indentLevel--; - }; - - foreach ($items as $k => $item) { - if ($item->context_expr->_type == 'Tuple') { - $context_expr = $item->context_expr->dims; - $optional_vars = $item->optional_vars->dims; - foreach ($context_expr as $k2 => $dim) { - $parseItem($dim, $optional_vars[$k2]); - } - } else { - $parseItem($item->context_expr, $item->optional_vars); - } - } - - $code .= 'try {' . PHP_EOL; - $this->indentLevel++; - $code .= $this->parseBody($body); - $this->indentLevel--; - $code .= PHP_EOL . '} finally {' . PHP_EOL; - $code .= $finally_code; - $code .= '}' . PHP_EOL; - return $code; - } - - private function parseDict($dict) - { - $code = '[' . PHP_EOL; - foreach ($dict->values as $k => $v) { - if (empty($dict->keys[$k])) { - debug($v); - } - $key = $dict->keys[$k]; - $this->indentLevel++; - $code .= $this->getIndent() . $this->parseTarget($key) . ' => ' . $this->parseTarget($v) . ',' . PHP_EOL; - $this->indentLevel--; - } - return 'new PyDict(' . $code . '])'; - } - - - private function parseBoolOp($value) - { - switch ($value->op->_type) { - case 'And': - return $this->parseAnd($value->values); - case 'Or': - return $this->parseOr($value->values); - default: - debug($value); - break; - } - } - - private function parseAnd($values) - { - $targets = []; - foreach ($values as $v) { - $targets[] = $this->parseTarget($v); - } - return implode(' and ', $targets); - } - - private function parseOr($values) - { - $targets = []; - foreach ($values as $v) { - $targets[] = $this->parseTarget($v); - } - return implode(' or ', $targets); - } - - private function parseDelete(mixed $node) - { - $code = ''; - foreach ($node->targets as $target) { - $code .= $this->getIndent() . $this->parseSubscript($target) . ';' . PHP_EOL; - } - return $code; - } - - private function parseSet($target) - { - $values = []; - foreach ($target->elts as $e) { - $values[] = $this->parseValue($e); - } - return 'new PySet([' . implode(', ', $values) . '])'; - } - - private function parseWhile(mixed $node) - { - $test = $this->parseTest($node->test); - $code = 'while(' . $test . ') {' . PHP_EOL; - $this->indentLevel++; - $code .= $this->parseBody($node->body); - $this->indentLevel--; - return $code . $this->getIndent() . PHP_EOL . $this->getIndent() . '}'; - } - - private function parseTry(mixed $node) - { - $code = 'try {' . PHP_EOL; - $this->indentLevel++; - $code .= $this->parseBody($node->body); - $this->indentLevel--; - $code .= PHP_EOL . '}'; - - if ($node->handlers) { - $code .= ' catch(PyError $e) {' . PHP_EOL; - $this->indentLevel++; - foreach ($node->handlers as $k => $handler) { - if ($handler->type->_type == 'Name') { - $id = $handler->type->id; - $type = isset($this->builtinTypes[$id]) ? '$builtins->' . $id : '$' . $id; - } else { - $types = []; - foreach ($handler->type->dims as $dim) { - $id = $dim->id; - $types[] = isset($this->builtinTypes[$id]) ? '$builtins->' . $id : '$' . $id; - } - $type = 'new PyTuple([' . implode(', ', $types) . '])'; - } - $code .= ($k == 0 ? $this->getIndent() . 'if' : ' elseif') . ' (PyCore::isinstance($e, ' . $type . ')) {' . PHP_EOL; - $this->indentLevel++; - $code .= $this->parseBody($node->body); - $this->indentLevel--; - $code .= PHP_EOL . $this->getIndent() . '}'; - } - $code .= ' else {'; - $this->indentLevel++; - $code .= PHP_EOL . $this->getIndent() . 'throw $e;'; - $this->indentLevel--; - $code .= PHP_EOL . $this->getIndent() . '}' . PHP_EOL; - $this->indentLevel--; - $code .= $this->getIndent() . '}'; - } - - if ($node->finalbody) { - $code .= ' finally {' . PHP_EOL; - $this->indentLevel++; - $code .= $this->parseBody($node->finalbody); - $this->indentLevel--; - $code .= PHP_EOL . $this->getIndent() . '}'; - } - - return $code; - } - - private function parseRaise(mixed $node) - { - return 'throw ' . $this->parseCall($node->exc); - } - - private function parseName($value) - { - return '$' . $value->id; - } - - private function parseContinue(mixed $node) - { - return 'continue'; - } - - private function parseBreak(mixed $node) - { - return 'break'; - } - - private function parseGlobal(mixed $node) - { - $names = []; - foreach ($node->names as $name) { - $names[] = '$' . $name; - } - return 'global ' . implode(', ', $names); - } - - private function parsePass(mixed $node) - { - return 'return'; - } - - private function parseLambda($value) - { - $lines = []; - $args = $this->parseArguments($value->args); - $code = 'function (' . $args . ') {' . PHP_EOL; - $this->indentLevel++; - $this->parseLine($value->body, $lines); - $code .= $this->getIndent() . 'return ' . $lines[0] . PHP_EOL; - $this->indentLevel--; - $code .= $this->getIndent() . '}'; - return $code; - } - - private function parseFormattedValue($value) - { - $expr = $this->parseValue($value->value); - if ($value->format_spec and $value->format_spec->_type == 'JoinedStr') { - if (count($value->format_spec->values) != 1) { - debug($value); - } - $format = $value->format_spec->values[0]->value; - if ($format[0] == '.' and $format[-1] == 'f') { - return 'round(' . $expr . ', ' . substr($format, 1, strlen($format) - 2) . ')'; - } - } else { - return $expr; - } - } - - private function parseDictComp($node) - { - $code = ''; - $this->indentLevel++; - $code .= $this->getIndent() . '$___ = [];' . PHP_EOL; - $recipient = '$___'; - $generators = $this->parseGenerators($node->generators, $recipient, $captures); - $code .= $this->getIndent() . $generators; - $code .= $this->getIndent() . 'return $___;' . PHP_EOL; - $this->indentLevel--; - $code .= $this->getIndent() . '})(' . implode(',', $captures) . ')'; - return '(function(' . implode(',', $captures) . ') {' . PHP_EOL . $code; - } - - private function parseGeneratorExp($value) - { - debug($value); - } -} error_reporting(E_ERROR); // web or cli @@ -996,7 +55,7 @@ if (!empty($argv[2])) { $mode = 'cli'; } -$translator = new Translator(); +$translator = new PhpAot\Python\Translator(); $translator->setMode($mode); $translator->setIndent(' '); $translator->convert($json); diff --git a/src/Python/Translator.php b/src/Python/Translator.php new file mode 100644 index 00000000..71c2468f --- /dev/null +++ b/src/Python/Translator.php @@ -0,0 +1,945 @@ +keywordsMap = array_flip($this->keywords); + $this->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) { + if ($body->_type == 'FunctionDef') { + $this->definedFunctions[$body->name] = 1; + } + } + } + + function parseAttribute($attr) + { + switch ($attr->_type) { + case 'Name': + return '$' . $attr->id; + case 'Attribute': + return $this->parseAttribute($attr->value) . '->' . $attr->attr; + case 'Constant': + return 'PyCore::str("' . $attr->s . '")'; + case 'Call': + return $this->parseCall($attr); + case 'Subscript': + return $this->parseSubscript($attr); + case 'BinOp': + return $this->parseBinOp($attr); + default: + var_dump(__METHOD__, __LINE__); + debug($attr); + } + } + + function parseFunc($fn) + { + switch ($fn->_type) { + case 'Name': + $id = $fn->id; + if (isset($this->keywordsMap[$id])) { + return 'PyCore::' . $id; + } elseif (isset($this->definedFunctions[$id])) { + return $id; + } elseif (isset($this->builtinTypes[$id])) { + return '$builtins->' . $id; + } else { + return '$' . $id; + } + case 'Attribute': + return $this->parseAttribute($fn->value) . '->' . $fn->attr; + default: + debug($fn); + } + } + + function parseCall($call): string + { + $fn = $this->parseFunc($call->func); + if_empty_debug($fn, $call); + $args = $call->args; + $kwargs = $call->keywords; + + $args_list = []; + + foreach ($args as $arg) { + $args_list[] = $this->parseValue($arg); + } + + foreach ($kwargs as $arg) { + $name = $arg->arg; + $args_list[] = $name . ': ' . $this->parseValue($arg->value); + } + + if (empty($args_list)) { + return "$fn()"; + } else { + return "$fn(" . implode(', ', $args_list) . ")"; + } + } + + static function valueToRepr($v, $python = false) + { + if (is_string($v)) { + $v = str_replace( + ["\\", "\n", "\r", "\t", "\v", "\x00", "\""], + ["\\\\", "\\n", "\\r", "\\t", "\\v", "\\x00", "\\\""], + $v); + return "\"$v\""; + } elseif ($v === []) { + return '[]'; + } elseif (is_numeric($v)) { + if ($python) { + if (is_infinite($v)) { + return "float('inf')"; + } elseif (is_nan($v)) { + return "float('nan')"; + } + } + return strval($v); + } elseif (is_bool($v)) { + return $python ? ($v ? 'True' : 'False') : ($v ? 'true' : 'false'); + } elseif (is_null($v)) { + return $python ? 'None' : 'null'; + } elseif (is_array($v)) { + return var_export($v, true); + } else { + return $python ? 'None' : 'null'; + } + } + + function parseConstant($value): string + { + return self::valueToRepr($value->value); + } + + function parseTuple($tuple) + { + $ids = []; + foreach ($tuple->dims as $dim) { + $ids[] = $this->parseTarget($dim); + } + return '[' . implode(', ', $ids) . ']'; + } + + function parseListComp($listComp) + { + $code = ''; + $this->indentLevel++; + $code .= $this->getIndent() . '$___ = [];' . PHP_EOL; + $recipient = '$___'; + $generators = $this->parseGenerators($listComp->generators, $recipient, $captures); + $code .= $this->getIndent() . $generators; + $code .= $this->getIndent() . 'return $___;' . PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '})(' . implode(',', $captures) . ')'; + return '(function(' . implode(',', $captures) . ') {' . PHP_EOL . $code; + } + + function parseValue($value) + { + switch ($value->_type) { + case 'Call': + return $this->parseCall($value); + case 'Constant': + return $this->parseConstant($value); + case 'Attribute': + return $this->parseAttribute($value); + case 'Name': + return $this->parseName($value); + case 'List': + return $this->parseList($value); + case 'Dict': + return $this->parseDict($value); + case 'Set': + return $this->parseSet($value); + case 'Tuple': + return $this->parseTuple($value); + case 'ListComp': + return $this->parseListComp($value); + case 'DictComp': + return $this->parseDictComp($value); + case 'BinOp': + return $this->parseBinOp($value); + case 'JoinedStr': + return $this->parseJoinedStr($value); + case 'Subscript': + return $this->parseSubscript($value); + case 'FormattedValue': + return $this->parseFormattedValue($value); + case 'UnaryOp': + return $this->parseTarget($value); + case 'BoolOp': + return $this->parseBoolOp($value); + case 'Compare': + return $this->parseTest($value); + case 'IfExp': + return $this->parseIfExp($value); + case 'Yield': + return $this->parseYield($value); + case 'Lambda': + return $this->parseLambda($value); + case 'GeneratorExp': + return $this->parseGeneratorExp($value); + default: + debug($value); + break; + } + } + + function parseSlice($slice, $op = 'Load') + { + if ($slice->_type == 'Slice') { + $_args[] = $slice->lower ? $this->parseTarget($slice->lower) : 'null'; + $_args[] = $slice->upper ? $this->parseTarget($slice->upper) : 'null'; + $_args[] = $slice->step ? $this->parseTarget($slice->step) : 'null'; + $target = 'PyCore::slice(' . implode(', ', $_args) . ')'; + } else { + $target = $this->parseTarget($slice); + } + if ($op == 'Store') { + return $slice->id . '->__setitem__(' . $target . ', $__value)'; + } elseif ($op == 'Del') { + return $slice->id . '->__delitem__(' . $target . ')'; + } else { + return $slice->id . '->__getitem__(' . $target . ')'; + } + } + + function parseImportFrom($node) + { + $module = $node->module; + $imports = []; + foreach ($node->names as $name) { + $type = $name->name; + $as = empty($name->asname) ? $type : $name->asname; + $imports[] = "\$$as = PyCore::import('$module')->$type"; + } + + return implode(';' . PHP_EOL, $imports); + } + + function parseImport($node) + { + $name = $node->names[0]; + $module = $name->name; + $as = empty($name->asname) ? $module : $name->asname; + return "\$$as = PyCore::import('$module')"; + } + + function parseTarget($target) + { + switch ($target->_type) { + case 'Name': + return '$' . $target->id; + case 'UnaryOp': + $operand = $this->parseTarget($target->operand); + switch ($target->op->_type) { + case 'USub': + return '-' . $operand; + case 'Not': + return '!' . $operand; + default: + debug($target); + break; + } + break; + case 'Tuple': + return $this->parseTuple($target); + case 'Subscript': + return $this->parseSubscript($target); + case 'IfExp': + return $this->parseIfExp($target); + case 'Attribute': + return $this->parseAttribute($target); + case 'Constant': + return $this->parseConstant($target); + case 'List': + return $this->parseList($target); + case 'Set': + return $this->parseSet($target); + case 'Call': + return $this->parseCall($target); + case 'BinOp': + return $this->parseBinOp($target); + case 'Compare': + return $this->parseTest($target); + case 'Slice': + return $this->parseSlice($target); + case 'BoolOp': + return $this->parseBoolOp($target); + case 'ListComp': + return $this->parseListComp($target); + default: + debug($target); + break; + } + } + + function parseIfExp($exp) + { + $test = $this->parseTest($exp->test); + $orelse = $this->parseValue($exp->orelse); + $body = $this->parseValue($exp->body); + return $test . ' ? ' . $body . ' : ' . $orelse; + } + + function parseExpr($node) + { + switch ($node->_type) { + case 'Expr': + if (is_string($node->value->value)) { + return '/** ' . $node->value->value . ' */' . PHP_EOL; + } else { + return $this->parseValue($node->value) . ';'; + } + default: + return $this->parseValue($node->value) . ';'; + } + } + + function parseArguments($args): string + { + $names = []; + foreach ($args->args as $arg) { + $names[] = '$' . $arg->arg; + } + if ($args->vararg) { + $names[] = '...$' . $args->vararg->arg; + } + return implode(', ', $names); + } + + function parseReturn($node) + { + return 'return ' . $this->parseValue($node->value); + } + + function parseTest($test) + { + $comparator = null; + $left = $test->left ? $this->parseValue($test->left) : ''; + if ($test->ops) { + $ops = $test->ops; + if (count($ops) != 1) { + debug($test); + } + $op = $ops[0]->_type; + $comparators = $test->comparators; + $comparator = $comparators[0]; + } elseif ($test->op) { + $op = $test->op->_type; + } else { + $op = ''; + } + + switch ($op) { + case 'Is': + case 'Eq': + return $left . ' == ' . $this->parseValue($comparator); + case 'NotEq': + return $left . ' != ' . $this->parseValue($comparator); + case 'In': + return $this->parseValue($comparator) . '->__contains__(' . $left . ')'; + case 'NotIn': + return '!' . $this->parseValue($comparator) . '->__contains__(' . $left . ')'; + case 'Gt': + return $left . ' > ' . $this->parseValue($comparator); + case 'Lt': + return $left . ' < ' . $this->parseValue($comparator); + case 'GtE': + return $left . ' >= ' . $this->parseValue($comparator); + case 'LtE': + return $left . ' <= ' . $this->parseValue($comparator); + case 'Not': + return '!' . $this->parseValue($test->operand); + case 'Or': + return $this->parseOr($test->values); + case 'And': + return $this->parseAnd($test->values); + default: + return $this->parseTarget($test); + } + } + + function parseIf($node) + { + $expr = $this->parseTest($node->test); + $this->indentLevel++; + $body = $this->parseBody($node->body); + $orelse = empty($node->orelse) ? '' : $this->parseBody($node->orelse); + $this->indentLevel--; + return 'if (' . $expr . ') {' . + PHP_EOL . $body . + PHP_EOL . $this->getIndent() . '}' . + ($orelse ? ' else {' . PHP_EOL . $orelse . PHP_EOL . $this->getIndent() . '}' : '') . + PHP_EOL; + } + + function parseIter($iter) + { + switch ($iter->_type) { + case 'Call': + return $this->parseCall($iter); + case 'List': + return $this->parseList($iter); + case 'Set': + return $this->parseSet($iter); + case 'Dict': + return $this->parseDict($iter); + case 'Name': + return $this->parseName($iter); + case 'Subscript': + return $this->parseSubscript($iter); + default: + debug($iter); + } + } + + function parseSubscript($node) + { + $code = ''; + if ($node->value) { + $code .= $this->parseValue($node->value); + } + if ($node->slice) { + $code .= $this->parseSlice($node->slice, $node->ctx->_type); + } + return $code; + } + + function parseFor($node) + { + $target = $this->parseTarget($node->target); + $iter = $this->parseIter($node->iter); + + $code = '$__iter = PyCore::iter(' . $iter . ');' . PHP_EOL; + $code .= $this->getIndent() . 'while($current = PyCore::next($__iter)) {' . PHP_EOL; + + $this->indentLevel++; + $code .= $this->getIndent() . $target . ' = $current;' . PHP_EOL; + $code .= $this->parseBody($node->body); + $this->indentLevel--; + + return $code . $this->getIndent() . PHP_EOL . $this->getIndent() . '}'; + } + + private function getIndent() + { + return str_repeat($this->indentStr, $this->indentLevel); + } + + function parseFunctionDef($node) + { + $name = $node->name; + $args = $this->parseArguments($node->args); + $fn = PHP_EOL . $this->getIndent() . 'function ' . $name . '(' . $args . ') {' . PHP_EOL; + $this->indentLevel++; + $fn .= $this->parseBody($node->body) . PHP_EOL;; + $this->indentLevel--; + $fn .= $this->getIndent() . '}' . PHP_EOL . PHP_EOL; + + return $fn; + } + + private function addLine($line, array &$lines) + { + if ($this->mode == 'cli' && STEP) { + echo $line . PHP_EOL; + } + $lines[] = $line; + } + + private function parseLine($node, &$lines) + { + switch ($node->_type) { + case 'ImportFrom': + $line = $this->parseImportFrom($node) . ';'; + break; + case 'Assign': + $target = $this->parseTarget($node->targets[0]); + $value = $this->parseValue($node->value); + if ($node->targets[0]->_type == 'Subscript') { + $this->addLine('$__value = ' . $value . ';', $lines); + $line = "$target;"; + } else { + $line = "$target = $value;"; + } + break; + case 'AugAssign': + $target = $this->parseTarget($node->target); + $value = $this->parseValue($node->value); + $line = "$target += $value;"; + break; + case 'Import': + $line = $this->parseImport($node) . ';'; + break; + case 'Expr': + $line = $this->parseExpr($node); + break; + case 'FunctionDef': + $line = $this->parseFunctionDef($node); + break; + case 'Return': + $line = $this->parseReturn($node) . ';'; + break; + case 'If': + $line = $this->parseIf($node); + break; + case 'For': + $line = $this->parseFor($node); + break; + case 'While': + $line = $this->parseWhile($node); + break; + case 'Try': + $line = $this->parseTry($node); + break; + case 'Raise': + $line = $this->parseRaise($node) . ';'; + break; + case 'Subscript': + $line = $this->parseSubscript($node) . ';'; + break; + case 'With': + $line = $this->parseWith($node); + break; + case 'Compare': + $line = $this->parseTest($node); + break; + case 'Delete': + $line = $this->parseDelete($node); + break; + case 'Continue': + $line = $this->parseContinue($node) . ';'; + break; + case 'Break': + $line = $this->parseBreak($node) . ';'; + break; + case 'Global': + $line = $this->parseGlobal($node) . ';'; + break; + case 'Pass': + $line = $this->parsePass($node) . ';'; + break; + case 'BinOp': + $line = $this->parseBinOp($node) . ';'; + break; + case 'IfExp': + $line = $this->parseIfExp($node) . ';'; + break; + case 'Tuple': + $line = $this->parseTuple($node) . ';'; + break; + case 'Lambda': + $line = $this->parseLambda($node) . ';'; + break; + case 'Call': + $line = $this->parseCall($node) . ';'; + break; + default: + debug($node); + $line = ''; + break; + } + $this->addLine($line, $lines); + } + + function parseBody($tree) + { + $lines = []; + foreach ($tree as $node) { + $this->parseLine($node, $lines); + } + foreach ($lines as &$line) { + $line = $this->getIndent() . $line; + } + return implode(PHP_EOL, $lines); + } + + function convert($tree) + { + $this->prepare($tree); + $output = 'parseBody($tree->body); + echo $output; + echo PHP_EOL; + } + + private function parseGenerators($generators, $recipient, &$captures) + { + $captures = []; + $code = ''; + + foreach ($generators as $k => $generator) { + $target = $this->parseTarget($generator->target); + if ($generator->iter->_type == 'Name') { + $name = '$' . $generator->iter->id; + $captures[] = $name; + } else { + $name = '$___iter'; + $code .= $this->getIndent() . '$___iter = ' . $this->parseIter($generator->iter) . ';' . PHP_EOL; + } + $code .= $this->getIndent() . 'foreach(' . $name . ' as $___i => ' . $target . ') {' . PHP_EOL; + $this->indentLevel++; + if (count($generators) === 1) { + $code .= $this->getIndent() . $recipient . '[] = ' . $target . ';' . PHP_EOL; + } else { + $code .= $this->getIndent() . $recipient . '[$___i][' . $k . '] = ' . $target . ';' . PHP_EOL; + } + $this->indentLevel--; + $code .= $this->getIndent() . '}' . PHP_EOL; + } + return $code; + } + + private function parseBinOp($value) + { + $op = $value->op->_type; + $left = $this->parseTarget($value->left); + $right = $this->parseValue($value->right); + switch ($op) { + case 'Mod': + return $left . ' % ' . $right; + case 'Add': + return $left . ' + ' . $right; + case 'Sub': + return $left . ' - ' . $right; + case 'Mult': + return $left . ' * ' . $right; + case 'Div': + return $left . ' / ' . $right; + default: + return '$operator->' . strtolower($op) . '(' . $left . ' , ' . $right . ')'; + } + } + + private function parseJoinedStr($value) + { + $list = []; + foreach ($value->values as $v) { + $list[] = $this->parseValue($v); + } + return implode(' . ', $list); + } + + private function parseList($target) + { + $values = []; + foreach ($target->elts as $e) { + $values[] = $this->parseValue($e); + } + return 'new PyList([' . implode(', ', $values) . '])'; + } + + private function parseYield($value) + { + return 'yield ' . $this->parseValue($value->value) . ';'; + } + + + private function parseWith(mixed $node) + { + $items = $node->items; + $body = $node->body; + $code = ''; + $finally_code = ''; + + $parseItem = function ($call, $target) use (&$code, &$finally_code) { + $call = $this->parseCall($call); + $target = empty($target) ? '$__' : $this->parseTarget($target); + $code .= $target . '__object = ' . $call . ';' . PHP_EOL;; + $code .= $target . ' = ' . $target . '__object->__enter__();' . PHP_EOL; + $this->indentLevel++; + $finally_code .= $this->getIndent() . $target . '__object->__exit__();' . PHP_EOL; + $this->indentLevel--; + }; + + foreach ($items as $k => $item) { + if ($item->context_expr->_type == 'Tuple') { + $context_expr = $item->context_expr->dims; + $optional_vars = $item->optional_vars->dims; + foreach ($context_expr as $k2 => $dim) { + $parseItem($dim, $optional_vars[$k2]); + } + } else { + $parseItem($item->context_expr, $item->optional_vars); + } + } + + $code .= 'try {' . PHP_EOL; + $this->indentLevel++; + $code .= $this->parseBody($body); + $this->indentLevel--; + $code .= PHP_EOL . '} finally {' . PHP_EOL; + $code .= $finally_code; + $code .= '}' . PHP_EOL; + return $code; + } + + private function parseDict($dict) + { + $code = '[' . PHP_EOL; + foreach ($dict->values as $k => $v) { + if (empty($dict->keys[$k])) { + debug($v); + } + $key = $dict->keys[$k]; + $this->indentLevel++; + $code .= $this->getIndent() . $this->parseTarget($key) . ' => ' . $this->parseTarget($v) . ',' . PHP_EOL; + $this->indentLevel--; + } + return 'new PyDict(' . $code . '])'; + } + + + private function parseBoolOp($value) + { + switch ($value->op->_type) { + case 'And': + return $this->parseAnd($value->values); + case 'Or': + return $this->parseOr($value->values); + default: + debug($value); + break; + } + } + + private function parseAnd($values) + { + $targets = []; + foreach ($values as $v) { + $targets[] = $this->parseTarget($v); + } + return implode(' and ', $targets); + } + + private function parseOr($values) + { + $targets = []; + foreach ($values as $v) { + $targets[] = $this->parseTarget($v); + } + return implode(' or ', $targets); + } + + private function parseDelete(mixed $node) + { + $code = ''; + foreach ($node->targets as $target) { + $code .= $this->getIndent() . $this->parseSubscript($target) . ';' . PHP_EOL; + } + return $code; + } + + private function parseSet($target) + { + $values = []; + foreach ($target->elts as $e) { + $values[] = $this->parseValue($e); + } + return 'new PySet([' . implode(', ', $values) . '])'; + } + + private function parseWhile(mixed $node) + { + $test = $this->parseTest($node->test); + $code = 'while(' . $test . ') {' . PHP_EOL; + $this->indentLevel++; + $code .= $this->parseBody($node->body); + $this->indentLevel--; + return $code . $this->getIndent() . PHP_EOL . $this->getIndent() . '}'; + } + + private function parseTry(mixed $node) + { + $code = 'try {' . PHP_EOL; + $this->indentLevel++; + $code .= $this->parseBody($node->body); + $this->indentLevel--; + $code .= PHP_EOL . '}'; + + if ($node->handlers) { + $code .= ' catch(PyError $e) {' . PHP_EOL; + $this->indentLevel++; + foreach ($node->handlers as $k => $handler) { + if ($handler->type->_type == 'Name') { + $id = $handler->type->id; + $type = isset($this->builtinTypes[$id]) ? '$builtins->' . $id : '$' . $id; + } else { + $types = []; + foreach ($handler->type->dims as $dim) { + $id = $dim->id; + $types[] = isset($this->builtinTypes[$id]) ? '$builtins->' . $id : '$' . $id; + } + $type = 'new PyTuple([' . implode(', ', $types) . '])'; + } + $code .= ($k == 0 ? $this->getIndent() . 'if' : ' elseif') . ' (PyCore::isinstance($e, ' . $type . ')) {' . PHP_EOL; + $this->indentLevel++; + $code .= $this->parseBody($node->body); + $this->indentLevel--; + $code .= PHP_EOL . $this->getIndent() . '}'; + } + $code .= ' else {'; + $this->indentLevel++; + $code .= PHP_EOL . $this->getIndent() . 'throw $e;'; + $this->indentLevel--; + $code .= PHP_EOL . $this->getIndent() . '}' . PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '}'; + } + + if ($node->finalbody) { + $code .= ' finally {' . PHP_EOL; + $this->indentLevel++; + $code .= $this->parseBody($node->finalbody); + $this->indentLevel--; + $code .= PHP_EOL . $this->getIndent() . '}'; + } + + return $code; + } + + private function parseRaise(mixed $node) + { + return 'throw ' . $this->parseCall($node->exc); + } + + private function parseName($value) + { + return '$' . $value->id; + } + + private function parseContinue(mixed $node) + { + return 'continue'; + } + + private function parseBreak(mixed $node) + { + return 'break'; + } + + private function parseGlobal(mixed $node) + { + $names = []; + foreach ($node->names as $name) { + $names[] = '$' . $name; + } + return 'global ' . implode(', ', $names); + } + + private function parsePass(mixed $node) + { + return 'return'; + } + + private function parseLambda($value) + { + $lines = []; + $args = $this->parseArguments($value->args); + $code = 'function (' . $args . ') {' . PHP_EOL; + $this->indentLevel++; + $this->parseLine($value->body, $lines); + $code .= $this->getIndent() . 'return ' . $lines[0] . PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '}'; + return $code; + } + + private function parseFormattedValue($value) + { + $expr = $this->parseValue($value->value); + if ($value->format_spec and $value->format_spec->_type == 'JoinedStr') { + if (count($value->format_spec->values) != 1) { + debug($value); + } + $format = $value->format_spec->values[0]->value; + if ($format[0] == '.' and $format[-1] == 'f') { + return 'round(' . $expr . ', ' . substr($format, 1, strlen($format) - 2) . ')'; + } + } else { + return $expr; + } + } + + private function parseDictComp($node) + { + $code = ''; + $this->indentLevel++; + $code .= $this->getIndent() . '$___ = [];' . PHP_EOL; + $recipient = '$___'; + $generators = $this->parseGenerators($node->generators, $recipient, $captures); + $code .= $this->getIndent() . $generators; + $code .= $this->getIndent() . 'return $___;' . PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '})(' . implode(',', $captures) . ')'; + return '(function(' . implode(',', $captures) . ') {' . PHP_EOL . $code; + } + + private function parseGeneratorExp($value) + { + debug($value); + } +}