fork download
  1. <?php
  2.  
  3. final class SimpleTemplate_SyntaxError extends \Exception {
  4. function __construct($message) {
  5. $this->message = 'SimpleTemplate - Syntax Error: ' . $message;
  6. }
  7. }
  8.  
  9. // contains all functions needed
  10. final class SimpleTemplate_FN {
  11. private static $fn = array();
  12. private static $init = false;
  13.  
  14. private static function init(){
  15. self::$init = true;
  16.  
  17. self::$fn = array(
  18. // array_flat -> http://stackoverflow.com/a/1320156
  19. 'array_flat' => function(){
  20. $return = array();
  21. $array = func_get_args();
  22. array_walk_recursive($array, function($value)use(&$return){
  23. $return[] = $value;
  24. });
  25. return $return;
  26. },
  27. 'inc' => function($_, $by = 1){
  28. // if there's no increment value
  29. if(!$by)
  30. {
  31. return $_;
  32. }
  33.  
  34. // if there's no value
  35. if(!$_)
  36. {
  37. return $by;
  38. }
  39.  
  40. $fn = function($_, $by){
  41. switch(gettype($_))
  42. {
  43. case 'NULL':
  44. case 'null':
  45. return $by;
  46. case 'integer':
  47. case 'double':
  48. case 'float':
  49. return $_ + $by;
  50. case 'string':
  51. if($_ === '')
  52. {
  53. return '';
  54. }
  55.  
  56. $_by = abs($by);
  57.  
  58. for($i = 0; $i < $_by; $i++)
  59. {
  60. if($by > 0)
  61. {
  62. ++$_;
  63. }
  64. else
  65. {
  66. $last = strlen($_) - 1;
  67.  
  68. if($_[$last] === 'a' || $_[$last] === 'A')
  69. {
  70. // handles aaaa -> zzz
  71. $_ = preg_replace_callback('@[aA]+$@', function($str){
  72. return str_repeat($str[0][0] === 'a' ? 'z': 'Z', strlen($str[0]) - 1);
  73. }, $_);
  74. }
  75. else
  76. {
  77. $_[$last] = chr(ord($_[$last]) - 1);
  78. }
  79. }
  80. }
  81.  
  82. return $_;
  83. default:
  84. return $by;
  85. }
  86. };
  87.  
  88.  
  89. if(gettype($_) === 'array')
  90. {
  91. array_walk_recursive($_, function(&$value)use(&$fn, &$by){
  92. $value = $fn($value, $by);
  93. });
  94. }
  95. else
  96. {
  97. $_ = $fn($_, $by);
  98. }
  99.  
  100. return $_;
  101. },
  102. 'len' => function($args){
  103. $result = array();
  104.  
  105. if(func_num_args() > 1)
  106. {
  107. $args = func_get_args();
  108. }
  109. else
  110. {
  111. $args = array($args);
  112. }
  113.  
  114. foreach($args as $arg)
  115. {
  116. switch(gettype($arg))
  117. {
  118. case 'array':
  119. $result[] = count($arg);
  120. break;
  121. case 'string':
  122. $result[] = strlen($arg);
  123. break;
  124. case 'integer':
  125. case 'double':
  126. case 'float':
  127. $result[] = 0;
  128. break;
  129. default:
  130. $result[] = null;
  131. }
  132. }
  133.  
  134. return $result;
  135. },
  136. 'repeat' => function($_, $times = 1){
  137. if($times < 1)
  138. {
  139. return '';
  140. }
  141.  
  142. array_walk_recursive($_, function(&$value)use(&$times){
  143. $value = str_repeat($value, $times);
  144. });
  145.  
  146. return $_;
  147. }
  148. );
  149. }
  150.  
  151. static function call($fn, $args = array()){
  152. if(!self::$init)
  153. {
  154. self::init();
  155. }
  156.  
  157. if(!self::$fn[$fn])
  158. {
  159. throw new Exception('Invalid function ' . $fn);
  160. }
  161.  
  162. return call_user_func_array(self::$fn[$fn], $args);
  163. }
  164.  
  165. static function name_list(){
  166. if(!self::$init)
  167. {
  168. self::init();
  169. }
  170.  
  171. return array_keys(self::$fn);
  172. }
  173. }
  174.  
  175. // compiler class
  176. class SimpleTemplate_Compiler {
  177. private $uuid = null;
  178.  
  179. private static $var_name = 'DATA';
  180. private static $default_var_name = '_';
  181.  
  182. private static $regex = array(
  183. 'var' => '(?:(?:(?:U|unsafe)\s+)?[_a-zA-Z]\w*(?:\.\w*)?)',
  184. 'value' => '(?:(?:"[^"\\\\]*(?:\\\\.[^"\\\\]*)*")|[\-+]?\d*(?:\.\d*)?|true|false|null)',
  185. 'var_value' => '(?:(?:"[^"\\\\]*(?:\\\\.[^"\\\\]*)*")|[\-+]?[\d\W]\d*(?:\.\d*)?|true|false|null|(?:(?:U|unsafe)\s+)?[_a-zA-Z]\w*(?:\.\w*)*)'
  186. );
  187.  
  188. private $options = array();
  189. private $template = null;
  190.  
  191. private $fn = null;
  192. private static $fn_body = <<<'PHP'
  193. // - FUNCTION BOILERPLATE
  194. $FN = array();
  195.  
  196. array_map(function($name)use(&$FN){
  197. $FN[$name] = function()use($name){
  198. return SimpleTemplate_FN::call($name, func_get_args());
  199. };
  200. },
  201. SimpleTemplate_FN::name_list()
  202. );
  203. // - END FUNCTION BOILERPLATE -
  204.  
  205. // - CODE
  206. %s
  207. // - END CODE -
  208. PHP;
  209.  
  210. private $php = '';
  211.  
  212. private static function render_var($name = null, $safe = true){
  213. preg_match('@^\s*(?:(?<unsafe>U|unsafe)\s+)?(?<var>.*)$@', $name ?: self::$default_var_name, $bits);
  214.  
  215. $var = '$' . self::$var_name . ($bits['var'] ? '[\'' . join('\'][\'', explode('.', $bits['var'])) . '\']' : '');
  216.  
  217. return $safe && !$bits['unsafe'] ? '(isset(' . $var . ')?' . $var . ':null)' : $var;
  218. }
  219.  
  220. private static function split_values($values, $delimiter = '\s*,\s*'){
  221. // http://stackoverflow.com/a/5696141/ --> regex quoted string
  222. // http://stackoverflow.com/a/632552/ --> regex to match $delimiter outside quotes
  223. return preg_split('@(' . ($delimiter ?: '\s*,\s*') . ')(?=(?:[^"]|"[^"\\\\]*(?:\\\\.[^"\\\\]*)*")*$)@', $values);
  224. }
  225.  
  226. private static function parse_values($values, $delimiter = '\s*,\s*', $safe = true){
  227. $value_bits = self::split_values($values, $delimiter);
  228.  
  229. foreach($value_bits as $k => $value)
  230. {
  231. $value_bits[$k] = self::parse_value($value, $safe);
  232. }
  233.  
  234. return $value_bits;
  235. }
  236.  
  237. private static function parse_boolean($data){
  238. if(
  239. '@(' . self::$regex['var_value'] . ')\s*(?:(isset|is(?:(?:\s*not|n\'?t)?\s*(?:(?:greater|lower)(?:\s*than)?|equal(?:\s*to)?|equal|a|(?:(?:instance|multiple|mod)(?:\s*of)?)|matches))?|has(?:\s*not)?)\s*(' . self::$regex['var_value'] . ')?)?@',
  240. $data, $bits
  241. )
  242. )
  243. {
  244. return '';
  245. }
  246.  
  247. $fn = array(
  248. 'is' => function($data, $var1, $var2){
  249. $symbols = array(
  250. '' => '%s === %s',
  251. 'a' => 'gettype(%s) === %s',
  252. 'instance' => 'is_a(%s, %s)',
  253. 'equal' => '%s == %s',
  254. 'lower' => '%s < %s',
  255. 'greater' => '%s > %s',
  256. 'multiple' => '!(%s %% %s)',
  257. 'mod' => '%s %% %s',
  258. 'matches' => 'preg_match(%2$s, %1$s)'
  259. );
  260.  
  261. preg_match('@(?<not>not)?\s*(?<operation>equal|lower|greater|a|instance|multiple|mod|matches)?\s*(?:of|to|than)?\s*@', $data, $bits);
  262.  
  263. return (isset($bits['not']) && $bits['not'] !== '' ? '!': '') . '(' . sprintf($symbols[isset($bits['operation']) ? $bits['operation']: ''], self::parse_value($var1), self::parse_value($var2)) . ')';
  264. },
  265. 'has' => function($data, $var1, $var2){
  266. return ($data === 'not' ? '!': '') . 'array_key_exists((array)' . self::parse_value($var1) . ', ' . self::parse_value($var2) . ')';
  267. },
  268. 'isset' => function($data, $var1){
  269. return ($data === 'not' ? '!': '') . 'isset(' . self::render_var($var1, false) . ')';
  270. }
  271. );
  272.  
  273. if(isset($bits[2]))
  274. {
  275. $ops = explode(' ', $bits[2], 2);
  276.  
  277. return $fn[$ops[0]](isset($ops[1]) ? $ops[1]: '', $bits[1], isset($bits[3]) ? $bits[3] : self::$default_var_name);
  278. }
  279. else
  280. {
  281. return self::parse_value($bits[1]);
  282. }
  283. }
  284.  
  285. private static function parse_value($value, $safe = true){
  286. if($value === '' || $value === '""')
  287. {
  288. return $value;
  289. }
  290. else if(preg_match('@^' . self::$regex['value'] . '$@', $value))
  291. {
  292. return $value[0] === '"'
  293. ? str_replace('$', '\\$', $value)
  294. : $value;
  295. }
  296. else if(preg_match('@^' . self::$regex['var'] . '$@', $value))
  297. {
  298. return self::render_var($value, $safe);
  299. }
  300. else
  301. {
  302. throw new SimpleTemplate_SyntaxError('Invalid value syntax: ' . $value);
  303. }
  304. }
  305.  
  306. private static function is_value($value){
  307. return strlen($value) && $value[0] !== '$' && $value[0] !== '(';
  308. }
  309.  
  310. private function format_code($code, $tabs, $skip_first = false, $skip_last = false){
  311. $lines = preg_split("@(?:\r?\n|\r)+@", $code);
  312. $heredoc_closing = self::$var_name . $this->uuid;
  313.  
  314. $return = $skip_first ? array_shift($lines) : '';
  315. $last = $skip_last ? PHP_EOL . array_pop($lines): '';
  316.  
  317. foreach($lines as $line)
  318. {
  319. if($return)
  320. {
  321. $return .= PHP_EOL;
  322. }
  323.  
  324. if($line === $heredoc_closing)
  325. {
  326. $return .= $heredoc_closing;
  327. }
  328. else
  329. {
  330. $return .= (
  331. preg_match('@^\s*\)+;?\s*$@', $line)
  332. ? substr($tabs, 1)
  333. : $tabs
  334. ). ltrim($line);
  335. }
  336. }
  337.  
  338. return $return . $last;
  339. }
  340.  
  341. private function compile($str){
  342. $UUID = $this->uuid;
  343.  
  344. $brackets = 0;
  345. $tabs = '';
  346.  
  347. $replacement = array(
  348. '/' => function($data)use(&$replacement, &$brackets, &$tabs){
  349. if($brackets > 0)
  350. {
  351. --$brackets;
  352.  
  353. return $tabs . '};';
  354. }
  355. else
  356. {
  357. return $tabs . ';';
  358. }
  359. },
  360. '//' => function(){
  361. return '';
  362. },
  363. 'echo' => function($data)use(&$replacement, &$brackets, &$tabs){
  364. preg_match('@^(?:separator\s+(?<separator>' . self::$regex['var_value'] . ')\s+)?(?<data>.*)$@', $data, $bits);
  365.  
  366. $separator = $bits['separator'] ? self::parse_value($bits['separator']): '\'\'';
  367.  
  368. return $tabs . 'echo implode(' . $separator . ', $FN[\'array_flat\'](' . implode(', ', self::parse_values($bits['data'])) . '));';
  369. },
  370. 'echol' => function($data)use(&$replacement, &$brackets, &$tabs){
  371. return $replacement['echo']($data) . 'echo PHP_EOL;';
  372. },
  373. 'echoj' => function($data)use(&$replacement, &$brackets, &$tabs){
  374. return $replacement['echo']('separator ' . $data);
  375. },
  376. 'echojl' => function($data)use(&$replacement, &$brackets, &$tabs){
  377. return $replacement['echol']('separator ' . $data);
  378. },
  379. 'echof' => function($data)use(&$replacement, &$brackets, &$tabs){
  380. return $replacement['echol']('separator ' . $data);
  381. },
  382. 'print' => function($data)use(&$replacement, &$brackets, &$tabs){
  383. return $replacement['call']((strpos('into', $data)===0? 's' : '') . 'printf ' . $data);
  384. },
  385. 'if' => function($data)use(&$replacement, &$brackets, &$tabs){
  386. ++$brackets;
  387.  
  388. return $tabs . 'if(' . self::parse_boolean($data) . ') {';
  389. },
  390. 'else' => function($data)use(&$replacement, &$brackets, &$tabs){
  391. preg_match('@(?<if>if)(?<data>.*)@', $data, $bits);
  392.  
  393. $return = substr($tabs, 1) . '} else';
  394.  
  395. if(isset($bits['if']) && isset($bits['data']))
  396. {
  397. --$brackets;
  398.  
  399. $return .= ltrim($replacement['if'](trim($bits['data'])));
  400. }
  401. else
  402. {
  403. $return .= ' {';
  404. }
  405.  
  406. return $return;
  407. },
  408. 'each' => function($data)use(&$replacement, &$brackets, &$tabs){
  409. ++$brackets;
  410.  
  411. preg_match('@^(?<var>' . self::$regex['var'] . ')\s*(?:as\s*(?<as>' . self::$regex['var'] . ')(?:\s*key\s*(?<key>' . self::$regex['var'] . ')\s*)?)?$@', $data, $bits);
  412.  
  413. static $count = 0;
  414.  
  415. $var_name = self::$var_name;
  416. $tmp_name = 'tmp_' . (++$count) . '_';
  417.  
  418. $vars_var = self::render_var($bits['var'], false);
  419. $vars_as = self::render_var(isset($bits['as']) ? $bits['as'] : '', false);
  420. $vars_key = self::render_var(isset($bits['key']) ? $bits['as'] : '__', false);
  421.  
  422. return <<<PHP
  423. {$tabs}// loop variables
  424. {$tabs}\${$tmp_name}val = isset({$vars_var}) ? \${$tmp_name}val = &{$vars_var} : null;
  425. {$tabs}\${$tmp_name}keys = gettype({$vars_var}) == 'array'
  426. {$tabs} ? array_keys({$vars_var})
  427. {$tabs} : array_keys(range(0, strlen(\${$tmp_name}val = \${$tmp_name}val . '') - 1));
  428. {$tabs}\${$tmp_name}key_last = end(\${$tmp_name}keys);
  429. {$tabs}
  430. {$tabs}// loop
  431. {$tabs}foreach(\${$tmp_name}keys as \${$tmp_name}index => \${$tmp_name}key){
  432. {$tabs} \${$var_name}['loop'] = array(
  433. {$tabs} 'index' => \${$tmp_name}index,
  434. {$tabs} 'i' => \${$tmp_name}index,
  435. {$tabs} 'key' => \${$tmp_name}key,
  436. {$tabs} 'k' => \${$tmp_name}key,
  437. {$tabs} 'value' => \${$tmp_name}val[\${$tmp_name}key],
  438. {$tabs} 'v' => \${$tmp_name}val[\${$tmp_name}key],
  439. {$tabs} 'first' => \${$tmp_name}key === \${$tmp_name}keys[0],
  440. {$tabs} 'last' => \${$tmp_name}key === \${$tmp_name}key_last,
  441. {$tabs} );
  442. {$tabs} {$vars_key} = \${$tmp_name}key;
  443. {$tabs} {$vars_as} = \${$tmp_name}val[\${$tmp_name}key];
  444. PHP;
  445. },
  446. 'while' => function($data)use(&$replacement, &$brackets, &$tabs){
  447. ++$brackets;
  448.  
  449. return $tabs . 'while(' . self::parse_boolean($data) . '){';
  450. },
  451. 'for' => function($data)use(&$replacement, &$brackets, &$tabs){
  452. ++$brackets;
  453.  
  454. '@(?<var>' . self::$regex['var'] . ')?\s*(?:from\s*(?<start>' . self::$regex['var_value'] . '))?(?:\s*to\s*(?<end>' . self::$regex['var_value'] . '))(?:\s*step\s*(?<step>' . self::$regex['var_value'] . '))?@',
  455. function($matches)use(&$replacement, &$brackets, &$tabs){
  456.  
  457. $values = array(
  458. 'start' => isset($matches['start']) && $matches['start'] !== '' ? self::parse_value($matches['start']) : '0',
  459. 'end' => isset($matches['end']) ? self::parse_value($matches['end']) : self::parse_value($matches['start']),
  460. 'step' => isset($matches['step']) ? self::parse_value($matches['step']) : '1'
  461. );
  462.  
  463. $return = $tabs . 'foreach(';
  464.  
  465. if(self::is_value($values['start']) && self::is_value($values['end']) && self::is_value($values['step']))
  466. {
  467. if($this->options['optimize'])
  468. {
  469. $return = "{$tabs}// ~ optimization enabled ~ inlining the results\r\n{$return}" . self::format_code(
  470. preg_replace('@^"|"$@', '', $values['start']),
  471. preg_replace('@^"|"$@', '', $values['end']),
  472. abs($values['step'])
  473. ),
  474. true
  475. ),
  476. $tabs . "\t",
  477. true
  478. );
  479. }
  480. else
  481. {
  482. $return = "{$tabs}// ~ optimization DISABLED ~ results could be inlined\r\n{$return}range({$values['start']}, {$values['end']}, abs({$values['step']}))";
  483. }
  484. }
  485. else
  486. {
  487. $return .= 'range(' . $values['start'] . ', ' . $values['end'] . ', abs(' . $values['step'] . '))';
  488. }
  489.  
  490. return $return . ' as ' . self::render_var(isset($matches['var']) ? $matches['var'] : '', false) . '){';
  491. },
  492. $data
  493. );
  494. },
  495. 'do' => function($data)use(&$replacement, &$brackets, &$tabs){
  496. ++$brackets;
  497.  
  498. return $tabs . 'do{';
  499. },
  500. 'until' => function($data)use(&$replacement, &$brackets, &$tabs){
  501. --$brackets;
  502.  
  503. return substr($tabs, 1) . '}while(!(' . self::parse_boolean($data) . '));';
  504. },
  505. 'set' => function($data)use(&$replacement, &$brackets, &$tabs){
  506. preg_match('@^\s*(?<op>[\+\-\*\\\/\%])?\s*(?<var>' . self::$regex['var'] . ')\s*(?:(?<op_val>' . self::$regex['var_value'] . ')\s)?\s*(?<values>.*)$@', $data, $bits);
  507.  
  508. $values = self::parse_values($bits['values']);
  509. $count = count($values);
  510.  
  511. $return = $tabs . self::render_var($bits['var'], false) . ' = ';
  512.  
  513. $close = 0;
  514.  
  515. if(isset($bits['op']))
  516. {
  517. switch($bits['op'])
  518. {
  519. case '-':
  520. $return .= <<<PHP
  521. call_user_func_array(function(){
  522. {$tabs} \$args = func_get_args();
  523. {$tabs} \$initial = array_shift(\$args);
  524. {$tabs} return array_reduce(\$args, function(\$carry, \$value){
  525. {$tabs} return \$carry - \$value;
  526. {$tabs} }, \$initial);
  527. {$tabs}}, \$FN['array_flat'](
  528. PHP;
  529. $close = 2;
  530. break;
  531. case '+':
  532. $return .= 'array_sum($FN[\'array_flat\'](';
  533. $close = 2;
  534. break;
  535. case '*':
  536. $return .= 'array_product($FN[\'array_flat\'](';
  537. $close = 2;
  538. break;
  539. case '\\':
  540. case '/':
  541. case '%':
  542. $ops = array(
  543. '\\' => 'round(%s / $value)',
  544. '/' => '(%s / $value)',
  545. '%' => '(%s %% $value)'
  546. );
  547.  
  548. $return .= 'array_map(function($value)use(&$' . self::$var_name . '){'
  549. .'return ' . sprintf(
  550. $ops[$bits['op']],
  551. isset($bits['op_val'])
  552. ? self::parse_value($bits['op_val'])
  553. : self::render_var($bits['var'], false)
  554. )
  555. . ';}, $FN[\'array_flat\'](';
  556. $close = 2;
  557. break;
  558. }
  559. }
  560.  
  561. if($count > 1)
  562. {
  563. $return .= 'array(' . implode(',', $values) . ')';
  564. }
  565. else
  566. {
  567. $return .= ($count && strlen($values[0]) ? $values[0] : 'null');
  568. }
  569.  
  570. return $return . str_repeat(')', $close) . ';';
  571. },
  572. 'unset' => function($data)use(&$replacement, &$brackets, &$tabs){
  573. $values = self::parse_values($data, null, false);
  574. $vars = array_filter($values, function($var){
  575. return !self::is_value($var);
  576. });
  577.  
  578. $return = $tabs . (
  579. count($values) !== count($vars)
  580. ? '// Warning: invalid values were passed' . PHP_EOL . $tabs
  581. : ''
  582. );
  583.  
  584. return $return . (
  585. $vars
  586. ? 'unset(' . implode(',', $vars) . ');'
  587. : '// Warning: no values were passed or all were filtered out'
  588. );
  589. },
  590. 'global' => function($data)use(&$replacement, &$brackets, &$tabs){
  591. $data = self::split_values($data, ' ');
  592.  
  593. return $tabs . self::render_var(array_shift($data), false) . ' = $GLOBALS[\'' . join('\'][\'', $data) . '\'];';
  594. },
  595. 'call' => function($data)use(&$replacement, &$brackets, &$tabs){
  596. preg_match('@^\s*(?<fn>' . self::$regex['var'] . ')\s*(?:into\s*(?<into>' . self::$regex['var'] . ')\s*)?(?<args>.*?)$@', $data, $bits);
  597.  
  598. $var = self::render_var($bits['fn'], false);
  599.  
  600. return $tabs . ($bits['into'] ? self::render_var($bits['into'], false) . ' = ' : '')
  601. . 'call_user_func_array('
  602. . 'isset(' . $var . ') && is_callable(' . $var . ')'
  603. . '? ' . $var
  604. . ': (isset($FN["' . $bits['fn'] . '"])'
  605. . '? $FN["' . $bits['fn'] . '"]'
  606. .': "' . str_replace('.', '_', $bits['fn']) . '"'
  607. . '), '
  608. . 'array(' . implode(',', self::parse_values($bits['args'])) . '));';
  609. },
  610. 'php' => function($data)use(&$replacement, &$brackets, &$tabs){
  611. return $tabs . 'call_user_func_array(function($FN, &$' . self::$var_name . '){' . PHP_EOL
  612. . "{$tabs}\t{$data};" . PHP_EOL
  613. . $tabs . '}, array($FN, &$' . self::$var_name . '));';
  614. },
  615. 'return' => function($data)use(&$replacement, &$brackets, &$tabs){
  616. return $tabs . 'return ' . ($data ? self::parse_value($data): '').';';
  617. },
  618. 'inc' => function($data)use(&$replacement, &$brackets, &$tabs){
  619. preg_match('@^(?:\s*by\s*(?<by>' . self::$regex['var_value'] . ')\s*)?(?<values>.*?)$@', $data, $bits);
  620. $values = self::parse_values($bits['values'], '\s*,\s*', false);
  621. $inc = isset($bits['by']) && $bits['by'] !== '' ? self::parse_value($bits['by']): '1';
  622.  
  623. $return = '';
  624.  
  625. if(!$inc || $inc === '"0"' || $inc === 'null' || $inc === 'false')
  626. {
  627. if($this->options['optimize'])
  628. {
  629. return "{$tabs}// ~ optimization enabled ~ increment by {$inc} removed";
  630. }
  631. else
  632. {
  633. $return .= "{$tabs}// ~ optimization DISABLED ~ increment by {$inc} could be removed" . PHP_EOL;
  634. }
  635. }
  636.  
  637. $var_name = self::$var_name;
  638.  
  639. foreach($values as $value)
  640. {
  641. if(!isset($value[0]) && !self::is_value($value[0]))
  642. {
  643. continue;
  644. }
  645.  
  646. $return .= "{$tabs}{$value} = \$FN['inc'](isset({$value})?{$value}:0, {$inc});" . PHP_EOL;
  647. }
  648.  
  649. return $return;
  650.  
  651. },
  652. 'fn' => function($data)use(&$replacement, &$brackets, &$tabs){
  653. if(
  654. '@^\s*(' . self::$regex['var'] . ')\s*(?:\s+(.*))?$@',
  655. $data, $bits
  656. ) === false
  657. )
  658. {
  659. return '';
  660. }
  661.  
  662. ++$brackets;
  663.  
  664. $version = SimpleTemplate::version();
  665. $var_name = self::$var_name;
  666.  
  667. $return = $tabs . self::render_var($bits[1], false) . <<<PHP
  668.  = function()use(&\$FN, &\$_){
  669. {$tabs} \${$var_name} = array(
  670. {$tabs} 'argv' => func_get_args(),
  671. {$tabs} 'argc' => func_num_args(),
  672. {$tabs} 'VERSION' => '{$version}',
  673. {$tabs} 'EOL' => PHP_EOL,
  674. {$tabs} 'PARENT' => &\$_
  675. {$tabs} );
  676. {$tabs} \$_ = &\${$var_name};
  677.  
  678. PHP;
  679. if(isset($bits[2]) && $bits[2])
  680. {
  681. $args = array();
  682. foreach(self::split_values($bits[2]) as $value)
  683. {
  684. if(!self::is_value(self::parse_value($value)))
  685. {
  686. $args[] = $value;
  687. }
  688. }
  689.  
  690. foreach($args as $k => $arg)
  691. {
  692. $return .= "{$tabs} \${$var_name}[\"{$arg}\"] = &\${$var_name}[\"argv\"][{$k}];" . PHP_EOL;
  693. }
  694. }
  695.  
  696. return $return;
  697. },
  698. 'eval' => function($data)use(&$replacement, &$brackets, &$tabs){
  699. $return = '';
  700. $value = self::parse_value($data);
  701.  
  702. if($this->options['optimize'] && self::is_value($value))
  703. {
  704. $return = $tabs . '// ~ optimization enabled ~ trying to avoid compiling in runtime' . PHP_EOL;
  705.  
  706. static $cached = array();
  707.  
  708. $sha1 = sha1($value);
  709.  
  710. if(isset($cached[$sha1]))
  711. {
  712. $return .= $tabs . '// {@eval} cached code found: cache entry ';
  713. }
  714. else
  715. {
  716. $return .= $tabs . '// {@eval} no cached code found: creating entry ';
  717.  
  718. $compiler = new SimpleTemplate_Compiler($this->template, stripslashes(trim($value, '"')), $this->options);
  719.  
  720. $cached[$sha1] = self::format_code($compiler->getPHP() . '// {@eval} ended', $tabs);
  721.  
  722. unset($compiler);
  723. }
  724.  
  725. $return .= $sha1 . PHP_EOL . $cached[$sha1];
  726. }
  727. else
  728. {
  729. $options = self::format_code(var_export($this->options, true), $tabs . "\t\t", true);
  730.  
  731. $return = <<<PHP
  732. {$tabs}// ~ optimization DISABLED or unfeasable ~ compilation in runtime is required
  733. {$tabs}call_user_func_array(function()use(&\$FN, &\$DATA){
  734. {$tabs} \$compiler = new SimpleTemplate_Compiler(\$this, {$value}, {$options});
  735. {$tabs} \$fn = \$compiler->getFN();
  736. {$tabs} return \$fn(\$DATA);
  737. {$tabs}}, array());
  738. PHP;
  739. }
  740.  
  741. return $return;
  742. }
  743. );
  744.  
  745. $trim_fn = $this->options['trim'] ? 'trim' : '';
  746.  
  747. $this->php .= "\r\necho {$trim_fn}(<<<'" . self::$var_name . "{$UUID}'\r\n"
  748. // http://stackoverflow.com/a/6464500
  749. '~{@(eval|echoj?l?|print|if|else|for|while|each|do|until|(?:un)?set|call|global|php|return|inc|fn|//?)(?:\\s*(.*?))?}(?=(?:[^"\\\\]*(?:\\\\.|"(?:[^"\\\\]*\\\\.)*[^"\\\\]*"))*[^"]*$)~',
  750. function($matches)use(&$replacement, &$brackets, &$tabs, &$UUID, &$trim_fn){
  751.  
  752. $tabs = $brackets
  753. ? str_repeat("\t", $brackets - ($matches[1] === '/'))
  754. : '';
  755.  
  756. $var_name = self::$var_name;
  757.  
  758. $php = $replacement[$matches[1]](isset($matches[2]) ? $matches[2] : null);
  759.  
  760. return "\r\n{$var_name}{$UUID}\r\n);\r\n{$tabs}// {$matches[0]}\r\n{$php}\r\n\r\n{$tabs}echo {$trim_fn}(<<<'{$var_name}{$UUID}'\r\n";
  761. },
  762. $str . ''
  763. )
  764. . "\r\n" . self::$var_name . "{$UUID}\r\n);\r\n";
  765.  
  766. $this->php = preg_replace(
  767. '@\r\n\t*echo\s*' . $trim_fn . '\(<<<\'' . self::$var_name . $UUID . '\'(?:\s*\r\n)?' . self::$var_name . $UUID . '\r\n\);@',
  768. '@\r\n' . self::$var_name . $UUID . '\r\n\);(\r\n)*\t*echo\s*' . $trim_fn . '\(<<<\'' . self::$var_name . $UUID . '\'@'
  769. ),
  770. '', ''
  771. ),
  772. $this->php
  773. );
  774.  
  775. if($brackets)
  776. {
  777. $this->php .= "\r\n// AUTO-CLOSE\r\n" . str_repeat('};', $brackets);
  778. }
  779. }
  780.  
  781. function getPHP(){
  782. return $this->php;
  783. }
  784.  
  785. function getFN(){
  786. if(!$this->fn)
  787. {
  788. $this->fn = eval('return function(&$' . self::$var_name . '){'
  789. . PHP_EOL
  790. . sprintf(self::$fn_body, $this->php)
  791. . PHP_EOL
  792. . '};'
  793. );
  794.  
  795. $this->fn = $this->fn->bindTo($this->template);
  796. }
  797.  
  798. return $this->fn;
  799. }
  800.  
  801. function __construct(SimpleTemplate $template, $code, array $options = array()){
  802. $this->options = $options;
  803. $this->template = $template;
  804.  
  805. // ALMOST unguessable name, to avoid syntax errors
  806. $this->uuid = str_shuffle(mt_rand() . time() . sha1($code));
  807.  
  808. $this->compile($code);
  809. }
  810. }
  811.  
  812. // base class
  813. class SimpleTemplate {
  814. private static $version = '0.62';
  815.  
  816. private $data = array();
  817. private $settings = array(
  818. 'optimize' => true,
  819. 'trim' => false
  820. );
  821.  
  822. private $compiler = null;
  823.  
  824. function __construct($code, array $options = array()){
  825. if(!$code)
  826. {
  827. throw new Exception('No code was provided');
  828. }
  829.  
  830. if($options)
  831. {
  832. $this->settings = array_merge($this->settings, $options);
  833. }
  834.  
  835. $this->compiler = new SimpleTemplate_Compiler($this, $code, $this->settings);
  836. }
  837.  
  838. function setData($key, $value){
  839. $this->data[$key] = $value;
  840. }
  841.  
  842. function getData($key, $value){
  843. return isset($this->data[$key]) ? $this->data[$key] : null;
  844. }
  845.  
  846. function unsetData($key){
  847. unset($this->data[$key]);
  848. }
  849.  
  850. function loadData($data){
  851. foreach($data as $k => $value)
  852. {
  853. $this->data[$k] = $value;
  854. }
  855. }
  856.  
  857. function clearData(){
  858. $this->data = array();
  859. }
  860.  
  861. function getPHP(){
  862. return $this->compiler->getPHP();
  863. }
  864.  
  865. function render(){
  866. $this->data['argv'] = func_get_args();
  867. $this->data['argc'] = func_num_args();
  868.  
  869. $this->data['VERSION'] = self::$version;
  870. $this->data['EOL'] = PHP_EOL;
  871.  
  872. $fn = $this->compiler->getFN();
  873.  
  874. return $fn($this->data);
  875. }
  876.  
  877. static function fromFile($path, array $options = array()){
  878. return new self(file_get_contents($path), $options);
  879. }
  880.  
  881. static function fromString($string, array $options = array()){
  882. return new self($string, $options);
  883. }
  884.  
  885. static function version(){
  886. return self::$version;
  887. }
  888. }
  889.  
  890. $c = <<<'CODE'
  891. {@setA argv.1}{@eachargv.0}{@setC C,"{@echoA.",_,"}"}{@calljoin intoC"",C}{@/}{@evalC}
  892. CODE;
  893.  
  894. /*
  895. ungolfed:
  896. $c = <<<'CODE'
  897. {@set args argv.1}
  898. {@each argv.0 as number}
  899. {@set code code, "{@echo args.", number , "}"}
  900. {@call join into code "", code}
  901. {@/}
  902. {@eval code}
  903. CODE;*/
  904.  
  905. $x = new SimpleTemplate($c);
  906.  
  907. echo $x->render('0123456789', 'abcdefghijkl');
Success #stdin #stdout 0.03s 23876KB
stdin
Standard input is empty
stdout
abcdefghij