PageRenderTime 38ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/cps.php

https://github.com/lucciano/eventerator
PHP | 2013 lines | 1828 code | 138 blank | 47 comment | 140 complexity | 846830efa71fab3193a9f7aa3c2aca99 MD5 | raw file
  1. <?php
  2. // TODO
  3. // static variables
  4. // line number mappings in generated code (comments)
  5. // traits
  6. // namespaces
  7. // break and continue by non-const number (include array of basic block IDs in compiled code)
  8. // pass down break and continue stack when including to permit break in top level of an included file
  9. // Continuation passing style transformation for PHP
  10. require_once 'PHP-Parser/lib/bootstrap.php';
  11. require_once 'print.php';
  12. require_once 'runtime.php';
  13. require_once 'compiler_plugins.php';
  14. const TEMP_NAME = 't';
  15. const GLOBALS_TEMP_NAME = 'G';
  16. const ARGS_TEMP_NAME = 'A';
  17. const CONT_NAME = 'c';
  18. const LOCALS_NAME = 'l';
  19. const LABELS_NAME = 'g';
  20. const VALUE_NAME = 'v';
  21. const VALUE_REF_NAME = 'r';
  22. const VALUE_IS_REF_NAME = 'q';
  23. const JUNK_NAME = 'j';
  24. const PARAM_NAME = 'p';
  25. const USES_NAME = 'u';
  26. const EXCEPT_NAME = 'x';
  27. const STATICS_NAME = 's';
  28. $SUPERGLOBALS = ['GLOBALS', '_SERVER', '_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_REQUEST', '_ENV'];
  29. $MAGIC_METHODS = ['__construct', '__set', '__get', '__destruct', '__sleep', '__wakeup', '__toString', '__isset', '__unset', '__call', '__callStatic', '__set_state', '__clone'];
  30. function config($name, $value = null) {
  31. static $configs = [];
  32. if (isset($value)) {
  33. $configs[$name] = $value;
  34. }
  35. else {
  36. if (array_key_exists($name, $configs)) {
  37. return $configs[$name];
  38. }
  39. }
  40. }
  41. config('disable_strict_logical_operator_type', false);
  42. config('disable_func_get_args', false);
  43. config('trampoline_max_stack', 40); // optimum seems to be around 40 or 50
  44. $interpreter = $argv[1];
  45. $compiler = $interpreter . ' ' . __FILE__ . ' "' . $interpreter . '"';
  46. $file = $argv[3];
  47. $source = file_get_contents('php://stdin');
  48. $compiled = compile($source);
  49. switch ($mode = $argv[2]) {
  50. case 'main':
  51. echo "<?php\nrequire_once('" . dirname(__FILE__) . "/runtime.php');\n\$l=&\$GLOBALS;\n\$t=['" . GLOBALS_TEMP_NAME . "'=>&\$GLOBALS];\n\$g=[];\n";
  52. printStatements(generateTrampoline($compiled));
  53. echo "\n";
  54. break;
  55. case 'include':
  56. echo "<?php\n";
  57. printStatements($compiled);
  58. break;
  59. default:
  60. throw new Exception('unknown mode ' . $mode);
  61. break;
  62. }
  63. // A container for a single parser node reference
  64. class NodeReference extends PHPParser_Node_Expr {
  65. function __construct(PHPParser_Node_Expr $node) {
  66. $this->node = $node;
  67. }
  68. function getNode() {
  69. return $this->node;
  70. }
  71. function setNode(PHPParser_Node_Expr $node) {
  72. $this->node = $node;
  73. }
  74. }
  75. // Wrapper for a closure if it represents a return from a function.
  76. // This is used to determine whether a given continuation represents
  77. // a tail call that can be optimized away.
  78. class ReturnClosure {
  79. private $closure;
  80. function __construct($closure) {
  81. $this->closure = $closure;
  82. }
  83. function __invoke() {
  84. return call_user_func_array($this->closure, func_get_args());
  85. }
  86. }
  87. function generateDefaultTemps() {
  88. $temps = [];
  89. $temps[] = new PHPParser_Node_Expr_ArrayItem(
  90. new PHPParser_Node_Expr_Variable('GLOBALS'),
  91. new PHPParser_Node_Scalar_String(GLOBALS_TEMP_NAME),
  92. true
  93. );
  94. if (!config('disable_func_get_args')) {
  95. $temps[] = new PHPParser_Node_Expr_ArrayItem(
  96. new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('array_slice'), [
  97. new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('func_get_args'), []),
  98. new PHPParser_Node_Scalar_LNumber(2)
  99. ]),
  100. new PHPParser_Node_Scalar_String(ARGS_TEMP_NAME)
  101. );
  102. }
  103. return new PHPParser_Node_Expr_Array($temps);
  104. }
  105. function generateTemp() {
  106. static $temp_counter = 0;
  107. return new PHPParser_Node_Expr_ArrayDimFetch(
  108. new PHPParser_Node_Expr_Variable(TEMP_NAME),
  109. new PHPParser_Node_Scalar_LNumber($temp_counter++)
  110. );
  111. }
  112. // Assign an expression to a temp or inline simple variable names.
  113. // Appends an assignment statement to $stmts if necessary.
  114. // Returns the name by which the expression will be known.
  115. function assignToTemp($expr, &$stmts, $byRef = false) {
  116. $temp = generateTemp();
  117. $assign = $byRef ? 'PHPParser_Node_Expr_AssignRef' : 'PHPParser_Node_Expr_Assign';
  118. $stmts[] = new $assign($temp, $expr);
  119. return $temp;
  120. }
  121. // Generate the expression to convert an expression into && compatible values (true, false, 1, 0, depending on value).
  122. function boolifyLogicalOperator($expr) {
  123. // If one does not depend in a === sense on the result of &&, and, ||, or being true vs. 1 vs. truthy value, then
  124. // we cut down a bit of bloat by configurably turning this into the identity function.
  125. if (config('disable_strict_logical_operator_type')) return $expr;
  126. $temp = generateTemp();
  127. return new PHPParser_Node_Expr_Ternary(
  128. new PHPParser_Node_Expr_FuncCall(
  129. new PHPParser_Node_Name('is_bool'),
  130. [new PHPParser_Node_Expr_Assign($temp, $expr)]
  131. ),
  132. $temp,
  133. new PHPParser_Node_Expr_Ternary(
  134. $temp,
  135. new PHPParser_Node_Scalar_LNumber(1),
  136. new PHPParser_Node_Scalar_LNumber(0)
  137. )
  138. );
  139. }
  140. function generateContinuation($next, $cont, $state) {
  141. if ($next instanceof ReturnClosure) {
  142. return $cont(new PHPParser_Node_Expr_Variable(CONT_NAME), $state);
  143. }
  144. $temp = generateTemp();
  145. return $next($temp, function ($result, $state) use ($cont, $temp) {
  146. $result = array_merge([
  147. new PHPParser_Node_Expr_Ternary(
  148. new PHPParser_Node_Expr_Variable(VALUE_IS_REF_NAME),
  149. new PHPParser_Node_Expr_AssignRef($temp, new PHPParser_Node_Expr_Variable(VALUE_REF_NAME)),
  150. new PHPParser_Node_Expr_Assign($temp, new PHPParser_Node_Expr_Variable(VALUE_NAME))
  151. ),
  152. new PHPParser_Node_Expr_Assign(
  153. new PHPParser_Node_Expr_Variable(LOCALS_NAME),
  154. new PHPParser_Node_Expr_Variable(LOCALS_NAME)
  155. )
  156. ], $result);
  157. return $cont(new PHPParser_Node_Expr_Closure([
  158. 'params' => [
  159. new PHPParser_Node_Param(VALUE_NAME),
  160. new PHPParser_Node_Param(VALUE_REF_NAME, new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('null')), null, true),
  161. new PHPParser_Node_Param(VALUE_IS_REF_NAME, new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('false')))
  162. ],
  163. 'uses' => [
  164. new PHPParser_Node_Expr_ClosureUse(CONT_NAME),
  165. new PHPParser_Node_Expr_ClosureUse(EXCEPT_NAME),
  166. new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME, true),
  167. new PHPParser_Node_Expr_ClosureUse(TEMP_NAME, true),
  168. new PHPParser_Node_Expr_ClosureUse(LABELS_NAME, true)
  169. ],
  170. 'stmts' => $result
  171. ]), $state);
  172. }, $state);
  173. }
  174. // When using generateContinuation, the $final parameter for the first traverseNode will
  175. // likely need to be the result of this function.
  176. function generateFinalForContinuation($final, $continuation) {
  177. return function ($result, $state) use ($final, $continuation) {
  178. if (!($continuation instanceof PHPParser_Node_Expr_Variable) || $continuation->name != CONT_NAME) {
  179. array_unshift($result, new PHPParser_Node_Expr_Assign(
  180. new PHPParser_Node_Expr_Variable(CONT_NAME),
  181. $continuation
  182. ));
  183. }
  184. return $final($result, $state);
  185. };
  186. }
  187. function isLValue($expr) {
  188. return ($expr instanceof PHPParser_Node_Expr_Variable ||
  189. $expr instanceof PHPParser_Node_Expr_ArrayDimFetch ||
  190. $expr instanceof PHPParser_Node_Expr_FuncCall ||
  191. $expr instanceof PHPParser_Node_Expr_MethodCall ||
  192. $expr instanceof PHPParser_Node_Expr_PropertyFetch ||
  193. $expr instanceof PHPParser_Node_Expr_StaticCall ||
  194. $expr instanceof PHPParser_Node_Expr_StaticPropertyFetch);
  195. }
  196. // When you need to call the current continuation with a return value, this function
  197. // will generate the return/call to emit.
  198. function generateReturn($value, $state) {
  199. if (!isset($value)) {
  200. $value = new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('null'));
  201. }
  202. $is_return_by_ref = $state ? $state->getIsReturnByRef() : false;
  203. $is_return_by_ref &= isLValue($value); // Don't try to return rvalue by ref. Simulates PHP behavior on return by reference.
  204. return [generateThunk(
  205. [
  206. new PHPParser_Node_Expr_ClosureUse(CONT_NAME),
  207. new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME, true),
  208. new PHPParser_Node_Expr_ClosureUse(TEMP_NAME, true)
  209. ],
  210. [new PHPParser_Node_Stmt_Return(
  211. new PHPParser_Node_Expr_FuncCall(
  212. new PHPParser_Node_Expr_Variable(CONT_NAME),
  213. $is_return_by_ref ?
  214. [
  215. new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('null')),
  216. new PHPParser_Node_Arg($value),
  217. new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('true'))
  218. ]
  219. :
  220. [new PHPParser_Node_Arg($value)]
  221. )
  222. )],
  223. 'return'
  224. )];
  225. }
  226. function generateTryCatchCall($expr, $state) {
  227. return new PHPParser_Node_Stmt_TryCatch(
  228. [new PHPParser_Node_Stmt_Return(
  229. new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Expr_Variable(CONT_NAME), [
  230. $expr
  231. ])
  232. )],
  233. [new PHPParser_Node_Stmt_Catch(
  234. new PHPParser_Node_Name('Exception'),
  235. VALUE_NAME,
  236. [new PHPParser_Node_Stmt_Return(
  237. new PHPParser_Node_Expr_FuncCall(
  238. new PHPParser_Node_Expr_ArrayDimFetch(
  239. new PHPParser_Node_Expr_Variable(EXCEPT_NAME),
  240. new PHPParser_Node_Scalar_LNumber($state->getCatchNum())
  241. ),
  242. [new PHPParser_Node_Expr_Variable(VALUE_NAME)]
  243. )
  244. )]
  245. )]
  246. );
  247. }
  248. function generateMethodCall($object, $function, $args, $type, $state) {
  249. $is_builtin_call = in_array($function, $GLOBALS['MAGIC_METHODS']);
  250. $get_class = new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('get_class'), [$object]);
  251. if ($type == PHPParser_Node_Expr_StaticCall) {
  252. if ($object->toString() == 'self') {
  253. $object = new PHPParser_Node_Name($state->getSelf());
  254. }
  255. elseif ($object->toString() == 'parent') {
  256. $object = new PHPParser_Node_Name($state->getParent());
  257. }
  258. $get_class = new PHPParser_Node_Scalar_String($object->toString());
  259. if (isset(CpsRuntime::$builtin_methods[$object->toString()][$function])) {
  260. $is_builtin_call = true;
  261. }
  262. }
  263. if ($is_builtin_call) {
  264. $stmt = generateTryCatchCall(new $type($object, $function, $args), $state);
  265. }
  266. else {
  267. $orig_args = $args;
  268. array_unshift($args, $state->generateExceptParameter());
  269. array_unshift($args, new PHPParser_Node_Expr_Variable(CONT_NAME));
  270. $stmt = new PHPParser_Node_Stmt_If(
  271. new PHPParser_Node_Expr_Isset([
  272. new PHPParser_Node_Expr_ArrayDimFetch(
  273. new PHPParser_Node_Expr_ArrayDimFetch(
  274. new PHPParser_Node_Expr_StaticPropertyFetch(
  275. new PHPParser_Node_Name('CpsRuntime'),
  276. 'builtin_methods'
  277. ),
  278. $get_class
  279. ),
  280. new PHPParser_Node_Scalar_String($function)
  281. )
  282. ]),
  283. [
  284. 'stmts' => [
  285. generateTryCatchCall(new $type($object, $function, $orig_args), $state)
  286. ],
  287. 'else' => new PHPParser_Node_Stmt_Else([
  288. new PHPParser_Node_Stmt_Return(new $type($object, $function, $args))
  289. ])
  290. ]
  291. );
  292. }
  293. return [generateThunk(
  294. [
  295. new PHPParser_Node_Expr_ClosureUse(CONT_NAME),
  296. new PHPParser_Node_Expr_ClosureUse(EXCEPT_NAME),
  297. new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME, true),
  298. new PHPParser_Node_Expr_ClosureUse(TEMP_NAME, true)
  299. ],
  300. [$stmt],
  301. 'call'
  302. )];
  303. }
  304. function wrapFunctionForCallback($function, $state) {
  305. $user_call_stmts = [new PHPParser_Node_Stmt_Return(
  306. new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('call_user_func_array'), [
  307. new PHPParser_Node_Expr_Variable(VALUE_NAME),
  308. new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('array_merge'), [
  309. new PHPParser_Node_Expr_Array([
  310. new PHPParser_Node_Expr_Variable(CONT_NAME),
  311. new PHPParser_Node_Expr_Variable(EXCEPT_NAME)
  312. ]),
  313. new PHPParser_Node_Expr_ArrayDimFetch(
  314. new PHPParser_Node_Expr_Variable(TEMP_NAME),
  315. new PHPParser_Node_Scalar_String(ARGS_TEMP_NAME)
  316. )
  317. ])
  318. ])
  319. )];
  320. $function_transformer = new PHPParser_Node_Expr_ArrayDimFetch(
  321. new PHPParser_Node_Expr_StaticPropertyFetch(
  322. new PHPParser_Node_Name('CpsRuntime'),
  323. 'builtin_functions'
  324. ),
  325. new PHPParser_Node_Expr_Variable(VALUE_NAME)
  326. );
  327. $builtin_call_stmts = [new PHPParser_Node_Stmt_Return(
  328. new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('call_user_func'), [
  329. $function_transformer,
  330. new PHPParser_Node_Expr_Variable(VALUE_NAME),
  331. new PHPParser_Node_Expr_ArrayDimFetch(
  332. new PHPParser_Node_Expr_Variable(TEMP_NAME),
  333. new PHPParser_Node_Scalar_String(ARGS_TEMP_NAME)
  334. ),
  335. new PHPParser_Node_Expr_Variable(CONT_NAME),
  336. $state->generateExceptParameter()
  337. ])
  338. )];
  339. // not a trampoline - this is the actual callback function that wraps the CPS callback - it takes parameters
  340. return new PHPParser_Node_Expr_Closure([
  341. 'uses' => [
  342. new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME, true),
  343. new PHPParser_Node_Expr_ClosureUse(TEMP_NAME)
  344. ],
  345. 'stmts' => array_merge(
  346. [
  347. new PHPParser_Node_Expr_Assign(
  348. new PHPParser_Node_Expr_ArrayDimFetch(
  349. new PHPParser_Node_Expr_Variable(TEMP_NAME),
  350. new PHPParser_Node_Scalar_String(ARGS_TEMP_NAME)
  351. ),
  352. new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('func_get_args'))
  353. )
  354. ],
  355. generateTrampoline([
  356. new PHPParser_Node_Expr_Assign(new PHPParser_Node_Expr_Variable(VALUE_NAME), $function),
  357. new PHPParser_Node_Stmt_If(
  358. new PHPParser_Node_Expr_BooleanAnd(
  359. new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('is_string'), [new PHPParser_Node_Expr_Variable(VALUE_NAME)]),
  360. new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('array_key_exists'), [
  361. new PHPParser_Node_Expr_Variable(VALUE_NAME),
  362. new PHPParser_Node_Expr_StaticPropertyFetch(
  363. new PHPParser_Node_Name('CpsRuntime'),
  364. 'builtin_functions'
  365. )
  366. ])
  367. ),
  368. [
  369. 'stmts' => $builtin_call_stmts,
  370. 'else' => new PHPParser_Node_Stmt_Else($user_call_stmts)
  371. ]
  372. )
  373. ])
  374. )
  375. ]);
  376. }
  377. function generateFunctionCall($function, $args, $state) {
  378. if ($function instanceof PHPParser_Node_Scalar_String) {
  379. $function = new PHPParser_Node_Name($function->value);
  380. }
  381. $function_key = null;
  382. if ($function instanceof PHPParser_Node_Name && array_key_exists($function->toString(), CpsRuntime::$function_transforms)) {
  383. $function_key = $function->toString();
  384. }
  385. if ($function instanceof PHPParser_Node_Name) {
  386. if ($function_key) {
  387. return call_user_func(CpsRuntime::$function_transforms[$function_key], $function, $args, $state);
  388. }
  389. else {
  390. // if we have a null function key that means it's not a true builtin function
  391. // we need to check builtin list at runtime in case it is a "fast" function
  392. $callable_function = $function;
  393. $function = new PHPParser_Node_Scalar_String($function->toString());
  394. $assign_value = null;
  395. }
  396. }
  397. elseif ($function instanceof PHPParser_Node_Expr_Closure) {
  398. $stmts = call_user_func(CpsRuntime::$function_transforms[$function_key], new PHPParser_Node_Expr_Variable(VALUE_NAME), $args, $state);
  399. array_unshift($stmts, new PHPParser_Node_Expr_Assign(
  400. new PHPParser_Node_Expr_Variable(VALUE_NAME),
  401. $function
  402. ));
  403. return $stmts;
  404. }
  405. else {
  406. $assign_value = new PHPParser_Node_Expr_Assign(
  407. new PHPParser_Node_Expr_Variable(VALUE_NAME),
  408. $function
  409. );
  410. $function = new PHPParser_Node_Expr_Variable(VALUE_NAME);
  411. $callable_function = $function;
  412. }
  413. $user_call_stmts = call_user_func(CpsRuntime::$function_transforms[null], $callable_function, $args, $state);
  414. $function_transformer = new PHPParser_Node_Expr_ArrayDimFetch(
  415. new PHPParser_Node_Expr_StaticPropertyFetch(
  416. new PHPParser_Node_Name('CpsRuntime'),
  417. 'builtin_functions'
  418. ),
  419. $function
  420. );
  421. $builtin_call_stmts = [new PHPParser_Node_Stmt_Return(
  422. new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('call_user_func'), [
  423. $function_transformer,
  424. $function,
  425. new PHPParser_Node_Expr_Array(array_map(function ($arg) {
  426. return new PHPParser_Node_Expr_ArrayItem($arg->value, null, isLValue($arg->value));
  427. }, $args)),
  428. new PHPParser_Node_Expr_Variable(CONT_NAME),
  429. $state->generateExceptParameter()
  430. ])
  431. )];
  432. $conditions = new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('array_key_exists'),
  433. [
  434. $function,
  435. new PHPParser_Node_Expr_StaticPropertyFetch(
  436. new PHPParser_Node_Name('CpsRuntime'),
  437. 'builtin_functions'
  438. )
  439. ]
  440. );
  441. if (!($function instanceof PHPParser_Node_Scalar_String)) {
  442. $conditions = new PHPParser_Node_Expr_BooleanAnd(
  443. new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('is_string'), [$function]),
  444. $conditions
  445. );
  446. }
  447. $stmts = [new PHPParser_Node_Stmt_If($conditions, [
  448. 'stmts' => $builtin_call_stmts,
  449. 'else' => new PHPParser_Node_Stmt_Else($user_call_stmts)
  450. ])];
  451. if ($assign_value) {
  452. array_unshift($stmts, $assign_value);
  453. }
  454. return $stmts;
  455. }
  456. function generateJump($label_num) {
  457. return generateThunk(
  458. [
  459. new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME),
  460. new PHPParser_Node_Expr_ClosureUse(TEMP_NAME, true),
  461. new PHPParser_Node_Expr_ClosureUse(LABELS_NAME, true)
  462. ],
  463. [new PHPParser_Node_Stmt_Return(
  464. new PHPParser_Node_Expr_FuncCall(
  465. new PHPParser_Node_Expr_ArrayDimFetch(
  466. new PHPParser_Node_Expr_Variable(LABELS_NAME),
  467. new PHPParser_Node_Scalar_LNumber($label_num)
  468. ),
  469. [
  470. new PHPParser_Node_Arg(new PHPParser_Node_Expr_Variable(LOCALS_NAME)),
  471. new PHPParser_Node_Arg(new PHPParser_Node_Expr_Variable(TEMP_NAME))
  472. ]
  473. )
  474. )],
  475. 'jump'
  476. );
  477. }
  478. function generateParams($node_params, &$params, &$param_items) {
  479. $param_items = [];
  480. $params = [];
  481. $i = 0;
  482. foreach ($node_params as $param) {
  483. $param_name = PARAM_NAME . ($i++);
  484. $param_items[] = new PHPParser_Node_Expr_ArrayItem(
  485. new PHPParser_Node_Expr_Variable($param_name),
  486. new PHPParser_Node_Scalar_String($param->name),
  487. true
  488. );
  489. $params[] = new PHPParser_Node_Param($param_name, $param->default, $param->type, $param->byRef);
  490. }
  491. }
  492. class FunctionState {
  493. private $is_return_by_ref = false;
  494. private $block_num = 0;
  495. private $basic_blocks = [];
  496. private $basic_block_aliases = [];
  497. private $break_stack = [];
  498. private $continue_stack = [];
  499. private $label_names = [];
  500. private $catch_num = 0;
  501. private $catches = [];
  502. private $self = null;
  503. private $parent = null;
  504. private $builtin_methods = [];
  505. private $is_in_instance_method = false;
  506. function setIsInInstanceMethod($is_in_instance_method) {
  507. $this->is_in_instance_method = $is_in_instance_method;
  508. }
  509. function isInInstanceMethod() {
  510. return $this->is_in_instance_method;
  511. }
  512. function addBuiltinMethod($name) {
  513. $this->builtin_methods[$name] = true;
  514. }
  515. function generateBuiltinMethodDeclarations() {
  516. return [new PHPParser_Node_Expr_Assign(
  517. new PHPParser_Node_Expr_ArrayDimFetch(
  518. new PHPParser_Node_Expr_StaticPropertyFetch(
  519. new PHPParser_Node_Name('CpsRuntime'),
  520. 'builtin_methods'
  521. ),
  522. new PHPParser_Node_Scalar_String($this->getSelf())
  523. ),
  524. new PHPParser_Node_Expr_Array(array_map(
  525. function($method_name) {
  526. return new PHPParser_Node_Expr_ArrayItem(
  527. new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('true')),
  528. new PHPParser_Node_Scalar_String($method_name)
  529. );
  530. },
  531. array_keys($this->builtin_methods)
  532. ))
  533. )];
  534. }
  535. function setSelf($self) {
  536. $this->self = $self;
  537. }
  538. function getSelf() {
  539. return $this->self;
  540. }
  541. function setParent($parent) {
  542. $this->parent = $parent;
  543. }
  544. function getParent() {
  545. return $this->parent;
  546. }
  547. function setIsReturnByRef($is_return_by_ref = true) {
  548. $this->is_return_by_ref = $is_return_by_ref;
  549. }
  550. function getIsReturnByRef() {
  551. return $this->is_return_by_ref;
  552. }
  553. function addBreakAndContinue($break_num, $continue_num) {
  554. $state = clone $this;
  555. $state->block_num =& $this->block_num;
  556. $state->basic_blocks =& $this->basic_blocks;
  557. $state->basic_block_aliases =& $this->basic_block_aliases;
  558. $state->label_names =& $this->label_names;
  559. $state->catch_num =& $this->catch_num;
  560. $state->catches =& $this->catches;
  561. array_unshift($state->break_stack, $break_num);
  562. array_unshift($state->continue_stack, $continue_num);
  563. return $state;
  564. }
  565. function getCatchNum() {
  566. return $this->catch_num;
  567. }
  568. function generateExceptParameter() {
  569. return new PHPParser_Node_Expr_Array([new PHPParser_Node_Expr_ArrayItem(
  570. new PHPParser_Node_Expr_ArrayDimFetch(
  571. new PHPParser_Node_Expr_Variable(EXCEPT_NAME),
  572. new PHPParser_Node_Scalar_LNumber($this->catch_num)
  573. )
  574. )]);
  575. }
  576. function addCatches($catches) {
  577. $state = clone $this;
  578. $state->block_num =& $this->block_num;
  579. $state->basic_blocks =& $this->basic_blocks;
  580. $state->basic_block_aliases =& $this->basic_block_aliases;
  581. $state->label_names =& $this->label_names;
  582. $state->break_stack =& $this->break_stack;
  583. $state->continue_stack =& $this->continue_stack;
  584. $state->catches =& $this->catches;
  585. $state->catch_num = $this->catch_num + 1;
  586. $state->catches[$state->catch_num] = $catches;
  587. return $state;
  588. }
  589. function generateThrow($exception) {
  590. $stmts = [];
  591. $exception = assignToTemp($exception, $stmts);
  592. // trampoline
  593. $stmts[] = generateThunk(
  594. [
  595. new PHPParser_Node_Expr_ClosureUse(EXCEPT_NAME),
  596. new PHPParser_Node_Expr_ClosureUse(TEMP_NAME)
  597. ],
  598. [
  599. new PHPParser_Node_Stmt_Return(
  600. new PHPParser_Node_Expr_FuncCall(
  601. new PHPParser_Node_Expr_ArrayDimFetch(
  602. new PHPParser_Node_Expr_Variable(EXCEPT_NAME),
  603. new PHPParser_Node_Scalar_LNumber($this->catch_num)
  604. ),
  605. [$exception]
  606. )
  607. )
  608. ],
  609. 'throw'
  610. );
  611. return $stmts;
  612. }
  613. function generateCatches() {
  614. $stmts = [];
  615. foreach ($this->catches as $catch_num => $catches) {
  616. $stmts[] = new PHPParser_Node_Expr_Assign(
  617. new PHPParser_Node_Expr_ArrayDimFetch(new PHPParser_Node_Expr_Variable(EXCEPT_NAME)),
  618. new PHPParser_Node_Expr_Closure([
  619. 'params' => [new PHPParser_Node_Param(VALUE_NAME)],
  620. 'uses' => [
  621. new PHPParser_Node_Expr_ClosureUse(CONT_NAME),
  622. new PHPParser_Node_Expr_ClosureUse(EXCEPT_NAME, true),
  623. new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME, true),
  624. new PHPParser_Node_Expr_ClosureUse(TEMP_NAME, true),
  625. new PHPParser_Node_Expr_ClosureUse(LABELS_NAME, true)
  626. ],
  627. 'stmts' => $catches
  628. ])
  629. );
  630. }
  631. return $stmts;
  632. }
  633. function generateBlockNum() {
  634. return $this->block_num++;
  635. }
  636. function addBasicBlock($block_num, $stmts) {
  637. if (count($stmts) == 1 &&
  638. ($return = $stmts[0]) instanceof PHPParser_Node_Stmt_Return &&
  639. ($closure = $return->expr) instanceof PHPParser_Node_Expr_Closure &&
  640. count($closure->stmts) == 1 &&
  641. ($return = $closure->stmts[0]) instanceof PHPParser_Node_Stmt_Return &&
  642. ($funccall = $return->expr) instanceof PHPParser_Node_Expr_FuncCall &&
  643. ($name = $funccall->name) instanceof PHPParser_Node_Expr_ArrayDimFetch &&
  644. ($var = $name->var) instanceof PHPParser_Node_Expr_Variable &&
  645. $var->name == LABELS_NAME)
  646. {
  647. // If all a basic block does is goto another basic block, point the basic
  648. // block pointer directly at the other block.
  649. $this->basic_block_aliases[$block_num] = $name->dim->value;
  650. }
  651. else {
  652. $this->basic_blocks[$block_num] = $stmts;
  653. }
  654. }
  655. function generateBasicBlocks() {
  656. $assignments = [];
  657. foreach ($this->basic_blocks as $num => $block) {
  658. $assignments[] = new PHPParser_Node_Expr_Assign(
  659. new PHPParser_Node_Expr_ArrayDimFetch(
  660. new PHPParser_Node_Expr_Variable(LABELS_NAME),
  661. new PHPParser_Node_Scalar_LNumber($num)
  662. ),
  663. new PHPParser_Node_Expr_Closure([
  664. 'params' => [
  665. new PHPParser_Node_Param(LOCALS_NAME, null, null, true),
  666. new PHPParser_Node_Param(TEMP_NAME, null, null, true)
  667. ],
  668. 'uses' => [
  669. new PHPParser_Node_Expr_ClosureUse(CONT_NAME),
  670. new PHPParser_Node_Expr_ClosureUse(EXCEPT_NAME, true),
  671. new PHPParser_Node_Expr_ClosureUse(LABELS_NAME, true)
  672. ],
  673. 'stmts' => $block
  674. ])
  675. );
  676. }
  677. foreach ($this->basic_block_aliases as $num => $to) {
  678. while (array_key_exists($to, $this->basic_block_aliases)) {
  679. $to = $this->basic_block_aliases[$to]; // Fully resolve aliases
  680. }
  681. $assignments[] = new PHPParser_Node_Expr_Assign(
  682. new PHPParser_Node_Expr_ArrayDimFetch(
  683. new PHPParser_Node_Expr_Variable(LABELS_NAME),
  684. new PHPParser_Node_Scalar_LNumber($num)
  685. ),
  686. new PHPParser_Node_Expr_ArrayDimFetch(
  687. new PHPParser_Node_Expr_Variable(LABELS_NAME),
  688. new PHPParser_Node_Scalar_LNumber($to)
  689. )
  690. );
  691. }
  692. return $assignments;
  693. }
  694. function addLabel($num, $name) {
  695. $this->label_names[$name] = $num;
  696. }
  697. function getLabel($label) {
  698. if (!array_key_exists($label, $this->label_names)) {
  699. throw new Exception('No such label: ' . $label);
  700. }
  701. return generateJump($this->label_names[$label]);
  702. }
  703. function getBreak($n = 1) {
  704. if (!$n) {
  705. $n = 1;
  706. }
  707. if ($n > count($this->break_stack)) {
  708. throw new Exception('Too many break levels');
  709. }
  710. return generateJump($this->break_stack[$n - 1]);
  711. }
  712. function getContinue($n = 1) {
  713. if (!$n) {
  714. $n = 1;
  715. }
  716. if ($n > count($this->continue_stack)) {
  717. throw new Exception('Too many continue levels');
  718. }
  719. return generateJump($this->continue_stack[$n - 1]);
  720. }
  721. }
  722. function traverseStatements($stmts, $final, $after_stmts, $state, $is_top_level = false) {
  723. if ($stmts === null) { // This is for functions in interfaces
  724. return $final(null, $state);
  725. }
  726. // Hoist functions (and classes, even though PHP doesn't exactly do so)
  727. $functions = [];
  728. $statements = [];
  729. foreach ($stmts as $stmt) {
  730. if ($stmt instanceof PHPParser_Node_Stmt_Function/* ||
  731. $stmt instanceof PHPParser_Node_Stmt_Class ||
  732. $stmt instanceof PHPParser_Node_Stmt_Interface*/)
  733. {
  734. $functions[] = $stmt;
  735. }
  736. else {
  737. $statements[] = $stmt;
  738. }
  739. }
  740. $stmts = array_merge($functions, $statements);
  741. return call_user_func($loop = function ($final, $stmts, $state) use (&$loop, $after_stmts) {
  742. if ($node = array_shift($stmts)) {
  743. return function () use ($node, $final, &$loop, $stmts, $state) {
  744. return traverseNode($node, function ($node_result, $final, $state) use (&$loop, $stmts) {
  745. return $loop(function ($future_result, $state) use ($node_result, $final, $stmts) {
  746. if (!isset($node_result)) {
  747. // do nothing
  748. }
  749. elseif (is_array($node_result)) {
  750. $future_result = array_merge($node_result, $future_result);
  751. }
  752. else {
  753. array_unshift($future_result, $node_result);
  754. }
  755. return $final($future_result, $state);
  756. }, $stmts, $state);
  757. }, $final, $state);
  758. };
  759. }
  760. else {
  761. return $final($after_stmts, $state);
  762. }
  763. }, function ($result, $state) use ($final, $is_top_level) {
  764. if ($is_top_level) {
  765. $result = array_merge($state->generateBasicBlocks(), $state->generateCatches(), $result);
  766. }
  767. return $final($result, $state);
  768. }, $stmts, $state);
  769. }
  770. // $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
  771. // $final is a function($result), where $result is an array of statements
  772. function traverseNode($node, $next, $final, $state) {
  773. if ($node instanceof PHPParser_Node_Scalar_LineConst) {
  774. return $next(new PHPParser_Node_Scalar_LNumber($node->line), $final, $state);
  775. }
  776. elseif ($node instanceof PHPParser_Node_Scalar_FileConst) {
  777. return $next(new PHPParser_Node_Scalar_String($GLOBALS['file']), $final, $state);
  778. }
  779. elseif ($node instanceof PHPParser_Node_Scalar_DirConst) {
  780. return $next(new PHPParser_Node_Scalar_String(dirname($GLOBALS['file'])), $final, $state);
  781. }
  782. elseif ($node instanceof PHPParser_Node_Scalar_Encapsed) {
  783. return call_user_func($loop = function ($parts, $compiled_parts, $final, $state) use (&$loop, $next) {
  784. if (count($parts)) {
  785. $part = array_shift($parts);
  786. return traverseNode($part, function ($part, $final, $state) use (&$loop, $parts, $compiled_parts) {
  787. $compiled_parts[] = $part;
  788. return $loop($parts, $compiled_parts, $final, $state);
  789. }, $final, $state);
  790. }
  791. else {
  792. return $next(new PHPParser_Node_Scalar_Encapsed($compiled_parts), $final, $state);
  793. }
  794. }, $node->parts, [], $final, $state);
  795. }
  796. elseif ($node instanceof PHPParser_Node_Expr_ConstFetch && $node->name->toString() == '__COMPILER_HALT_OFFSET__') {
  797. if (isset($GLOBALS['compiler_halt_offset'])) {
  798. $result = $GLOBALS['compiler_halt_offset'];
  799. }
  800. else {
  801. $result = new NodeReference($node);
  802. $GLOBALS['compiler_halt_offset_nodes'][] = $result;
  803. }
  804. return $next($result, $final, $state);
  805. }
  806. elseif ($node instanceof PHPParser_Node_Stmt_HaltCompiler) {
  807. $offset = new PHPParser_Node_Scalar_LNumber(strlen($GLOBALS['source']) - strlen($node->remaining));
  808. if (isset($GLOBALS['compiler_halt_offset_nodes'])) {
  809. foreach ($GLOBALS['compiler_halt_offset_nodes'] as $offset_node) {
  810. $offset_node->setNode($offset);
  811. }
  812. unset($GLOBALS['compiler_halt_offset_nodes']);
  813. }
  814. $GLOBALS['compiler_halt_offset'] = $offset;
  815. return $final(generateReturn(new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('null')), $state), $state);
  816. }
  817. elseif ($node instanceof PHPParser_Node_Stmt_Const) {
  818. return call_user_func($loop = function ($consts, $compiled_consts, $final, $state) use (&$loop, $next) {
  819. if ($const = array_shift($consts)) {
  820. return traverseNode($const->value, function ($const_value, $final, $state) use ($const, $consts, &$loop) {
  821. $compiled_consts[] = new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('define'), [
  822. new PHPParser_Node_Scalar_String($const->name),
  823. $const_value
  824. ]);
  825. return function () use (&$loop, $consts, $compiled_consts, $final, $state) {
  826. return $loop($consts, $compiled_consts, $final, $state);
  827. };
  828. }, $final, $state);
  829. }
  830. else {
  831. return $next($compiled_consts, $final, $state);
  832. }
  833. }, $node->consts, [], $final, $state);
  834. }
  835. elseif ($node instanceof PHPParser_Node_Expr_ConstFetch) {
  836. return $next($node, $final, $state);
  837. }
  838. elseif ($node === null || is_string($node) || $node instanceof PHPParser_Node_Scalar || $node instanceof PHPParser_Node_Name || $node instanceof PHPParser_Node_Stmt_InlineHTML) {
  839. return $next($node, $final, $state);
  840. }
  841. elseif ($node instanceof PHPParser_Node_Stmt_While) {
  842. $continue_num = $state->generateBlockNum();
  843. $break_num = $state->generateBlockNum();
  844. return traverseNode($node->cond, function ($cond, $final, $state) use ($node, $next, $continue_num, $break_num) {
  845. return traverseStatements($node->stmts, function ($while_body) use ($cond, $next, $continue_num, $break_num, $final, $state) {
  846. return $next(null, function ($after_while, $state) use ($break_num, $continue_num, $while_body, $cond, $final) {
  847. $state->addBasicBlock($break_num, $after_while);
  848. $while_body = [
  849. new PHPParser_Node_Stmt_If($cond, ['stmts' => $while_body]),
  850. generateJump($break_num)
  851. ];
  852. return $final($while_body, $state);
  853. }, $state);
  854. }, [generateJump($continue_num)], $state->addBreakAndContinue($break_num, $continue_num));
  855. }, function ($while_body, $state) use ($final, $continue_num) {
  856. $state->addBasicBlock($continue_num, $while_body);
  857. return $final([generateJump($continue_num)], $state);
  858. }, $state);
  859. }
  860. elseif ($node instanceof PHPParser_Node_Stmt_Do) {
  861. $continue_num = $state->generateBlockNum();
  862. $break_num = $state->generateBlockNum();
  863. $body_num = $state->generateBlockNum();
  864. return traverseNode($node->cond, function ($cond, $final, $state) use ($node, $next, $continue_num, $break_num, $body_num) {
  865. return traverseStatements($node->stmts, function ($do_body) use ($cond, $next, $continue_num, $break_num, $body_num, $final, $state) {
  866. $state->addBasicBlock($body_num, $do_body);
  867. return $next(null, function ($after_do, $state) use ($break_num, $continue_num, $body_num, $do_body, $cond, $final) {
  868. $state->addBasicBlock($break_num, $after_do);
  869. return $final([
  870. new PHPParser_Node_Stmt_If($cond,
  871. ['stmts' => [generateJump($body_num)]]
  872. ),
  873. generateJump($break_num)
  874. ], $state);
  875. }, $state);
  876. }, [generateJump($continue_num)], $state->addBreakAndContinue($break_num, $continue_num));
  877. }, function ($cond_block, $state) use ($final, $continue_num, $body_num) {
  878. $state->addBasicBlock($continue_num, $cond_block);
  879. return $final([generateJump($body_num)], $state);
  880. }, $state);
  881. }
  882. elseif ($node instanceof PHPParser_Node_Stmt_For) {
  883. $continue_num = $state->generateBlockNum();
  884. $break_num = $state->generateBlockNum();
  885. return traverseStatements($node->init, function ($init) use ($node, $continue_num, $break_num, $state, $next, $final) {
  886. $cond = $node->cond;
  887. $cond_var = generateTemp();
  888. if (count($cond)) {
  889. $cond[] = new PHPParser_Node_Expr_Assign($cond_var, array_pop($cond));
  890. }
  891. return call_user_func($cond_loop = function ($conds, $compiled_conds, $state) use (&$cond_loop, $node, $next, $final, $continue_num, $break_num, $init) {
  892. if ($cond = array_shift($conds)) {
  893. return traverseNode($cond, function ($cond, $final, $state) use (&$cond_loop, $conds, $compiled_conds) {
  894. $compiled_conds[] = $cond;
  895. return function () use (&$cond_loop, $conds, $compiled_conds, $state) {
  896. return $cond_loop($conds, $compiled_conds, $state);
  897. };
  898. }, $final, $state);
  899. }
  900. else {
  901. return traverseStatements($node->loop, function ($loop) use ($node, $state, $break_num, $continue_num, $compiled_conds, $next, $final, $init) {
  902. return traverseStatements($node->stmts, function ($for_body) use ($state, $continue_num, $break_num, $compiled_conds, $next, $final, $init) {
  903. if (count($compiled_conds)) {
  904. $last_cond = array_pop($compiled_conds);
  905. $for_body = array_merge($compiled_conds, [
  906. new PHPParser_Node_Stmt_If($last_cond, [
  907. 'stmts' => $for_body
  908. ])
  909. ]);
  910. }
  911. $for_body[] = generateJump($break_num);
  912. $state->addBasicBlock($continue_num, $for_body);
  913. return $next(null, function ($after_for, $state) use ($break_num, $final, $init) {
  914. $state->addBasicBlock($break_num, $after_for);
  915. return $final($init, $state);
  916. }, $state);
  917. }, $loop, $state->addBreakAndContinue($break_num, $continue_num));
  918. }, [generateJump($continue_num)], $state);
  919. }
  920. }, $node->cond, [], $state);
  921. }, [generateJump($continue_num)], $state);
  922. }
  923. elseif ($node instanceof PHPParser_Node_Stmt_Foreach) {
  924. // TODO handle non-array Traversables
  925. $continue_num = $state->generateBlockNum();
  926. $break_num = $state->generateBlockNum();
  927. return traverseNode($node->expr, function ($expr, $final, $state) use ($continue_num, $break_num, $node, $next) {
  928. $stmts = [];
  929. $temp = assignToTemp($expr, $stmts, $node->byRef);
  930. $stmts[] = new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('reset'), [$temp]);
  931. return traverseStatements($node->stmts, function ($foreach_body) use ($final, $state, $node, $next, $continue_num, $break_num, $stmts, $temp) {
  932. return traverseNode($node->keyVar, function ($keyVar, $final, $state) use ($foreach_body, $node, $next, $break_num, $temp) {
  933. return traverseNode($node->valueVar, function ($valueVar, $final, $state) use ($foreach_body, $keyVar, $next, $break_num, $temp) {
  934. return $next(null, function ($after_foreach, $state) use ($break_num, $foreach_body, $temp, $final, $keyVar, $valueVar) {
  935. $state->addBasicBlock($break_num, $after_foreach);
  936. $keyTemp = generateTemp();
  937. array_unshift($foreach_body, new PHPParser_Node_Expr_AssignRef(
  938. $valueVar,
  939. new PHPParser_Node_Expr_ArrayDimFetch($temp, $keyTemp)
  940. ));
  941. if ($keyVar) {
  942. array_unshift($foreach_body, new PHPParser_Node_Expr_Assign(
  943. $keyVar,
  944. $keyTemp
  945. ));
  946. }
  947. $foreach_body = [
  948. new PHPParser_Node_Stmt_If(
  949. new PHPParser_Node_Expr_AssignList(
  950. [$keyTemp, null],
  951. new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name('each'), [$temp])
  952. ),
  953. ['stmts' => $foreach_body]
  954. ),
  955. generateJump($break_num)
  956. ];
  957. return $final($foreach_body, $state);
  958. }, $state);
  959. }, $final, $state);
  960. }, function ($foreach_body, $state) use ($final, $continue_num, $stmts) {
  961. $state->addBasicBlock($continue_num, $foreach_body);
  962. $stmts[] = generateJump($continue_num);
  963. return $final($stmts, $state);
  964. }, $state);
  965. }, [generateJump($continue_num)], $state->addBreakAndContinue($break_num, $continue_num));
  966. }, $final, $state);
  967. }
  968. elseif ($node instanceof PHPParser_Node_Stmt_If) {
  969. $done_num = $state->generateBlockNum();
  970. return traverseNode($node->cond, function ($cond, $final, $state) use ($node, $next, $done_num) {
  971. $next_num = $done_num;
  972. if (count($node->elseifs) > 0 || isset($node->else)) {
  973. $next_num = $state->generateBlockNum();
  974. }
  975. return traverseStatements($node->stmts, function ($if_body, $state) use ($cond, $next_num, $done_num, $final, $node) {
  976. $if_body = [
  977. new PHPParser_Node_Stmt_If($cond, ['stmts' => $if_body]),
  978. generateJump($next_num)
  979. ];
  980. // Handle zero or more elseif blocks
  981. $elseifs = $node->elseifs;
  982. $else = $node->else;
  983. return call_user_func($loop = function ($next_num, $elseifs, $final, $state) use (&$loop, $done_num, $else, $if_body) {
  984. if (isset($elseifs) && $elseif = array_shift($elseifs)) {
  985. $elseif_num = $next_num;
  986. $next_num = $done_num;
  987. if (count($elseifs) > 0 || isset($else)) {
  988. $next_num = $state->generateBlockNum();
  989. }
  990. return traverseNode($elseif->cond, function ($cond, $final, $state) use (&$loop, $elseif, $elseif_num, $next_num, $done_num, $elseifs) {
  991. return traverseStatements($elseif->stmts, function ($elseif_body, $state) use (&$loop, $elseif_num, $next_num, $cond, $elseifs, $final) {
  992. $elseif_body = [
  993. new PHPParser_Node_Stmt_If($cond, ['stmts' => $elseif_body]),
  994. generateJump($next_num)
  995. ];
  996. $state->addBasicBlock($elseif_num, $elseif_body);
  997. return function () use (&$loop, $next_num, $elseifs, $final, $state) {
  998. return $loop($next_num, $elseifs, $final, $state);
  999. };
  1000. }, [generateJump($done_num)], $state);
  1001. }, $final, $state);
  1002. }
  1003. elseif (isset($else)) {
  1004. return traverseStatements($else->stmts, function ($else_body, $state) use ($next_num, $if_body, $final) {
  1005. $state->addBasicBlock($next_num, $else_body);
  1006. return $final($if_body, $state);
  1007. }, [generateJump($done_num)], $state);
  1008. }
  1009. else {
  1010. return $final($if_body, $state);
  1011. }
  1012. }, $next_num, $elseifs, $final, $state);
  1013. }, [generateJump($done_num)], $state);
  1014. }, function ($if_body, $state) use ($final, $done_num, $next) {
  1015. return $next(null, function ($after_if, $state) use ($done_num, $final, $if_body) {
  1016. $state->addBasicBlock($done_num, $after_if);
  1017. return $final($if_body, $state);
  1018. }, $state);
  1019. }, $state);
  1020. }
  1021. elseif ($node instanceof PHPParser_Node_Stmt_Break) {
  1022. $num = $node->num->value;
  1023. return $next(null, function ($result, $new_state) use ($final, $state, $num) {
  1024. return $final([$state->getBreak($num)], $new_state);
  1025. }, $state);
  1026. }
  1027. elseif ($node instanceof PHPParser_Node_Stmt_Continue) {
  1028. $num = $node->num->value;
  1029. return $next(null, function ($result, $new_state) use ($final, $state, $num) {
  1030. return $final([$state->getContinue($num)], $new_state);
  1031. }, $state);
  1032. }
  1033. elseif ($node instanceof PHPParser_Node_Stmt_Label) {
  1034. $label_num = $state->generateBlockNum();
  1035. $state->addLabel($label_num, $node->name);
  1036. return $next(null, function ($after_label, $state) use ($label_num, $final) {
  1037. $state->addBasicBlock($label_num, $after_label);
  1038. return $final([generateJump($label_num)], $state);
  1039. }, $state);
  1040. }
  1041. elseif ($node instanceof PHPParser_Node_Stmt_Goto) {
  1042. $name = $node->name;
  1043. return $next(null, function ($after_goto, $state) use ($final, $name) {
  1044. return $final([$state->getLabel($name)], $state);
  1045. }, $state);
  1046. }
  1047. elseif ($node instanceof PHPParser_Node_Expr_Exit) {
  1048. return traverseNode($node->expr, function ($expr, $final, $state) use ($next) {
  1049. return $next(null, function ($stmts_x, $state_x) use ($expr, $state, $final) {
  1050. // Executable statements after exit() are not reached, but we run the compiler over them anyway.
  1051. return $final([new PHPParser_node_Expr_Exit($expr)], $state);
  1052. }, $state);
  1053. }, $final, $state);
  1054. }
  1055. elseif ($node instanceof PHPParser_Node_Stmt_Return) {
  1056. return traverseNode($node->expr, new ReturnClosure(function ($expr, $final, $state) use ($next) {
  1057. return $next(null, function ($stmts_x, $state_x) use ($expr, $state, $final) {
  1058. // 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())
  1059. return $final(generateReturn($expr, $state), $state);
  1060. }, $state);
  1061. }), $final, $state);
  1062. }
  1063. elseif ($node instanceof PHPParser_Node_Expr_FuncCall) {
  1064. return generateContinuation($next, function ($continuation, $state) use ($node, $final) {
  1065. return traverseNode($node->name, function ($function, $final, $state) use ($next, $node) {
  1066. return call_user_func($loop = function ($final, $i, $compiled_args, $state) use (&$loop, $function, $node, $next) {
  1067. if ($i < count($node->args)) {
  1068. $arg = $node->args[$i++];
  1069. $byRef = $arg->byRef;
  1070. return function () use (&$loop, $arg, $i, $byRef, $compiled_args, $final, $state) {
  1071. return traverseNode($arg->value, function ($node_result, $final, $state) use (&$loop, $i, $byRef, $compiled_args) {
  1072. $compiled_args[] = new PHPParser_Node_Arg($node_result, $byRef);
  1073. return $loop($final, $i, $compiled_args, $state);
  1074. }, $final, $state);
  1075. };
  1076. }
  1077. else {
  1078. // trampoline
  1079. return $final([generateThunk(
  1080. [
  1081. new PHPParser_Node_Expr_ClosureUse(CONT_NAME),
  1082. new PHPParser_Node_Expr_ClosureUse(EXCEPT_NAME),
  1083. new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME, true),
  1084. new PHPParser_Node_Expr_ClosureUse(TEMP_NAME)
  1085. ],
  1086. generateFunctionCall($function, $compiled_args, $state),
  1087. 'call'
  1088. )], $state);
  1089. }
  1090. }, $final, 0, [], $state);
  1091. }, generateFinalForContinuation($final, $continuation), $state);
  1092. }, $state);
  1093. }
  1094. elseif ($node instanceof PHPParser_Node_Stmt_Function) {
  1095. $name = $node->name;
  1096. generateParams($node->params, $params, $param_items);
  1097. array_unshift($params, new PHPParser_Node_Param(EXCEPT_NAME));
  1098. array_unshift($params, new PHPParser_Node_Param(CONT_NAME));
  1099. $new_state = new FunctionState();
  1100. $new_state->setIsReturnByRef($node->byRef);
  1101. // Provide a way to escape from CPS if a function doesn't need it.
  1102. if (count($node->stmts) &&
  1103. $node->stmts[0] instanceof PHPParser_Node_Scalar_String &&
  1104. $node->stmts[0]->value == 'fast')
  1105. {
  1106. // "fast" pragma to treat this function as a builtin
  1107. // this promises that the function does not call any non-fast functions
  1108. CpsRuntime::add_builtin_function($node->name);
  1109. array_shift($node->stmts); // drop the pragma
  1110. return $next([
  1111. $node,
  1112. new PHPParser_Node_Expr_StaticCall(new PHPParser_Node_Name('CpsRuntime'), new PHPParser_Node_Name('add_builtin_function'), [
  1113. new PHPParser_Node_Scalar_String($node->name)
  1114. ])
  1115. ], $final, $state);
  1116. }
  1117. return traverseStatements($node->stmts, function ($result, $new_state) use ($next, $final, $name, $params, $param_items, $byRef, $state) {
  1118. $result = array_merge(
  1119. [
  1120. new PHPParser_Node_Expr_Assign(
  1121. new PHPParser_Node_Expr_Variable(LOCALS_NAME),
  1122. new PHPParser_Node_Expr_Array($param_items)
  1123. ),
  1124. new PHPParser_Node_Expr_Assign(
  1125. new PHPParser_Node_Expr_Variable(TEMP_NAME),
  1126. generateDefaultTemps()
  1127. ),
  1128. new PHPParser_Node_Expr_Assign(
  1129. new PHPParser_Node_Expr_Variable(LABELS_NAME),
  1130. new PHPParser_Node_Expr_Array()
  1131. )
  1132. ],
  1133. $result);
  1134. $result = new PHPParser_Node_Stmt_Function($name, [
  1135. 'params' => $params,
  1136. 'stmts' => $result
  1137. ]);
  1138. return $next($result, $final, $state);
  1139. }, generateReturn(new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('null')), $new_state), $new_state, true);
  1140. }
  1141. elseif ($node instanceof PHPParser_Node_Expr_Closure) {
  1142. generateParams($node->params, $params, $param_items);
  1143. array_unshift($params, new PHPParser_Node_Param(EXCEPT_NAME));
  1144. array_unshift($params, new PHPParser_Node_Param(CONT_NAME));
  1145. if ($state->isInInstanceMethod()) {
  1146. $param_items[] = new PHPParser_Node_Expr_ArrayItem(
  1147. new PHPParser_Node_Expr_Variable('this'),
  1148. new PHPParser_Node_Scalar_String('this'),
  1149. true
  1150. );
  1151. }
  1152. $uses = [];
  1153. foreach ($node->uses as $use) {
  1154. $name = new PHPParser_Node_Scalar_String($use->var);
  1155. $uses[] = new PHPParser_Node_Expr_ArrayItem(
  1156. new PHPParser_Node_Expr_ArrayDimFetch(new PHPParser_Node_Expr_Variable(LOCALS_NAME), $name),
  1157. $name,
  1158. $use->byRef
  1159. );
  1160. }
  1161. $uses = new PHPParser_Node_Expr_Assign(
  1162. new PHPParser_Node_Expr_Variable(USES_NAME),
  1163. new PHPParser_Node_Expr_Array($uses)
  1164. );
  1165. $new_state = new FunctionState();
  1166. $new_state->setSelf($state->getSelf());
  1167. $new_state->setParent($state->getParent());
  1168. $new_state->setIsReturnByRef($node->byRef);
  1169. $new_state->setIsInInstanceMethod($state->isInInstanceMethod());
  1170. return traverseStatements($node->stmts, function ($result, $new_state) use ($param_items, $next, $final, $uses, $params, $state) {
  1171. $result = array_merge(
  1172. [
  1173. new PHPParser_Node_Expr_Assign(
  1174. new PHPParser_Node_Expr_Variable(LOCALS_NAME),
  1175. new PHPParser_Node_Expr_Plus(
  1176. new PHPParser_Node_Expr_Variable(USES_NAME),
  1177. new PHPParser_Node_Expr_Array($param_items)
  1178. )
  1179. ),
  1180. new PHPParser_Node_Expr_Assign(
  1181. new PHPParser_Node_Expr_Variable(TEMP_NAME),
  1182. generateDefaultTemps()
  1183. ),
  1184. new PHPParser_Node_Expr_Assign(
  1185. new PHPParser_Node_Expr_Variable(LABELS_NAME),
  1186. new PHPParser_Node_Expr_Array()
  1187. )
  1188. ],
  1189. $result);
  1190. $stmts = [$uses];
  1191. $temp = assignToTemp(new PHPParser_Node_Expr_Closure([
  1192. 'params' => $params,
  1193. 'uses' => [new PHPParser_Node_Expr_ClosureUse(USES_NAME)],
  1194. 'stmts' => $result
  1195. ]), $stmts);
  1196. return $next($temp, function ($result, $state) use ($final, $stmts) {
  1197. return $final(array_merge($stmts, $result), $state);
  1198. }, $state);
  1199. }, generateReturn(new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('null')), $new_state), $new_state, true);
  1200. }
  1201. elseif ($node instanceof PHPParser_Node_Expr_Array) {
  1202. $node_items = $node->items;
  1203. return call_user_func($loop = function ($final, $items, $i, $state) use (&$loop, $node_items, $next) {
  1204. if ($i < count($node_items)) {
  1205. $item = $node_items[$i++];
  1206. $value = $item->value;
  1207. $byRef = $item->byRef;
  1208. return traverseNode($item->key, function ($key, $final, $state) use ($value, $byRef, &$loop, $i, $items) {
  1209. return traverseNode($value, function ($value, $final, $state) use ($byRef, &$loop, $i, $key, $items) {
  1210. $items[] = new PHPParser_Node_Expr_ArrayItem($value, $key, $byRef);
  1211. return function () use (&$loop, $final, $items, $i, $state) {
  1212. return $loop($final, $items, $i, $state);
  1213. };
  1214. }, $final, $state);
  1215. }, $final, $state);
  1216. }
  1217. else {
  1218. return $next(new PHPParser_Node_Expr_Array($items), $final, $state);
  1219. }
  1220. }, $final, [], 0, $state);
  1221. }
  1222. elseif ($node instanceof PHPParser_Node_Stmt_Unset) {
  1223. return call_user_func($loop = function ($vars, $compiled_vars, $final, $state) use (&$loop, $next) {
  1224. if ($var = array_shift($vars)) {
  1225. return traverseNode($var, function ($var, $final, $state) use (&$loop, $compiled_vars, $vars) {
  1226. $compiled_vars[] = $var;
  1227. return $loop($vars, $compiled_vars, $final, $state);
  1228. }, $final, $state);
  1229. }
  1230. else {
  1231. return $next(new PHPParser_Node_Stmt_Unset($compiled_vars), $final, $state);
  1232. }
  1233. }, $node->vars, [], $final, $state);
  1234. }
  1235. elseif ($node instanceof PHPParser_Node_Expr_Isset) {
  1236. // isset() short circuits once it finds an unset variable, evaluating left to right
  1237. $vars = $node->vars;
  1238. return generateContinuation($next, function ($continuation, $state) use ($vars, $final) {
  1239. return call_user_func($loop = function ($i, $final, $state) use (&$loop, $vars, $continuation) {
  1240. $var = $vars[$i++];
  1241. if ($i < count($vars)) {
  1242. $next = function ($junk, $final, $state) use (&$loop, $i) {
  1243. return $loop($i, $final, $state);
  1244. };
  1245. return $next(null, function ($continuation_stmts, $state) use (&$loop, $final, $next, $i, $vars, $var) {
  1246. return traverseNode($var, function ($var, $final, $state) use (&$loop, $i, $continuation_stmts) {
  1247. return $final([
  1248. new PHPParser_Node_Stmt_If(
  1249. new PHPParser_Node_Expr_Isset([$var]),
  1250. [
  1251. 'stmts' => $continuation_stmts,
  1252. 'else' => new PHPParser_Node_Stmt_Else(
  1253. generateReturn(new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('false')), $state)
  1254. )
  1255. ]
  1256. )
  1257. ], $state);
  1258. }, $final, $state);
  1259. }, $state);
  1260. }
  1261. else {
  1262. return traverseNode($var, function ($var, $final, $state) {
  1263. return $final(generateReturn(new PHPParser_Node_Expr_Isset([$var]), $state), $state);
  1264. }, $final, $state);
  1265. }
  1266. }, 0, generateFinalForContinuation($final, $continuation), $state);
  1267. }, $state);
  1268. }
  1269. elseif ($node instanceof PHPParser_Node_Expr_PostDec || // unary that takes a "var"
  1270. $node instanceof PHPParser_Node_Expr_PostInc ||
  1271. $node instanceof PHPParser_Node_Expr_PreDec ||
  1272. $node instanceof PHPParser_Node_Expr_PreInc)
  1273. {
  1274. $node_class = get_class($node);
  1275. return traverseNode($node->var, function ($result, $final, $state) use ($node_class, $next) {
  1276. return $next(new $node_class($result), $final, $state);
  1277. }, $final, $state);
  1278. }
  1279. elseif ($node instanceof PHPParser_Node_Expr_BitwiseNot || // unary that takes a "expr"
  1280. $node instanceof PHPParser_Node_Expr_BooleanNot ||
  1281. $node instanceof PHPParser_Node_Expr_ErrorSuppress ||
  1282. $node instanceof PHPParser_Node_Expr_Print ||
  1283. $node instanceof PHPParser_Node_Expr_UnaryMinus ||
  1284. $node instanceof PHPParser_Node_Expr_UnaryPlus ||
  1285. $node instanceof PHPParser_Node_Expr_Cast ||
  1286. $node instanceof PHPParser_Node_Expr_Clone)
  1287. {
  1288. $node_class = get_class($node);
  1289. return traverseNode($node->expr, function ($result, $final, $state) use ($node_class, $next) {
  1290. return $next(new $node_class($result), $final, $state);
  1291. }, $final, $state);
  1292. }
  1293. elseif ($node instanceof PHPParser_Node_Stmt_Echo) {
  1294. $exprs = $node->exprs;
  1295. return call_user_func($loop = function ($i, $results, $final, $state) use (&$loop, $exprs, $next) {
  1296. if ($i < count($exprs)) {
  1297. $expr = $exprs[$i++];
  1298. return function () use ($expr, &$loop, $i, $final, $results, $state) {
  1299. return traverseNode($expr, function ($result, $final, $state) use (&$loop, $i, $results) {
  1300. $results[] = $result;
  1301. return $loop($i, $results, $final, $state);
  1302. }, $final, $state);
  1303. };
  1304. }
  1305. else {
  1306. return $next(new PHPParser_Node_Stmt_Echo($results), $final, $state);
  1307. }
  1308. }, 0, [], $final, $state);
  1309. }
  1310. elseif ($node instanceof PHPParser_Node_Stmt_Global) {
  1311. $vars = $node->vars;
  1312. return call_user_func($loop = function ($i, $results, $final, $state) use (&$loop, $vars, $next) {
  1313. if ($i < count($vars)) {
  1314. $var = $vars[$i++];
  1315. if ($var instanceof PHPParser_Node_Expr_Variable) {
  1316. $name = $var->name;
  1317. return function () use ($name, &$loop, $i, $final, $results, $state) {
  1318. return traverseNode($name, function ($name, $final, $state) use (&$loop, $i, $results) {
  1319. if (is_string($name)) {
  1320. $name = new PHPParser_Node_Scalar_String($name);
  1321. }
  1322. else {
  1323. $name = assignToTemp($name, $results);
  1324. }
  1325. $results[] = new PHPParser_Node_Expr_AssignRef(
  1326. new PHPParser_Node_Expr_ArrayDimFetch(
  1327. new PHPParser_Node_Expr_Variable(LOCALS_NAME),
  1328. $name
  1329. ),
  1330. new PHPParser_Node_Expr_ArrayDimFetch(
  1331. new PHPParser_Node_Expr_ArrayDimFetch(
  1332. new PHPParser_Node_Expr_Variable(TEMP_NAME),
  1333. new PHPParser_Node_Scalar_String(GLOBALS_TEMP_NAME)
  1334. ),
  1335. $name
  1336. )
  1337. );
  1338. return $loop($i, $results, $final, $state);
  1339. }, $final, $state);
  1340. };
  1341. }
  1342. else {
  1343. throw new Exception('Cannot globalize a non-variable');
  1344. }
  1345. }
  1346. else {
  1347. return $next($results, $final, $state);
  1348. }
  1349. }, 0, [], $final, $state);
  1350. }
  1351. elseif ($node instanceof PHPParser_Node_Expr_Ternary) {
  1352. return generateContinuation($next, function ($continuation, $state) use ($node, $next, $final) {
  1353. $if = $node->if;
  1354. $else = $node->else;
  1355. return traverseNode($node->cond, function ($cond, $final, $state) use ($next, $if, $else) {
  1356. $ifTemp = null;
  1357. if (!isset($if)) {
  1358. $ifTemp = generateTemp();
  1359. $cond = new PHPParser_Node_Expr_Assign($ifTemp, $cond);
  1360. }
  1361. $ifFinal = function ($ifBranch, $state) use ($final, $else, $next, $cond) {
  1362. $elseNext = function ($else, $final, $state) {
  1363. return $final(generateReturn($else, $state), $state);
  1364. };
  1365. if ($next instanceof ReturnClosure) {
  1366. $elseNext = new ReturnClosure($elseNext);
  1367. }
  1368. return traverseNode($else, $elseNext, function ($elseBranch, $state) use ($final, $ifBranch, $cond) {
  1369. return $final([new PHPParser_Node_Stmt_If($cond, [
  1370. 'stmts' => $ifBranch,
  1371. 'else' => new PHPParser_Node_Stmt_Else($elseBranch)
  1372. ])], $state);
  1373. }, $state);
  1374. };
  1375. if ($ifTemp) {
  1376. return $ifFinal(generateReturn($ifTemp, $state), $state);
  1377. }
  1378. $ifNext = function ($if, $final, $state) {
  1379. return $final(generateReturn($if, $state), $state);
  1380. };
  1381. if ($next instanceof ReturnClosure) {
  1382. $ifNext = new ReturnClosure($next);
  1383. }
  1384. return traverseNode($if, $ifNext, $ifFinal, $state);
  1385. }, generateFinalForContinuation($final, $continuation), $state);
  1386. }, $state);
  1387. }
  1388. elseif ($node instanceof PHPParser_Node_Expr_BooleanAnd || // short circuit binary operators
  1389. $node instanceof PHPParser_Node_Expr_LogicalAnd ||
  1390. $node instanceof PHPParser_Node_Expr_BooleanOr ||
  1391. $node instanceof PHPParser_Node_Expr_LogicalOr)
  1392. {
  1393. $short_circuit_if_left_is = true;
  1394. if ($node instanceof PHPParser_Node_Expr_BooleanAnd || $node instanceof PHPParser_Node_Expr_LogicalAnd) {
  1395. $short_circuit_if_left_is = false;
  1396. }
  1397. return generateContinuation($next, function ($continuation, $state) use ($next, $node, $final, $short_circuit_if_left_is) {
  1398. $right = $node->right;
  1399. return traverseNode($node->left, function ($left, $final, $state) use ($next, $right, $short_circuit_if_left_is) {
  1400. $leftTemp = assignToTemp($left, $stmts);
  1401. $rightNext = function ($right, $final, $state) {
  1402. return $final(generateReturn(boolifyLogicalOperator($right), $state), $state);
  1403. };
  1404. if ($next instanceof ReturnClosure) {
  1405. $rightNext = new ReturnClosure($rightNext);
  1406. }
  1407. return traverseNode($right, $rightNext, function ($rightBranch, $state) use ($stmts, $final, $leftTemp, $short_circuit_if_left_is) {
  1408. $leftBranch = generateReturn(boolifyLogicalOperator($leftTemp), $state);
  1409. $if = $rightBranch;
  1410. $else = $leftBranch;
  1411. if ($short_circuit_if_left_is) {
  1412. $if = $leftBranch;
  1413. $else = $rightBranch;
  1414. }
  1415. $stmts[] = new PHPParser_Node_Stmt_If($leftTemp, [
  1416. 'stmts' => $if,
  1417. 'else' => new PHPParser_Node_Stmt_Else($else)
  1418. ]);
  1419. return $final($stmts, $state);
  1420. }, $state);
  1421. }, generateFinalForContinuation($final, $continuation), $state);
  1422. }, $state);
  1423. }
  1424. elseif ($node instanceof PHPParser_Node_Expr_BitwiseAnd || // binary expressions
  1425. $node instanceof PHPParser_Node_Expr_BitwiseOr ||
  1426. $node instanceof PHPParser_Node_Expr_BitwiseXor ||
  1427. $node instanceof PHPParser_Node_Expr_Concat ||
  1428. $node instanceof PHPParser_Node_Expr_Div ||
  1429. $node instanceof PHPParser_Node_Expr_Equal ||
  1430. $node instanceof PHPParser_Node_Expr_GreaterOrEqual ||
  1431. $node instanceof PHPParser_Node_Expr_Greater ||
  1432. $node instanceof PHPParser_Node_Expr_Identical ||
  1433. $node instanceof PHPParser_Node_Expr_LogicalXor ||
  1434. $node instanceof PHPParser_Node_Expr_Minus ||
  1435. $node instanceof PHPParser_Node_Expr_Mod ||
  1436. $node instanceof PHPParser_Node_Expr_Mul ||
  1437. $node instanceof PHPParser_Node_Expr_NotEqual ||
  1438. $node instanceof PHPParser_Node_Expr_NotIdentical ||
  1439. $node instanceof PHPParser_Node_Expr_Plus ||
  1440. $node instanceof PHPParser_Node_Expr_ShiftLeft ||
  1441. $node instanceof PHPParser_Node_Expr_ShiftRight ||
  1442. $node instanceof PHPParser_Node_Expr_SmallerOrEqual ||
  1443. $node instanceof PHPParser_Node_Expr_Smaller)
  1444. {
  1445. $node_class = get_class($node);
  1446. $node_right = $node->right;
  1447. return traverseNode($node->left, function ($left, $final, $state) use ($node_class, $node_right, $next) {
  1448. return traverseNode($node_right, function ($right, $final, $state) use ($left, $next, $node_class) {
  1449. return $next(new $node_class($left, $right), $final, $state);
  1450. }, $final, $state);
  1451. }, $final, $state);
  1452. }
  1453. elseif ($node instanceof PHPParser_Node_Expr_Assign || // assignments
  1454. $node instanceof PHPParser_Node_Expr_AssignBitwiseAnd ||
  1455. $node instanceof PHPParser_Node_Expr_AssignBitwiseOr ||
  1456. $node instanceof PHPParser_Node_Expr_AssignBitwiseXor ||
  1457. $node instanceof PHPParser_Node_Expr_AssignConcat ||
  1458. $node instanceof PHPParser_Node_Expr_AssignDiv ||
  1459. $node instanceof PHPParser_Node_Expr_AssignMinus ||
  1460. $node instanceof PHPParser_Node_Expr_AssignMod ||
  1461. $node instanceof PHPParser_Node_Expr_AssignMul ||
  1462. $node instanceof PHPParser_Node_Expr_AssignPlus ||
  1463. $node instanceof PHPParser_Node_Expr_AssignRef ||
  1464. $node instanceof PHPParser_Node_Expr_AssignShiftLeft ||
  1465. $node instanceof PHPParser_Node_Expr_AssignShiftRight)
  1466. {
  1467. $node_class = get_class($node);
  1468. $expr = $node->expr;
  1469. return traverseNode($node->var, function ($var, $final, $state) use ($node_class, $expr, $next) {
  1470. return traverseNode($expr, function ($expr, $final, $state) use ($var, $next, $node_class) {
  1471. return $next(new $node_class($var, $expr), $final, $state);
  1472. }, $final, $state);
  1473. }, $final, $state);
  1474. }
  1475. elseif ($node instanceof PHPParser_Node_Expr_AssignList) {
  1476. $expr = $node->expr;
  1477. return call_user_func($loop = function ($vars, $compiled_vars, $final, $state) use (&$loop, $expr, $next) {
  1478. if (count($vars)) {
  1479. $var = array_shift($vars);
  1480. return function () use (&$loop, $var, $vars, $final, $state, $compiled_vars) {
  1481. return traverseNode($var, function ($var, $final, $state) use (&$loop, $vars, $compiled_vars) {
  1482. $compiled_vars[] = $var;
  1483. return $loop($vars, $compiled_vars, $final, $state);
  1484. }, $final, $state);
  1485. };
  1486. }
  1487. else {
  1488. return traverseNode($expr, function ($expr, $final, $state) use ($compiled_vars, $next) {
  1489. return $next(new PHPParser_Node_Expr_AssignList($compiled_vars, $expr), $final, $state);
  1490. }, $final, $state);
  1491. }
  1492. }, $node->vars, [], $final, $state);
  1493. }
  1494. elseif ($node instanceof PHPParser_Node_Expr_ArrayDimFetch) {
  1495. $dim = $node->dim;
  1496. return traverseNode($node->var, function ($var, $final, $state) use ($dim, $next) {
  1497. return traverseNode($dim, function ($dim, $final, $state) use ($var, $next) {
  1498. return $next(new PHPParser_Node_Expr_ArrayDimFetch($var, $dim), $final, $state);
  1499. }, $final, $state);
  1500. }, $final, $state);
  1501. }
  1502. elseif ($node instanceof PHPParser_Node_Expr_Variable) {
  1503. return traverseNode($node->name, function ($name, $final, $state) use ($next) {
  1504. if ($name instanceof PHPParser_Node_Scalar_String) {
  1505. $name = $name->value;
  1506. }
  1507. if (is_string($name)) {
  1508. if (in_array($name, $GLOBALS['SUPERGLOBALS'])) {
  1509. // Directly access superglobals. Don't go through the locals.
  1510. return $next(new PHPParser_Node_Expr_Variable($name), $final, $state);
  1511. }
  1512. $name = new PHPParser_Node_Scalar_String($name);
  1513. }
  1514. return $next(new PHPParser_Node_Expr_ArrayDimFetch(
  1515. new PHPParser_Node_Expr_Variable(LOCALS_NAME),
  1516. $name
  1517. ), $final, $state);
  1518. }, $final, $state);
  1519. }
  1520. elseif ($node instanceof PHPParser_Node_Expr_Instanceof) {
  1521. return traverseNode($node->expr, function ($expr, $final, $state) use ($next, $node) {
  1522. return traverseNode($node->class, function ($class, $final, $state) use ($next, $expr) {
  1523. return $next(new PHPParser_Node_Expr_Instanceof($expr, $class), $final, $state);
  1524. }, $final, $state);
  1525. }, $final, $state);
  1526. }
  1527. elseif ($node instanceof PHPParser_Node_Stmt_Interface) {
  1528. return traverseStatements($node->stmts, function ($stmts, $new_state) use ($final, $next, $state, $node) {
  1529. return $next(new PHPParser_Node_Stmt_Interface(
  1530. $node->name,
  1531. [
  1532. 'extends' => $node->extends,
  1533. 'stmts' => $stmts
  1534. ]
  1535. ), $final, $state);
  1536. }, [], new FunctionState(), true);
  1537. }
  1538. elseif ($node instanceof PHPParser_Node_Stmt_Class) {
  1539. $new_state = new FunctionState();
  1540. $new_state->setSelf($node->name);
  1541. $new_state->setParent($node->extends);
  1542. $builtin_methods = [];
  1543. if ($node->extends && array_key_exists($node->extends->toString(), CpsRuntime::$builtin_methods)) {
  1544. $builtin_methods = CpsRuntime::$builtin_methods[$node->extends->toString()];
  1545. CpsRuntime::$builtin_methods[$node->name] = $builtin_methods;
  1546. // Find non-builtins that are defined in this class
  1547. foreach ($node->stmts as $method) {
  1548. if ($method instanceof PHPParser_Node_Stmt_ClassMethod) {
  1549. unset(CpsRuntime::$builtin_methods[$node->name][$method->name]);
  1550. }
  1551. }
  1552. foreach (CpsRuntime::$builtin_methods[$node->name] as $method_name => $junk) {
  1553. $new_state->addBuiltinMethod($method_name);
  1554. }
  1555. }
  1556. return traverseStatements($node->stmts, function ($stmts, $new_state) use ($final, $next, $state, $node) {
  1557. $result = $new_state->generateBuiltinMethodDeclarations();
  1558. $result[] = new PHPParser_Node_Stmt_Class(
  1559. $node->name,
  1560. [
  1561. 'type' => $node->type,
  1562. 'extends' => $node->extends,
  1563. 'implements' => $node->implements,
  1564. 'stmts' => $stmts
  1565. ]
  1566. );
  1567. return $next($result, $final, $state);
  1568. }, [], $new_state, true);
  1569. }
  1570. elseif ($node instanceof PHPParser_Node_Stmt_ClassMethod) {
  1571. if (count($node->stmts) &&
  1572. $node->stmts[0] instanceof PHPParser_Node_Scalar_String &&
  1573. $node->stmts[0]->value == 'fast')
  1574. {
  1575. array_shift($node->stmts);
  1576. CpsRuntime::add_builtin_method($state->getSelf(), $node->name);
  1577. $state->addBuiltinMethod($node->name);
  1578. return $next($node, $final, $state);
  1579. }
  1580. generateParams($node->params, $params, $param_items);
  1581. $name = $node->name;
  1582. $should_trampoline = false;
  1583. if (in_array($name, $GLOBALS['MAGIC_METHODS'])) {
  1584. $should_trampoline = true;
  1585. }
  1586. else {
  1587. array_unshift($params, new PHPParser_Node_Param(EXCEPT_NAME));
  1588. array_unshift($params, new PHPParser_Node_Param(CONT_NAME));
  1589. }
  1590. $new_state = new FunctionState();
  1591. $new_state->setSelf($state->getSelf());
  1592. $new_state->setParent($state->getParent());
  1593. $new_state->setIsReturnByRef($node->byRef);
  1594. $new_state->setIsInInstanceMethod(!($node->type & PHPParser_Node_Stmt_Class::MODIFIER_STATIC));
  1595. return traverseStatements($node->stmts, function ($result, $new_state) use ($next, $final, $state, $node, $name, $params, $param_items, $should_trampoline) {
  1596. if (isset($result)) {
  1597. if ($should_trampoline) {
  1598. $result = generateTrampoline($result);
  1599. }
  1600. $param_items[] = new PHPParser_Node_Expr_ArrayItem(new PHPParser_Node_Expr_Variable('this'), new PHPParser_Node_Scalar_String('this'));
  1601. $result = array_merge(
  1602. [
  1603. new PHPParser_Node_Expr_Assign(
  1604. new PHPParser_Node_Expr_Variable(LOCALS_NAME),
  1605. new PHPParser_Node_Expr_Array($param_items)
  1606. ),
  1607. new PHPParser_Node_Expr_Assign(
  1608. new PHPParser_Node_Expr_Variable(TEMP_NAME),
  1609. generateDefaultTemps()
  1610. ),
  1611. new PHPParser_Node_Expr_Assign(
  1612. new PHPParser_Node_Expr_Variable(LABELS_NAME),
  1613. new PHPParser_Node_Expr_Array()
  1614. )
  1615. ],
  1616. $result);
  1617. }
  1618. return $next(new PHPParser_Node_Stmt_ClassMethod($name, [
  1619. 'type' => $node->type,
  1620. 'params' => $params,
  1621. 'stmts' => $result,
  1622. 'byRef' => $should_trampoline && $new_state->getIsReturnByRef()
  1623. ]), $final, $state);
  1624. }, generateReturn(new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('null')), $new_state), $new_state, true);
  1625. }
  1626. elseif ($node instanceof PHPParser_Node_Expr_MethodCall) {
  1627. return generateContinuation($next, function ($continuation, $state) use ($node, $final) {
  1628. return traverseNode($node->var, function ($var, $final, $state) use ($node) {
  1629. $args = $node->args;
  1630. return traverseNode($node->name, function ($name, $final, $state) use ($args, $var) {
  1631. return call_user_func($loop = function ($final, $args, $compiled_args, $state) use (&$loop, $var, $name) {
  1632. if ($arg = array_shift($args)) {
  1633. return function () use (&$loop, $arg, $args, $compiled_args, $state, $final) {
  1634. $byRef = $arg->byRef;
  1635. return traverseNode($arg->value, function ($arg_value, $final, $state) use (&$loop, $byRef, $args, $compiled_args) {
  1636. $compiled_args[] = new PHPParser_Node_Arg($arg_value, $byRef);
  1637. return $loop($final, $args, $compiled_args, $state);
  1638. }, $final, $state);
  1639. };
  1640. }
  1641. else {
  1642. return $final(generateMethodCall($var, $name, $compiled_args, PHPParser_Node_Expr_MethodCall, $state), $state);
  1643. }
  1644. }, $final, $args, [], $state);
  1645. }, $final, $state);
  1646. }, generateFinalForContinuation($final, $continuation), $state);
  1647. }, $state);
  1648. }
  1649. elseif ($node instanceof PHPParser_Node_Expr_New) {
  1650. return generateContinuation($next, function ($continuation, $state) use ($final, $node) {
  1651. return traverseNode($node->class, function ($class, $final, $state) use ($node) {
  1652. return call_user_func($loop = function ($args, $compiled_args, $final, $state) use (&$loop, $class) {
  1653. if ($arg = array_shift($args)) {
  1654. $byRef = $arg->byRef;
  1655. return traverseNode($arg->value, function ($arg, $final, $state) use (&$loop, $args, $byRef, $compiled_args) {
  1656. $compiled_args[] = new PHPParser_Node_Arg($arg, $byRef);
  1657. return function () use (&$loop, $args, $compiled_args, $final, $state) {
  1658. return $loop($args, $compiled_args, $final, $state);
  1659. };
  1660. }, $final, $state);
  1661. }
  1662. else {
  1663. $stmts = [generateTryCatchCall(new PHPParser_Node_Expr_New($class, $compiled_args), $state)];
  1664. return $final([generateThunk(
  1665. [
  1666. new PHPParser_Node_Expr_ClosureUse(CONT_NAME),
  1667. new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME, true),
  1668. new PHPParser_Node_Expr_ClosureUse(TEMP_NAME)
  1669. ],
  1670. $stmts,
  1671. 'new'
  1672. )], $state);
  1673. }
  1674. }, $node->args, [], $final, $state);
  1675. }, generateFinalForContinuation($final, $continuation), $state);
  1676. }, $state);
  1677. }
  1678. elseif ($node instanceof PHPParser_Node_Expr_PropertyFetch) {
  1679. $name = $node->name;
  1680. return traverseNode($node->var, function ($var, $final, $state) use ($next, $name) {
  1681. return traverseNode($name, function ($name, $final, $state) use ($next, $var) {
  1682. return $next(new PHPParser_Node_Expr_PropertyFetch($var, $name), $final, $state);
  1683. }, $final, $state);
  1684. }, $final, $state);
  1685. }
  1686. elseif ($node instanceof PHPParser_Node_Expr_StaticPropertyFetch) {
  1687. $name = $node->name;
  1688. return traverseNode($node->class, function ($class, $final, $state) use ($next, $name) {
  1689. return traverseNode($name, function ($name, $final, $state) use ($next, $class) {
  1690. return $next(new PHPParser_Node_Expr_StaticPropertyFetch($class, $name), $final, $state);
  1691. }, $final, $state);
  1692. }, $final, $state);
  1693. }
  1694. elseif ($node instanceof PHPParser_Node_Expr_StaticCall) {
  1695. return generateContinuation($next, function ($continuation, $state) use ($final, $node) {
  1696. return traverseNode($node->class, function ($class, $final, $state) use ($node) {
  1697. return traverseNode($node->name, function ($name, $final, $state) use ($node, $class) {
  1698. return call_user_func($loop = function ($args, $compiled_args, $final, $state) use (&$loop, $class, $name) {
  1699. if ($arg = array_shift($args)) {
  1700. $byRef = $arg->byRef;
  1701. return traverseNode($arg->value, function ($arg_value, $final, $state) use ($args, $compiled_args, &$loop, $byRef) {
  1702. $compiled_args[] = new PHPParser_Node_Arg($arg_value, $byRef);
  1703. return $loop($args, $compiled_args, $final, $state);
  1704. }, $final, $state);
  1705. }
  1706. else {
  1707. return $final(generateMethodCall($class, $name, $compiled_args, PHPParser_Node_Expr_StaticCall, $state), $state);
  1708. }
  1709. }, $node->args, [], $final, $state);
  1710. }, $final, $state);
  1711. }, generateFinalForContinuation($final, $continuation), $state);
  1712. }, $state);
  1713. }
  1714. elseif ($node instanceof PHPParser_Node_Stmt_Property) {
  1715. $type = $node->type;
  1716. return call_user_func($loop = function ($props, $compiled_props, $final, $state) use (&$loop, $next, $type) {
  1717. if ($prop = array_shift($props)) {
  1718. $name = $prop->name;
  1719. return traverseNode($prop->default, function ($default, $final, $state) use (&$loop, $props, $compiled_props, $name) {
  1720. $compiled_props[] = new PHPParser_Node_Stmt_PropertyProperty($name, $default);
  1721. return $loop($props, $compiled_props, $final, $state);
  1722. }, $final, $state);
  1723. }
  1724. else {
  1725. return $next(new PHPParser_Node_Stmt_Property($type, $compiled_props), $final, $state);
  1726. }
  1727. }, $node->props, [], $final, $state);
  1728. }
  1729. elseif ($node instanceof PHPParser_Node_Stmt_ClassConst ||
  1730. $node instanceof PHPParser_Node_Expr_ClassConstFetch)
  1731. {
  1732. return $next($node, $final, $state);
  1733. }
  1734. elseif ($node instanceof PHPParser_Node_Stmt_Throw) {
  1735. return traverseNode($node->expr, function ($expr, $final, $state) use ($next) {
  1736. return $final($state->generateThrow($expr), $state); // skip calling $next, since throw completes the function
  1737. }, $final, $state);
  1738. }
  1739. elseif ($node instanceof PHPParser_Node_Stmt_TryCatch) {
  1740. $after_num = $state->generateBlockNum();
  1741. return call_user_func($loop = function ($catches, $compiled_catches) use (&$loop, $next, $final, $state, $after_num, $node) {
  1742. if ($catch = array_shift($catches)) {
  1743. return traverseStatements($catch->stmts, function ($catch_body) use (&$loop, $compiled_catches, $catches, $catch) {
  1744. array_unshift($catch_body, new PHPParser_Node_Expr_Assign(
  1745. new PHPParser_Node_Expr_ArrayDimFetch(
  1746. new PHPParser_Node_Expr_Variable(LOCALS_NAME),
  1747. new PHPParser_Node_Scalar_String($catch->var)
  1748. ),
  1749. new PHPParser_Node_Expr_Variable(VALUE_NAME)
  1750. ));
  1751. $compiled_catches[] = new PHPParser_Node_Stmt_If(
  1752. new PHPParser_Node_Expr_Instanceof(
  1753. new PHPParser_Node_Expr_Variable(VALUE_NAME),
  1754. $catch->type
  1755. ), ['stmts' => $catch_body]);
  1756. return $loop($catches, $compiled_catches);
  1757. }, [generateJump($after_num)], $state);
  1758. }
  1759. else {
  1760. $compiled_catches = array_merge($compiled_catches, $state->generateThrow(new PHPParser_Node_Expr_Variable(VALUE_NAME)));
  1761. return traverseStatements($node->stmts, function ($try_body, $new_state) use ($next, $final, $after_num, $state) {
  1762. return $next(null, function ($after_try, $state) use ($after_num, $final, $try_body) {
  1763. $state->addBasicBlock($after_num, $after_try);
  1764. return $final($try_body, $state);
  1765. }, $state);
  1766. }, [generateJump($after_num)], $state->addCatches($compiled_catches));
  1767. }
  1768. }, $node->catches, []);
  1769. }
  1770. elseif ($node instanceof PHPParser_Node_Expr_Include) {
  1771. return generateContinuation($next, function ($continuation, $state) use ($final, $node) {
  1772. return traverseNode($node->expr, function ($file, $final, $state) use ($node) {
  1773. $is_once = ($node->type == PHPParser_Node_Expr_Include::TYPE_INCLUDE_ONCE) || ($node->type == PHPParser_Node_Expr_Include::TYPE_REQUIRE_ONCE);
  1774. $is_require = ($node->type == PHPParser_Node_Expr_Include::TYPE_REQUIRE) || ($node->type == PHPParser_Node_Expr_Include::TYPE_REQUIRE_ONCE);
  1775. $require = $is_require ? 'require' : 'include';
  1776. $once = $is_once ? '_once' : '';
  1777. $is_require = new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name($is_require ? 'true' : 'false'));
  1778. return $final([new PHPParser_Node_Stmt_Return(
  1779. new PHPParser_Node_Expr_FuncCall(new PHPParser_Node_Name($require . $once), [
  1780. new PHPParser_Node_Expr_StaticCall(new PHPParser_Node_Name('CpsRuntime'), new PHPParser_Node_Name('include_file'), [
  1781. new PHPParser_Node_Arg($file),
  1782. new PHPParser_Node_Arg(new PHPParser_Node_Scalar_String($GLOBALS['compiler'])),
  1783. new PHPParser_Node_Arg($is_require)
  1784. ])
  1785. ])
  1786. )], $state);
  1787. }, generateFinalForContinuation($final, $continuation), $state);
  1788. }, $state);
  1789. }
  1790. else {
  1791. throw new Exception('Unknown node type ' . get_class($node) . ' on line ' . $node->getLine());
  1792. }
  1793. }
  1794. // Manual trampolining to unwind tail recursion in the compiler
  1795. function run($thunk) {
  1796. $result_container = [];
  1797. $cont = function ($result) use (&$result_container) {
  1798. $result_container[] = $result;
  1799. };
  1800. while ($thunk) {
  1801. $thunk = $thunk($cont);
  1802. if (count($result_container) > 0) {
  1803. return $result_container[0];
  1804. }
  1805. }
  1806. }
  1807. // Top level way to compile code.
  1808. // Returns a transformed tree.
  1809. function compile($code) {
  1810. $parser = new PHPParser_Parser();
  1811. try {
  1812. $stmts = $parser->parse(new PHPParser_Lexer($code));
  1813. }
  1814. catch (PHPParser_Error $e) {
  1815. echo "\nParse error: " . $e->getMessage() . ' in ' . $GLOBALS['file'] . "\n";
  1816. exit(255);
  1817. }
  1818. return run(function ($cont) use ($stmts) {
  1819. $state = new FunctionState();
  1820. return traverseStatements($stmts, $cont, generateReturn(new PHPParser_Node_Scalar_LNumber(1), $state), $state, true);
  1821. });
  1822. }
  1823. function generateTrampoline($stmts) {
  1824. return [new PHPParser_Node_Stmt_Return(
  1825. new PHPParser_Node_Expr_StaticCall(new PHPParser_Node_Name('CpsRuntime'), new PHPParser_Node_Name('trampoline'), [
  1826. new PHPParser_Node_Expr_Closure([
  1827. 'params' => [
  1828. new PHPParser_Node_Param(CONT_NAME),
  1829. new PHPParser_Node_Param(EXCEPT_NAME)
  1830. ],
  1831. 'uses' => [
  1832. new PHPParser_Node_Expr_ClosureUse(LOCALS_NAME, true),
  1833. new PHPParser_Node_Expr_ClosureUse(TEMP_NAME),
  1834. new PHPParser_Node_Expr_ClosureUse(LABELS_NAME)
  1835. ],
  1836. 'stmts' => $stmts
  1837. ])
  1838. ])
  1839. )];
  1840. }
  1841. function generateThunk($uses, $stmts, $type) {
  1842. $result = new PHPParser_Node_Stmt_Return(
  1843. new PHPParser_Node_Expr_Closure([
  1844. 'uses' => $uses,
  1845. 'stmts' => $stmts
  1846. ])
  1847. );
  1848. if ($max_stack = config('trampoline_max_stack')) {
  1849. return new PHPParser_Node_Stmt_If(
  1850. new PHPParser_Node_Expr_Smaller(
  1851. new PHPParser_Node_Expr_PreInc(
  1852. new PHPParser_Node_Expr_StaticPropertyFetch(
  1853. new PHPParser_Node_Name('CpsRuntime'),
  1854. 'stack_depth'
  1855. )
  1856. ),
  1857. new PHPParser_Node_Scalar_LNumber(config('trampoline_max_stack'))
  1858. ),
  1859. [
  1860. 'stmts' => $stmts,
  1861. 'else' => new PHPParser_Node_Stmt_Else([
  1862. new PHPParser_Node_Expr_Assign(
  1863. new PHPParser_Node_Expr_StaticPropertyFetch(
  1864. new PHPParser_Node_Name('CpsRuntime'),
  1865. 'stack_depth'
  1866. ),
  1867. new PHPParser_Node_Scalar_LNumber(0)
  1868. ),
  1869. $result
  1870. ])
  1871. ]
  1872. );
  1873. }
  1874. return $result;
  1875. }