/cps.php
PHP | 2013 lines | 1828 code | 138 blank | 47 comment | 140 complexity | 846830efa71fab3193a9f7aa3c2aca99 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- <?php
- // TODO
- // static variables
- // line number mappings in generated code (comments)
- // traits
- // namespaces
- // break and continue by non-const number (include array of basic block IDs in compiled code)
- // pass down break and continue stack when including to permit break in top level of an included file
- // Continuation passing style transformation for PHP
- require_once 'PHP-Parser/lib/bootstrap.php';
- require_once 'print.php';
- require_once 'runtime.php';
- require_once 'compiler_plugins.php';
- const TEMP_NAME = 't';
- const GLOBALS_TEMP_NAME = 'G';
- const ARGS_TEMP_NAME = 'A';
- const CONT_NAME = 'c';
- const LOCALS_NAME = 'l';
- const LABELS_NAME = 'g';
- const VALUE_NAME = 'v';
- const VALUE_REF_NAME = 'r';
- const VALUE_IS_REF_NAME = 'q';
- const JUNK_NAME = 'j';
- const PARAM_NAME = 'p';
- const USES_NAME = 'u';
- const EXCEPT_NAME = 'x';
- const STATICS_NAME = 's';
- $SUPERGLOBALS = ['GLOBALS', '_SERVER', '_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_REQUEST', '_ENV'];
- $MAGIC_METHODS = ['__construct', '__set', '__get', '__destruct', '__sleep', '__wakeup', '__toString', '__isset', '__unset', '__call', '__callStatic', '__set_state', '__clone'];
- function config($name, $value = null) {
- static $configs = [];
- if (isset($value)) {
- $configs[$name] = $value;
- }
- else {
- if (array_key_exists($name, $configs)) {
- return $configs[$name];
- }
- }
- }
- config('disable_strict_logical_operator_type', false);
- config('disable_func_get_args', false);
- config('trampoline_max_stack', 40); // optimum seems to be around 40 or 50
- $interpreter = $argv[1];
- $compiler = $interpreter . ' ' . __FILE__ . ' "' . $interpreter . '"';
- $file = $argv[3];
- $source = file_get_contents('php://stdin');
- $compiled = compile($source);
- switch ($mode = $argv[2]) {
- case 'main':
- echo "<?php\nrequire_once('" . dirname(__FILE__) . "/runtime.php');\n\$l=&\$GLOBALS;\n\$t=['" . GLOBALS_TEMP_NAME . "'=>&\$GLOBALS];\n\$g=[];\n";
- printStatements(generateTrampoline($compiled));
- echo "\n";
- break;
- case 'include':
- echo "<?php\n";
- printStatements($compiled);
- break;
-
- default:
- throw new Exception('unknown mode ' . $mode);
- break;
- }
- // A container for a single parser node reference
- class NodeReference extends PHPParser_Node_Expr {
- function __construct(PHPParser_Node_Expr $node) {
- $this->node = $node;
- }
-
- function getNode() {
- return $this->node;
- }
-
- function setNode(PHPParser_Node_Expr $node) {
- $this->node = $node;
- }
- }
- // Wrapper for a closure if it represents a return from a function.
- // This is used to determine whether a given continuation represents
- // a tail call that can be optimized away.
- class ReturnClosure {
- private $closure;
- function __construct($closure) {
- $this->closure = $closure;
- }
-
- function __invoke() {
- return call_user_func_array($this->closure, func_get_args());
- }
- }
- function generateDefaultTemps() {
- $temps = [];
- $temps[] = new PHPParser_Node_Expr_ArrayItem(
- new PHPParser_Node_Expr_Variable('GLOBALS'),
- new PHPParser_Node_Scalar_String(GLOBALS_TEMP_NAME),
- true
- );
- if (!config('disable_func_get_args')) {
- $temps[] = new PHPParser_Node_Expr_ArrayItem(
- new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('array_slice'), [
- new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('func_get_args'), []),
- new PHPParser_Node_Scalar_LNumber(2)
- ]),
- new PHPParser_Node_Scalar_String(ARGS_TEMP_NAME)
- );
- }
- return new PHPParser_Node_Expr_Array($temps);
- }
- function generateTemp() {
- static $temp_counter = 0;
- return new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_Variable(TEMP_NAME),
- new PHPParser_Node_Scalar_LNumber($temp_counter++)
- );
- }
- // Assign an expression to a temp or inline simple variable names.
- // Appends an assignment statement to $stmts if necessary.
- // Returns the name by which the expression will be known.
- function assignToTemp($expr, &$stmts, $byRef = false) {
- $temp = generateTemp();
- $assign = $byRef ? 'PHPParser_Node_Expr_AssignRef' : 'PHPParser_Node_Expr_Assign';
- $stmts[] = new $assign($temp, $expr);
- return $temp;
- }
- // Generate the expression to convert an expression into && compatible values (true, false, 1, 0, depending on value).
- function boolifyLogicalOperator($expr) {
- // If one does not depend in a === sense on the result of &&, and, ||, or being true vs. 1 vs. truthy value, then
- // we cut down a bit of bloat by configurably turning this into the identity function.
- if (config('disable_strict_logical_operator_type')) return $expr;
-
- $temp = generateTemp();
- return new PHPParser_Node_Expr_Ternary(
- new PHPParser_Node_Expr_FuncCall(
- new PHPParser_Node_Name('is_bool'),
- [new PHPParser_Node_Expr_Assign($temp, $expr)]
- ),
- $temp,
- new PHPParser_Node_Expr_Ternary(
- $temp,
- new PHPParser_Node_Scalar_LNumber(1),
- new PHPParser_Node_Scalar_LNumber(0)
- )
- );
- }
- function generateContinuation($next, $cont, $state) {
- if ($next instanceof ReturnClosure) {
- return $cont(new PHPParser_Node_Expr_Variable(CONT_NAME), $state);
- }
-
- $temp = generateTemp();
- return $next($temp, function ($result, $state) use ($cont, $temp) {
- $result = array_merge([
- new PHPParser_Node_Expr_Ternary(
- new PHPParser_Node_Expr_Variable(VALUE_IS_REF_NAME),
- new PHPParser_Node_Expr_AssignRef($temp, new PHPParser_Node_Expr_Variable(VALUE_REF_NAME)),
- new PHPParser_Node_Expr_Assign($temp, new PHPParser_Node_Expr_Variable(VALUE_NAME))
- ),
- new PHPParser_Node_Expr_Assign(
- new PHPParser_Node_Expr_Variable(LOCALS_NAME),
- new PHPParser_Node_Expr_Variable(LOCALS_NAME)
- )
- ], $result);
- return $cont(new PHPParser_Node_Expr_Closure([
- 'params' => [
- new PHPParser_Node_Param(VALUE_NAME),
- new PHPParser_Node_Param(VALUE_REF_NAME, new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('null')), null, true),
- new PHPParser_Node_Param(VALUE_IS_REF_NAME, new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('false')))
- ],
- 'uses' => [
- new PHPParser_Node_Expr_ClosureUse(CONT_NAME),
- new PHPParser_Node_Expr_ClosureUse(EXCEPT_NAME),
- new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME, true),
- new PHPParser_Node_Expr_ClosureUse(TEMP_NAME, true),
- new PHPParser_Node_Expr_ClosureUse(LABELS_NAME, true)
- ],
- 'stmts' => $result
- ]), $state);
- }, $state);
- }
- // When using generateContinuation, the $final parameter for the first traverseNode will
- // likely need to be the result of this function.
- function generateFinalForContinuation($final, $continuation) {
- return function ($result, $state) use ($final, $continuation) {
- if (!($continuation instanceof PHPParser_Node_Expr_Variable) || $continuation->name != CONT_NAME) {
- array_unshift($result, new PHPParser_Node_Expr_Assign(
- new PHPParser_Node_Expr_Variable(CONT_NAME),
- $continuation
- ));
- }
- return $final($result, $state);
- };
- }
- function isLValue($expr) {
- return ($expr instanceof PHPParser_Node_Expr_Variable ||
- $expr instanceof PHPParser_Node_Expr_ArrayDimFetch ||
- $expr instanceof PHPParser_Node_Expr_FuncCall ||
- $expr instanceof PHPParser_Node_Expr_MethodCall ||
- $expr instanceof PHPParser_Node_Expr_PropertyFetch ||
- $expr instanceof PHPParser_Node_Expr_StaticCall ||
- $expr instanceof PHPParser_Node_Expr_StaticPropertyFetch);
- }
- // When you need to call the current continuation with a return value, this function
- // will generate the return/call to emit.
- function generateReturn($value, $state) {
- if (!isset($value)) {
- $value = new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('null'));
- }
-
- $is_return_by_ref = $state ? $state->getIsReturnByRef() : false;
- $is_return_by_ref &= isLValue($value); // Don't try to return rvalue by ref. Simulates PHP behavior on return by reference.
-
- return [generateThunk(
- [
- new PHPParser_Node_Expr_ClosureUse(CONT_NAME),
- new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME, true),
- new PHPParser_Node_Expr_ClosureUse(TEMP_NAME, true)
- ],
- [new PHPParser_Node_Stmt_Return(
- new PHPParser_Node_Expr_FuncCall(
- new PHPParser_Node_Expr_Variable(CONT_NAME),
- $is_return_by_ref ?
- [
- new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('null')),
- new PHPParser_Node_Arg($value),
- new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('true'))
- ]
- :
- [new PHPParser_Node_Arg($value)]
- )
- )],
- 'return'
- )];
- }
- function generateTryCatchCall($expr, $state) {
- return new PHPParser_Node_Stmt_TryCatch(
- [new PHPParser_Node_Stmt_Return(
- new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Expr_Variable(CONT_NAME), [
- $expr
- ])
- )],
- [new PHPParser_Node_Stmt_Catch(
- new PHPParser_Node_Name('Exception'),
- VALUE_NAME,
- [new PHPParser_Node_Stmt_Return(
- new PHPParser_Node_Expr_FuncCall(
- new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_Variable(EXCEPT_NAME),
- new PHPParser_Node_Scalar_LNumber($state->getCatchNum())
- ),
- [new PHPParser_Node_Expr_Variable(VALUE_NAME)]
- )
- )]
- )]
- );
- }
- function generateMethodCall($object, $function, $args, $type, $state) {
- $is_builtin_call = in_array($function, $GLOBALS['MAGIC_METHODS']);
-
- $get_class = new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('get_class'), [$object]);
- if ($type == PHPParser_Node_Expr_StaticCall) {
- if ($object->toString() == 'self') {
- $object = new PHPParser_Node_Name($state->getSelf());
- }
- elseif ($object->toString() == 'parent') {
- $object = new PHPParser_Node_Name($state->getParent());
- }
-
- $get_class = new PHPParser_Node_Scalar_String($object->toString());
-
- if (isset(CpsRuntime::$builtin_methods[$object->toString()][$function])) {
- $is_builtin_call = true;
- }
- }
-
- if ($is_builtin_call) {
- $stmt = generateTryCatchCall(new $type($object, $function, $args), $state);
- }
- else {
- $orig_args = $args;
- array_unshift($args, $state->generateExceptParameter());
- array_unshift($args, new PHPParser_Node_Expr_Variable(CONT_NAME));
-
- $stmt = new PHPParser_Node_Stmt_If(
- new PHPParser_Node_Expr_Isset([
- new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_StaticPropertyFetch(
- new PHPParser_Node_Name('CpsRuntime'),
- 'builtin_methods'
- ),
- $get_class
- ),
- new PHPParser_Node_Scalar_String($function)
- )
- ]),
- [
- 'stmts' => [
- generateTryCatchCall(new $type($object, $function, $orig_args), $state)
- ],
- 'else' => new PHPParser_Node_Stmt_Else([
- new PHPParser_Node_Stmt_Return(new $type($object, $function, $args))
- ])
- ]
- );
- }
-
- return [generateThunk(
- [
- new PHPParser_Node_Expr_ClosureUse(CONT_NAME),
- new PHPParser_Node_Expr_ClosureUse(EXCEPT_NAME),
- new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME, true),
- new PHPParser_Node_Expr_ClosureUse(TEMP_NAME, true)
- ],
- [$stmt],
- 'call'
- )];
- }
- function wrapFunctionForCallback($function, $state) {
- $user_call_stmts = [new PHPParser_Node_Stmt_Return(
- new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('call_user_func_array'), [
- new PHPParser_Node_Expr_Variable(VALUE_NAME),
- new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('array_merge'), [
- new PHPParser_Node_Expr_Array([
- new PHPParser_Node_Expr_Variable(CONT_NAME),
- new PHPParser_Node_Expr_Variable(EXCEPT_NAME)
- ]),
- new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_Variable(TEMP_NAME),
- new PHPParser_Node_Scalar_String(ARGS_TEMP_NAME)
- )
- ])
- ])
- )];
- $function_transformer = new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_StaticPropertyFetch(
- new PHPParser_Node_Name('CpsRuntime'),
- 'builtin_functions'
- ),
- new PHPParser_Node_Expr_Variable(VALUE_NAME)
- );
- $builtin_call_stmts = [new PHPParser_Node_Stmt_Return(
- new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('call_user_func'), [
- $function_transformer,
- new PHPParser_Node_Expr_Variable(VALUE_NAME),
- new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_Variable(TEMP_NAME),
- new PHPParser_Node_Scalar_String(ARGS_TEMP_NAME)
- ),
- new PHPParser_Node_Expr_Variable(CONT_NAME),
- $state->generateExceptParameter()
- ])
- )];
-
- // not a trampoline - this is the actual callback function that wraps the CPS callback - it takes parameters
- return new PHPParser_Node_Expr_Closure([
- 'uses' => [
- new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME, true),
- new PHPParser_Node_Expr_ClosureUse(TEMP_NAME)
- ],
- 'stmts' => array_merge(
- [
- new PHPParser_Node_Expr_Assign(
- new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_Variable(TEMP_NAME),
- new PHPParser_Node_Scalar_String(ARGS_TEMP_NAME)
- ),
- new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('func_get_args'))
- )
- ],
- generateTrampoline([
- new PHPParser_Node_Expr_Assign(new PHPParser_Node_Expr_Variable(VALUE_NAME), $function),
- new PHPParser_Node_Stmt_If(
- new PHPParser_Node_Expr_BooleanAnd(
- new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('is_string'), [new PHPParser_Node_Expr_Variable(VALUE_NAME)]),
- new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('array_key_exists'), [
- new PHPParser_Node_Expr_Variable(VALUE_NAME),
- new PHPParser_Node_Expr_StaticPropertyFetch(
- new PHPParser_Node_Name('CpsRuntime'),
- 'builtin_functions'
- )
- ])
- ),
- [
- 'stmts' => $builtin_call_stmts,
- 'else' => new PHPParser_Node_Stmt_Else($user_call_stmts)
- ]
- )
- ])
- )
- ]);
- }
- function generateFunctionCall($function, $args, $state) {
- if ($function instanceof PHPParser_Node_Scalar_String) {
- $function = new PHPParser_Node_Name($function->value);
- }
-
- $function_key = null;
- if ($function instanceof PHPParser_Node_Name && array_key_exists($function->toString(), CpsRuntime::$function_transforms)) {
- $function_key = $function->toString();
- }
-
-
- if ($function instanceof PHPParser_Node_Name) {
- if ($function_key) {
- return call_user_func(CpsRuntime::$function_transforms[$function_key], $function, $args, $state);
- }
- else {
- // if we have a null function key that means it's not a true builtin function
- // we need to check builtin list at runtime in case it is a "fast" function
- $callable_function = $function;
- $function = new PHPParser_Node_Scalar_String($function->toString());
- $assign_value = null;
- }
- }
- elseif ($function instanceof PHPParser_Node_Expr_Closure) {
- $stmts = call_user_func(CpsRuntime::$function_transforms[$function_key], new PHPParser_Node_Expr_Variable(VALUE_NAME), $args, $state);
- array_unshift($stmts, new PHPParser_Node_Expr_Assign(
- new PHPParser_Node_Expr_Variable(VALUE_NAME),
- $function
- ));
- return $stmts;
- }
- else {
- $assign_value = new PHPParser_Node_Expr_Assign(
- new PHPParser_Node_Expr_Variable(VALUE_NAME),
- $function
- );
- $function = new PHPParser_Node_Expr_Variable(VALUE_NAME);
- $callable_function = $function;
- }
-
- $user_call_stmts = call_user_func(CpsRuntime::$function_transforms[null], $callable_function, $args, $state);
- $function_transformer = new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_StaticPropertyFetch(
- new PHPParser_Node_Name('CpsRuntime'),
- 'builtin_functions'
- ),
- $function
- );
- $builtin_call_stmts = [new PHPParser_Node_Stmt_Return(
- new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('call_user_func'), [
- $function_transformer,
- $function,
- new PHPParser_Node_Expr_Array(array_map(function ($arg) {
- return new PHPParser_Node_Expr_ArrayItem($arg->value, null, isLValue($arg->value));
- }, $args)),
- new PHPParser_Node_Expr_Variable(CONT_NAME),
- $state->generateExceptParameter()
- ])
- )];
-
- $conditions = new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('array_key_exists'),
- [
- $function,
- new PHPParser_Node_Expr_StaticPropertyFetch(
- new PHPParser_Node_Name('CpsRuntime'),
- 'builtin_functions'
- )
- ]
- );
- if (!($function instanceof PHPParser_Node_Scalar_String)) {
- $conditions = new PHPParser_Node_Expr_BooleanAnd(
- new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('is_string'), [$function]),
- $conditions
- );
- }
-
- $stmts = [new PHPParser_Node_Stmt_If($conditions, [
- 'stmts' => $builtin_call_stmts,
- 'else' => new PHPParser_Node_Stmt_Else($user_call_stmts)
- ])];
-
- if ($assign_value) {
- array_unshift($stmts, $assign_value);
- }
-
- return $stmts;
- }
- function generateJump($label_num) {
- return generateThunk(
- [
- new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME),
- new PHPParser_Node_Expr_ClosureUse(TEMP_NAME, true),
- new PHPParser_Node_Expr_ClosureUse(LABELS_NAME, true)
- ],
- [new PHPParser_Node_Stmt_Return(
- new PHPParser_Node_Expr_FuncCall(
- new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_Variable(LABELS_NAME),
- new PHPParser_Node_Scalar_LNumber($label_num)
- ),
- [
- new PHPParser_Node_Arg(new PHPParser_Node_Expr_Variable(LOCALS_NAME)),
- new PHPParser_Node_Arg(new PHPParser_Node_Expr_Variable(TEMP_NAME))
- ]
- )
- )],
- 'jump'
- );
- }
- function generateParams($node_params, &$params, &$param_items) {
- $param_items = [];
- $params = [];
- $i = 0;
- foreach ($node_params as $param) {
- $param_name = PARAM_NAME . ($i++);
-
- $param_items[] = new PHPParser_Node_Expr_ArrayItem(
- new PHPParser_Node_Expr_Variable($param_name),
- new PHPParser_Node_Scalar_String($param->name),
- true
- );
-
- $params[] = new PHPParser_Node_Param($param_name, $param->default, $param->type, $param->byRef);
- }
- }
- class FunctionState {
- private $is_return_by_ref = false;
- private $block_num = 0;
- private $basic_blocks = [];
- private $basic_block_aliases = [];
- private $break_stack = [];
- private $continue_stack = [];
- private $label_names = [];
- private $catch_num = 0;
- private $catches = [];
- private $self = null;
- private $parent = null;
- private $builtin_methods = [];
- private $is_in_instance_method = false;
- function setIsInInstanceMethod($is_in_instance_method) {
- $this->is_in_instance_method = $is_in_instance_method;
- }
-
- function isInInstanceMethod() {
- return $this->is_in_instance_method;
- }
- function addBuiltinMethod($name) {
- $this->builtin_methods[$name] = true;
- }
- function generateBuiltinMethodDeclarations() {
- return [new PHPParser_Node_Expr_Assign(
- new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_StaticPropertyFetch(
- new PHPParser_Node_Name('CpsRuntime'),
- 'builtin_methods'
- ),
- new PHPParser_Node_Scalar_String($this->getSelf())
- ),
- new PHPParser_Node_Expr_Array(array_map(
- function($method_name) {
- return new PHPParser_Node_Expr_ArrayItem(
- new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('true')),
- new PHPParser_Node_Scalar_String($method_name)
- );
- },
- array_keys($this->builtin_methods)
- ))
- )];
- }
- function setSelf($self) {
- $this->self = $self;
- }
- function getSelf() {
- return $this->self;
- }
- function setParent($parent) {
- $this->parent = $parent;
- }
- function getParent() {
- return $this->parent;
- }
- function setIsReturnByRef($is_return_by_ref = true) {
- $this->is_return_by_ref = $is_return_by_ref;
- }
-
- function getIsReturnByRef() {
- return $this->is_return_by_ref;
- }
- function addBreakAndContinue($break_num, $continue_num) {
- $state = clone $this;
- $state->block_num =& $this->block_num;
- $state->basic_blocks =& $this->basic_blocks;
- $state->basic_block_aliases =& $this->basic_block_aliases;
- $state->label_names =& $this->label_names;
- $state->catch_num =& $this->catch_num;
- $state->catches =& $this->catches;
- array_unshift($state->break_stack, $break_num);
- array_unshift($state->continue_stack, $continue_num);
- return $state;
- }
-
- function getCatchNum() {
- return $this->catch_num;
- }
-
- function generateExceptParameter() {
- return new PHPParser_Node_Expr_Array([new PHPParser_Node_Expr_ArrayItem(
- new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_Variable(EXCEPT_NAME),
- new PHPParser_Node_Scalar_LNumber($this->catch_num)
- )
- )]);
- }
-
- function addCatches($catches) {
- $state = clone $this;
- $state->block_num =& $this->block_num;
- $state->basic_blocks =& $this->basic_blocks;
- $state->basic_block_aliases =& $this->basic_block_aliases;
- $state->label_names =& $this->label_names;
- $state->break_stack =& $this->break_stack;
- $state->continue_stack =& $this->continue_stack;
- $state->catches =& $this->catches;
- $state->catch_num = $this->catch_num + 1;
- $state->catches[$state->catch_num] = $catches;
- return $state;
- }
-
- function generateThrow($exception) {
- $stmts = [];
- $exception = assignToTemp($exception, $stmts);
- // trampoline
- $stmts[] = generateThunk(
- [
- new PHPParser_Node_Expr_ClosureUse(EXCEPT_NAME),
- new PHPParser_Node_Expr_ClosureUse(TEMP_NAME)
- ],
- [
- new PHPParser_Node_Stmt_Return(
- new PHPParser_Node_Expr_FuncCall(
- new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_Variable(EXCEPT_NAME),
- new PHPParser_Node_Scalar_LNumber($this->catch_num)
- ),
- [$exception]
- )
- )
- ],
- 'throw'
- );
- return $stmts;
- }
-
- function generateCatches() {
- $stmts = [];
- foreach ($this->catches as $catch_num => $catches) {
- $stmts[] = new PHPParser_Node_Expr_Assign(
- new PHPParser_Node_Expr_ArrayDimFetch(new PHPParser_Node_Expr_Variable(EXCEPT_NAME)),
- new PHPParser_Node_Expr_Closure([
- 'params' => [new PHPParser_Node_Param(VALUE_NAME)],
- 'uses' => [
- new PHPParser_Node_Expr_ClosureUse(CONT_NAME),
- new PHPParser_Node_Expr_ClosureUse(EXCEPT_NAME, true),
- new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME, true),
- new PHPParser_Node_Expr_ClosureUse(TEMP_NAME, true),
- new PHPParser_Node_Expr_ClosureUse(LABELS_NAME, true)
- ],
- 'stmts' => $catches
- ])
- );
- }
- return $stmts;
- }
-
- function generateBlockNum() {
- return $this->block_num++;
- }
-
- function addBasicBlock($block_num, $stmts) {
- if (count($stmts) == 1 &&
- ($return = $stmts[0]) instanceof PHPParser_Node_Stmt_Return &&
- ($closure = $return->expr) instanceof PHPParser_Node_Expr_Closure &&
- count($closure->stmts) == 1 &&
- ($return = $closure->stmts[0]) instanceof PHPParser_Node_Stmt_Return &&
- ($funccall = $return->expr) instanceof PHPParser_Node_Expr_FuncCall &&
- ($name = $funccall->name) instanceof PHPParser_Node_Expr_ArrayDimFetch &&
- ($var = $name->var) instanceof PHPParser_Node_Expr_Variable &&
- $var->name == LABELS_NAME)
- {
- // If all a basic block does is goto another basic block, point the basic
- // block pointer directly at the other block.
- $this->basic_block_aliases[$block_num] = $name->dim->value;
- }
- else {
- $this->basic_blocks[$block_num] = $stmts;
- }
- }
-
- function generateBasicBlocks() {
- $assignments = [];
- foreach ($this->basic_blocks as $num => $block) {
- $assignments[] = new PHPParser_Node_Expr_Assign(
- new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_Variable(LABELS_NAME),
- new PHPParser_Node_Scalar_LNumber($num)
- ),
- new PHPParser_Node_Expr_Closure([
- 'params' => [
- new PHPParser_Node_Param(LOCALS_NAME, null, null, true),
- new PHPParser_Node_Param(TEMP_NAME, null, null, true)
- ],
- 'uses' => [
- new PHPParser_Node_Expr_ClosureUse(CONT_NAME),
- new PHPParser_Node_Expr_ClosureUse(EXCEPT_NAME, true),
- new PHPParser_Node_Expr_ClosureUse(LABELS_NAME, true)
- ],
- 'stmts' => $block
- ])
- );
- }
- foreach ($this->basic_block_aliases as $num => $to) {
- while (array_key_exists($to, $this->basic_block_aliases)) {
- $to = $this->basic_block_aliases[$to]; // Fully resolve aliases
- }
- $assignments[] = new PHPParser_Node_Expr_Assign(
- new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_Variable(LABELS_NAME),
- new PHPParser_Node_Scalar_LNumber($num)
- ),
- new PHPParser_Node_Expr_ArrayDimFetch(
- new PHPParser_Node_Expr_Variable(LABELS_NAME),
- new PHPParser_Node_Scalar_LNumber($to)
- )
- );
- }
- return $assignments;
- }
-
- function addLabel($num, $name) {
- $this->label_names[$name] = $num;
- }
-
- function getLabel($label) {
- if (!array_key_exists($label, $this->label_names)) {
- throw new Exception('No such label: ' . $label);
- }
- return generateJump($this->label_names[$label]);
- }
-
- function getBreak($n = 1) {
- if (!$n) {
- $n = 1;
- }
- if ($n > count($this->break_stack)) {
- throw new Exception('Too many break levels');
- }
- return generateJump($this->break_stack[$n - 1]);
- }
-
- function getContinue($n = 1) {
- if (!$n) {
- $n = 1;
- }
- if ($n > count($this->continue_stack)) {
- throw new Exception('Too many continue levels');
- }
- return generateJump($this->continue_stack[$n - 1]);
- }
- }
- function traverseStatements($stmts, $final, $after_stmts, $state, $is_top_level = false) {
- if ($stmts === null) { // This is for functions in interfaces
- return $final(null, $state);
- }
- // Hoist functions (and classes, even though PHP doesn't exactly do so)
- $functions = [];
- $statements = [];
- foreach ($stmts as $stmt) {
- if ($stmt instanceof PHPParser_Node_Stmt_Function/* ||
- $stmt instanceof PHPParser_Node_Stmt_Class ||
- $stmt instanceof PHPParser_Node_Stmt_Interface*/)
- {
- $functions[] = $stmt;
- }
- else {
- $statements[] = $stmt;
- }
- }
- $stmts = array_merge($functions, $statements);
- return call_user_func($loop = function ($final, $stmts, $state) use (&$loop, $after_stmts) {
- if ($node = array_shift($stmts)) {
- return function () use ($node, $final, &$loop, $stmts, $state) {
- return traverseNode($node, function ($node_result, $final, $state) use (&$loop, $stmts) {
- return $loop(function ($future_result, $state) use ($node_result, $final, $stmts) {
- if (!isset($node_result)) {
- // do nothing
- }
- elseif (is_array($node_result)) {
- $future_result = array_merge($node_result, $future_result);
- }
- else {
- array_unshift($future_result, $node_result);
- }
- return $final($future_result, $state);
- }, $stmts, $state);
- }, $final, $state);
- };
- }
- else {
- return $final($after_stmts, $state);
- }
- }, function ($result, $state) use ($final, $is_top_level) {
- if ($is_top_level) {
- $result = array_merge($state->generateBasicBlocks(), $state->generateCatches(), $result);
- }
- return $final($result, $state);
- }, $stmts, $state);
- }
- // $next is a function($node_result, $final, $state), where $node_result is a node or array of nodes and $final is the new final step
- // $final is a function($result), where $result is an array of statements
- function traverseNode($node, $next, $final, $state) {
- if ($node instanceof PHPParser_Node_Scalar_LineConst) {
- return $next(new PHPParser_Node_Scalar_LNumber($node->line), $final, $state);
- }
- elseif ($node instanceof PHPParser_Node_Scalar_FileConst) {
- return $next(new PHPParser_Node_Scalar_String($GLOBALS['file']), $final, $state);
- }
- elseif ($node instanceof PHPParser_Node_Scalar_DirConst) {
- return $next(new PHPParser_Node_Scalar_String(dirname($GLOBALS['file'])), $final, $state);
- }
- elseif ($node instanceof PHPParser_Node_Scalar_Encapsed) {
- return call_user_func($loop = function ($parts, $compiled_parts, $final, $state) use (&$loop, $next) {
- if (count($parts)) {
- $part = array_shift($parts);
- return traverseNode($part, function ($part, $final, $state) use (&$loop, $parts, $compiled_parts) {
- $compiled_parts[] = $part;
- return $loop($parts, $compiled_parts, $final, $state);
- }, $final, $state);
- }
- else {
- return $next(new PHPParser_Node_Scalar_Encapsed($compiled_parts), $final, $state);
- }
- }, $node->parts, [], $final, $state);
- }
- elseif ($node instanceof PHPParser_Node_Expr_ConstFetch && $node->name->toString() == '__COMPILER_HALT_OFFSET__') {
- if (isset($GLOBALS['compiler_halt_offset'])) {
- $result = $GLOBALS['compiler_halt_offset'];
- }
- else {
- $result = new NodeReference($node);
- $GLOBALS['compiler_halt_offset_nodes'][] = $result;
- }
- return $next($result, $final, $state);
- }
- elseif ($node instanceof PHPParser_Node_Stmt_HaltCompiler) {
- $offset = new PHPParser_Node_Scalar_LNumber(strlen($GLOBALS['source']) - strlen($node->remaining));
- if (isset($GLOBALS['compiler_halt_offset_nodes'])) {
- foreach ($GLOBALS['compiler_halt_offset_nodes'] as $offset_node) {
- $offset_node->setNode($offset);
- }
- unset($GLOBALS['compiler_halt_offset_nodes']);
- }
- $GLOBALS['compiler_halt_offset'] = $offset;
- return $final(generateReturn(new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('null')), $state), $state);
- }
- elseif ($node instanceof PHPParser_Node_Stmt_Const) {
- return call_user_func($loop = function ($consts, $compiled_consts, $final, $state) use (&$loop, $next) {
- if ($const = array_shift($consts)) {
- return traverseNode($const->value, function ($const_value, $final, $state) use ($const, $consts, &$loop) {
- $compiled_consts[] = new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('define'), [
- new PHPParser_Node_Scalar_String($const->name),
- $const_value
- ]);
- return function () use (&$loop, $consts, $compiled_consts, $final, $state) {
- return $loop($consts, $compiled_consts, $final, $state);
- };
- }, $final, $state);
- }
- else {
- return $next($compiled_consts, $final, $state);
- }
- }, $node->consts, [], $final, $state);
- }
- elseif ($node instanceof PHPParser_Node_Expr_ConstFetch) {
- return $next($node, $final, $state);
- }
- elseif ($node === null || is_string($node) || $node instanceof PHPParser_Node_Scalar || $node instanceof PHPParser_Node_Name || $node instanceof PHPParser_Node_Stmt_InlineHTML) {
- return $next($node, $final, $state);
- }
- elseif ($node instanceof PHPParser_Node_Stmt_While) {
- $continue_num = $state->generateBlockNum();
- $break_num = $state->generateBlockNum();
-
- return traverseNode($node->cond, function ($cond, $final, $state) use ($node, $next, $continue_num, $break_num) {
- return traverseStatements($node->stmts, function ($while_body) use ($cond, $next, $continue_num, $break_num, $final, $state) {
- return $next(null, function ($after_while, $state) use ($break_num, $continue_num, $while_body, $cond, $final) {
- $state->addBasicBlock($break_num, $after_while);
-
- $while_body = [
- new PHPParser_Node_Stmt_If($cond, ['stmts' => $while_body]),
- generateJump($break_num)
- ];
-
- return $final($while_body, $state);
- }, $state);
- }, [generateJump($continue_num)], $state->addBreakAndContinue($break_num, $continue_num));
- }, function ($while_body, $state) use ($final, $continue_num) {
- $state->addBasicBlock($continue_num, $while_body);
- return $final([generateJump($continue_num)], $state);
- }, $state);
- }
- elseif ($node instanceof PHPParser_Node_Stmt_Do) {
- $continue_num = $state->generateBlockNum();
- $break_num = $state->generateBlockNum();
- $body_num = $state->generateBlockNum();
-
- return traverseNode($node->cond, function ($cond, $final, $state) use ($node, $next, $continue_num, $break_num, $body_num) {
- return traverseStatements($node->stmts, function ($do_body) use ($cond, $next, $continue_num, $break_num, $body_num, $final, $state) {
- $state->addBasicBlock($body_num, $do_body);
-
- return $next(null, function ($after_do, $state) use ($break_num, $continue_num, $body_num, $do_body, $cond, $final) {
- $state->addBasicBlock($break_num, $after_do);
-
- return $final([
- new PHPParser_Node_Stmt_If($cond,
- ['stmts' => [generateJump($body_num)]]
- ),
- generateJump($break_num)
- ], $state);
- }, $state);
- }, [generateJump($continue_num)], $state->addBreakAndContinue($break_num, $continue_num));
- }, function ($cond_block, $state) use ($final, $continue_num, $body_num) {
- $state->addBasicBlock($continue_num, $cond_block);
- return $final([generateJump($body_num)], $state);
- }, $state);
- }
- elseif ($node instanceof PHPParser_Node_Stmt_For) {
- $continue_num = $state->generateBlockNum();
- $break_num = $state->generateBlockNum();
-
- return traverseStatements($node->init, function ($init) use ($node, $continue_num, $break_num, $state, $next, $final) {
- $cond = $node->cond;
- $cond_var = generateTemp();
- if (count($cond)) {
- $cond[] = new PHPParser_Node_Expr_Assign($cond_var, array_pop($cond));
- }
-
- return call_user_func($cond_loop = function ($conds, $compiled_conds, $state) use (&$cond_loop, $node, $next, $final, $continue_num, $break_num, $init) {
- if ($cond = array_shift($conds)) {
- return traverseNode($cond, function ($cond, $final, $state) use (&$cond_loop, $conds, $compiled_conds) {
- $compiled_conds[] = $cond;
- return function () use (&$cond_loop, $conds, $compiled_conds, $state) {
- return $cond_loop($conds, $compiled_conds, $state);
- };
- }, $final, $state);
- }
- else {
- return traverseStatements($node->loop, function ($loop) use ($node, $state, $break_num, $continue_num, $compiled_conds, $next, $final, $init) {
- return traverseStatements($node->stmts, function ($for_body) use ($state, $continue_num, $break_num, $compiled_conds, $next, $final, $init) {
- if (count($compiled_conds)) {
- $last_cond = array_pop($compiled_conds);
- $for_body = array_merge($compiled_conds, [
- new PHPParser_Node_Stmt_If($last_cond, [
- 'stmts' => $for_body
- ])
- ]);
- }
- $for_body[] = generateJump($break_num);
- $state->addBasicBlock($continue_num, $for_body);
-
- return $next(null, function ($after_for, $state) use ($break_num, $final, $init) {
- $state->addBasicBlock($break_num, $after_for);
-
- return $final($init, $state);
- }, $state);
- }, $loop, $state->addBreakAndContinue($break_num, $continue_num));
- }, [generateJump($continue_num)], $state);
- }
- }, $node->cond, [], $state);
- }, [generateJump($continue_num)], $state);
- }
- elseif ($node instanceof PHPParser_Node_Stmt_Foreach) {
- // TODO handle non-array Traversables
- $continue_num = $state->generateBlockNum();
- $break_num = $state->generateBlockNum();
- return traverseNode($node->expr, function ($expr, $final, $state) use ($continue_num, $break_num, $node, $next) {
- $stmts = [];
- $temp = assignToTemp($expr, $stmts, $node->byRef);
- $stmts[] = new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('reset'), [$temp]);
-
- return traverseStatements($node->stmts, function ($foreach_body) use ($final, $state, $node, $next, $continue_num, $break_num, $stmts, $temp) {
- return traverseNode($node->keyVar, function ($keyVar, $final, $state) use ($foreach_body, $node, $next, $break_num, $temp) {
- return traverseNode($node->valueVar, function ($valueVar, $final, $state) use ($foreach_body, $keyVar, $next, $break_num, $temp) {
- return $next(null, function ($after_foreach, $state) use ($break_num, $foreach_body, $temp, $final, $keyVar, $valueVar) {
- $state->addBasicBlock($break_num, $after_foreach);
-
- $keyTemp = generateTemp();
-
- array_unshift($foreach_body, new PHPParser_Node_Expr_AssignRef(
- $valueVar,
- new PHPParser_Node_Expr_ArrayDimFetch($temp, $keyTemp)
- ));
- if ($keyVar) {
- array_unshift($foreach_body, new PHPParser_Node_Expr_Assign(
- $keyVar,
- $keyTemp
- ));
- }
-
- $foreach_body = [
- new PHPParser_Node_Stmt_If(
- new PHPParser_Node_Expr_AssignList(
- [$keyTemp, null],
- new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('each'), [$temp])
- ),
- ['stmts' => $foreach_body]
- ),
- generateJump($break_num)
- ];
-
- return $final($foreach_body, $state);
- }, $state);
- }, $final, $state);
- }, function ($foreach_body, $state) use ($final, $continue_num, $stmts) {
- $state->addBasicBlock($continue_num, $foreach_body);
- $stmts[] = generateJump($continue_num);
- return $final($stmts, $state);
- }, $state);
- }, [generateJump($continue_num)], $state->addBreakAndContinue($break_num, $continue_num));
- }, $final, $state);
- }
- elseif ($node instanceof PHPParser_Node_Stmt_If) {
- $done_num = $state->generateBlockNum();
-
- return traverseNode($node->cond, function ($cond, $final, $state) use ($node, $next, $done_num) {
- $next_num = $done_num;
- if (count($node->elseifs) > 0 || isset($node->else)) {
- $next_num = $state->generateBlockNum();
- }
- return traverseStatements($node->stmts, function ($if_body, $state) use ($cond, $next_num, $done_num, $final, $node) {
- $if_body = [
- new PHPParser_Node_Stmt_If($cond, ['stmts' => $if_body]),
- generateJump($next_num)
- ];
-
- // Handle zero or more elseif blocks
- $elseifs = $node->elseifs;
- $else = $node->else;
- return call_user_func($loop = function ($next_num, $elseifs, $final, $state) use (&$loop, $done_num, $else, $if_body) {
- if (isset($elseifs) && $elseif = array_shift($elseifs)) {
- $elseif_num = $next_num;
- $next_num = $done_num;
- if (count($elseifs) > 0 || isset($else)) {
- $next_num = $state->generateBlockNum();
- }
- return traverseNode($elseif->cond, function ($cond, $final, $state) use (&$loop, $elseif, $elseif_num, $next_num, $done_num, $elseifs) {
- return traverseStatements($elseif->stmts, function ($elseif_body, $state) use (&$loop, $elseif_num, $next_num, $cond, $elseifs, $final) {
- $elseif_body = [
- new PHPParser_Node_Stmt_If($cond, ['stmts' => $elseif_body]),
- generateJump($next_num)
- ];
- $state->addBasicBlock($elseif_num, $elseif_body);
- return function () use (&$loop, $next_num, $elseifs, $final, $state) {
- return $loop($next_num, $elseifs, $final, $state);
- };
- }, [generateJump($done_num)], $state);
- }, $final, $state);
- }
- elseif (isset($else)) {
- return traverseStatements($else->stmts, function ($else_body, $state) use ($next_num, $if_body, $final) {
- $state->addBasicBlock($next_num, $else_body);
- return $final($if_body, $state);
- }, [generateJump($done_num)], $state);
- }
- else {
- return $final($if_body, $state);
- }
- }, $next_num, $elseifs, $final, $state);
- }, [generateJump($done_num)], $state);
- }, function ($if_body, $state) use ($final, $done_num, $next) {
- return $next(null, function ($after_if, $state) use ($done_num, $final, $if_body) {
- $state->addBasicBlock($done_num, $after_if);
- return $final($if_body, $state);
- }, $state);
- }, $state);
- }
- elseif ($node instanceof PHPParser_Node_Stmt_Break) {
- $num = $node->num->value;
- return $next(null, function ($result, $new_state) use ($final, $state, $num) {
- return $final([$state->getBreak($num)], $new_state);
- }, $state);
- }
- elseif ($node instanceof PHPParser_Node_Stmt_Continue) {
- $num = $node->num->value;
- return $next(null, function ($result, $new_state) use ($final, $state, $num) {
- return $final([$state->getContinue($num)], $new_state);
- }, $state);
- }
- elseif ($node instanceof PHPParser_Node_Stmt_Label) {
- $label_num = $state->generateBlockNum();
- $state->addLabel($label_num, $node->name);
- return $next(null, function ($after_label, $state) use ($label_num, $final) {
- $state->addBasicBlock($label_num, $after_label);
- return $final([generateJump($label_num)], $state);
- }, $state);
- }
- elseif ($node instanceof PHPParser_Node_Stmt_Goto) {
- $name = $node->name;
- return $next(null, function ($after_goto, $state) use ($final, $name) {
- return $final([$state->getLabel($name)], $state);
- }, $state);
- }
- elseif ($node instanceof PHPParser_Node_Expr_Exit) {
- return traverseNode($node->expr, function ($expr, $final, $state) use ($next) {
- return $next(null, function ($stmts_x, $state_x) use ($expr, $state, $final) {
- // Executable statements after exit() are not reached, but we run the compiler over them anyway.
- return $final([new PHPParser_node_Expr_Exit($expr)], $state);
- }, $state);
- }, $final, $state);
- }
- elseif ($node instanceof PHPParser_Node_Stmt_Return) {
- return traverseNode($node->expr, new ReturnClosure(function ($expr, $final, $state) use ($next) {
- return $next(null, function ($stmts_x, $state_x) use ($expr, $state, $final) {
- // Executable statements after a return are not reached, but we run the compiler over them in case doing so has side effects internal to the compiler (like __halt_compiler())
- return $final(generateReturn($expr, $state), $state);
- }, $state);
- }), $final, $state);
- }
- elseif ($node instanceof PHPParser_Node_Expr_FuncCall) {
- return generateContinuation($next, function ($continuation, $state) use ($node, $final) {
- return traverseNode($node->name, funct…
Large files files are truncated, but you can click here to view the full file