Skip to content

Instantly share code, notes, and snippets.

@asgrim
Last active August 29, 2015 14:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save asgrim/42ab16bc77cb0e154812 to your computer and use it in GitHub Desktop.
Save asgrim/42ab16bc77cb0e154812 to your computer and use it in GitHub Desktop.
My stack interpreter from Cute Little Interpreters tutorial at phpbnl15

Run only the compiler:

$ php compiler.php test.php

Run compiler, then interpret it

$ php compiler.php test.php | php heap-interpreter.php
<?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
// \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";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment