Run only the compiler:
$ php compiler.php test.phpRun compiler, then interpret it
$ php compiler.php test.php | php heap-interpreter.php| vendor |
| <?php | |
| require_once 'vendor/autoload.php'; | |
| $parser = new PhpParser\Parser(new PhpParser\Lexer); | |
| //$file = $argv[1]; | |
| $file = "test.php"; | |
| $code = file_get_contents($file); | |
| $ast = $parser->parse($code); | |
| //var_dump($ast); | |
| $state = [ | |
| "lines" => [], | |
| ]; | |
| compile($ast, $state); | |
| echo implode(" ", $state['lines']); | |
| function compile($ast, &$state) { | |
| foreach ($ast as $node) { | |
| compileNode($node, $state); | |
| } | |
| } | |
| function compileNode($node, &$state) { | |
| switch($node->getType()) { | |
| case 'Expr_BinaryOp_Plus': | |
| $left = compileNode($node->left, $state); | |
| $right = compileNode($node->right, $state); | |
| return "$left $right +"; | |
| case 'Scalar_LNumber': | |
| return $node->value; | |
| case 'Scalar_String': | |
| $ss = []; | |
| $cs = str_split($node->value); | |
| foreach ($cs as $c) { | |
| $ss[] = ord($c); | |
| } | |
| return implode(" ", $ss); | |
| case 'Stmt_Echo': | |
| foreach ($node->exprs as $expr) { | |
| $value = compileNode($expr, $state); | |
| $ss = []; | |
| $cs = explode(" ", $value); | |
| foreach ($cs as $c) { | |
| $ss[] = $c . " ."; | |
| } | |
| $state['lines'][] = implode(" ", $ss); | |
| } | |
| return; | |
| default: | |
| echo "Unknown node: " . $node->getType() . "\n"; | |
| } | |
| } |
| { | |
| "require": { | |
| "nikic/php-parser": "~1.1" | |
| } | |
| } |
| { | |
| "_readme": [ | |
| "This file locks the dependencies of your project to a known state", | |
| "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", | |
| "This file is @generated automatically" | |
| ], | |
| "hash": "f2b49436e25196e9c265ae186fa1571d", | |
| "packages": [ | |
| { | |
| "name": "nikic/php-parser", | |
| "version": "v1.1.0", | |
| "source": { | |
| "type": "git", | |
| "url": "https://github.com/nikic/PHP-Parser.git", | |
| "reference": "ac05ef6f95bf8361549604b6031c115f92f39528" | |
| }, | |
| "dist": { | |
| "type": "zip", | |
| "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ac05ef6f95bf8361549604b6031c115f92f39528", | |
| "reference": "ac05ef6f95bf8361549604b6031c115f92f39528", | |
| "shasum": "" | |
| }, | |
| "require": { | |
| "ext-tokenizer": "*", | |
| "php": ">=5.3" | |
| }, | |
| "type": "library", | |
| "extra": { | |
| "branch-alias": { | |
| "dev-master": "1.0-dev" | |
| } | |
| }, | |
| "autoload": { | |
| "files": [ | |
| "lib/bootstrap.php" | |
| ] | |
| }, | |
| "notification-url": "https://packagist.org/downloads/", | |
| "license": [ | |
| "BSD-3-Clause" | |
| ], | |
| "authors": [ | |
| { | |
| "name": "Nikita Popov" | |
| } | |
| ], | |
| "description": "A PHP parser written in PHP", | |
| "keywords": [ | |
| "parser", | |
| "php" | |
| ], | |
| "time": "2015-01-18 11:29:59" | |
| } | |
| ], | |
| "packages-dev": [], | |
| "aliases": [], | |
| "minimum-stability": "stable", | |
| "stability-flags": [], | |
| "prefer-stable": false, | |
| "prefer-lowest": false, | |
| "platform": [], | |
| "platform-dev": [] | |
| } |
| <?php | |
| <?php | |
| // \n => 10 | |
| // space => 32 | |
| // 0-9 => 48-57 | |
| // A-Z => 65-90 | |
| // a-z => 97-122 | |
| $code = file_get_contents("php://stdin"); | |
| $ops = preg_split('/\s+/', trim($code)); | |
| $labels = []; | |
| $calls = []; | |
| $stack = []; | |
| $buf = ''; | |
| // Pre-index the labels, if we want | |
| foreach ($ops as $ip => $op) { | |
| if (strpos($op, ':') !== false) { | |
| list($op, $param) = explode(':', $op); | |
| if ($op == 'label') { | |
| $labels[$param] = $ip; | |
| } | |
| } | |
| } | |
| $ip = 0; | |
| // ip = instruction pointer | |
| while($ip < count($ops)) { | |
| $op = $ops[$ip]; | |
| echo "$ip:\t$op\t" . json_encode($stack) . "\n"; | |
| $ip++; | |
| if (is_numeric($op)) { | |
| array_push($stack, (int)$op); | |
| continue; | |
| } | |
| if (strpos($op, ':') !== false) { | |
| list($op, $param) = explode(':', $op); | |
| } | |
| switch ($op) { | |
| case '*': | |
| $b = array_pop($stack); | |
| $a = array_pop($stack); | |
| array_push($stack, $a * $b); | |
| break; | |
| case '/': | |
| $b = array_pop($stack); | |
| $a = array_pop($stack); | |
| array_push($stack, $a / $b); | |
| break; | |
| case '+': | |
| $b = array_pop($stack); | |
| $a = array_pop($stack); | |
| array_push($stack, $a + $b); | |
| break; | |
| case '-': | |
| $b = array_pop($stack); | |
| $a = array_pop($stack); | |
| array_push($stack, $a - $b); | |
| break; | |
| case '.': // unary op (takes 1 arg) | |
| $a = array_pop($stack); | |
| $buf .= chr($a); | |
| break; | |
| case '.num'; | |
| $a = array_pop($stack); | |
| $buf .= $a; | |
| break; | |
| case '.newline': | |
| $buf .= "\n"; | |
| break; | |
| case 'dup': | |
| $a = array_pop($stack); | |
| array_push($stack, $a); | |
| array_push($stack, $a); | |
| break; | |
| case 'jmp': | |
| $a = array_pop($stack); | |
| $ip += $a; | |
| break; | |
| case 'label': | |
| break; | |
| case 'jump': | |
| $ip = $labels[$param]; | |
| //$ip = array_search('label:' . $param, $ops, true); | |
| break; | |
| case 'jumpz': | |
| $a = array_pop($stack); | |
| if ($a === 0) { | |
| $ip = $labels[$param]; | |
| } | |
| break; | |
| case 'jumpnz': | |
| $a = array_pop($stack); | |
| if ($a !== 0) { | |
| $ip = $labels[$param]; | |
| } | |
| break; | |
| case 'call': | |
| array_push($calls, $ip); | |
| $ip = $labels[$param]; | |
| break; | |
| case 'ret': | |
| $ip = array_pop($calls); | |
| break; | |
| default: | |
| throw new RuntimeException("unknown op $op"); | |
| } | |
| } | |
| echo $buf; | |
| //var_dump($stack); |
| <?php | |
| echo "hello world!\n"; |