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

----------------- v PHP v -----------------
$_ = &$DATA;

// {@fn F A,B}
$DATA['F']= function()use(&$_){
	$DATA = array(
		'argv' => func_get_args(),
		'argc' => func_num_args(),
		'VERSION' => '0.82',
		'EOL' => PHP_EOL,
		'PARENT' => &$_
	);
	$_ = &$DATA;
	$DATA["A"] = &$DATA["argv"][0];
	$DATA["B"] = &$DATA["argv"][1];


	// {@whileA is notmultipleB}
	while(!(!((isset($DATA['A'])?$DATA['A']:null) % (isset($DATA['B'])?$DATA['B']:null)))){

		// {@incA}
		$DATA['A'] = SimpleTemplate_FN::call('inc', isset($DATA['A'])?$DATA['A']:0, 1);


	// {@/}
	};

	// {@returnA}
	return (isset($DATA['A'])?$DATA['A']:null);

// {@/}
};

// {@fn Fn numberA, numberB}
$DATA['Fn']= function()use(&$_){
	$DATA = array(
		'argv' => func_get_args(),
		'argc' => func_num_args(),
		'VERSION' => '0.82',
		'EOL' => PHP_EOL,
		'PARENT' => &$_
	);
	$_ = &$DATA;
	$DATA["numberA"] = &$DATA["argv"][0];
	$DATA["numberB"] = &$DATA["argv"][1];


	// {@while numberA is not multiple of numberB}
	while(!(!((isset($DATA['numberA'])?$DATA['numberA']:null) % (isset($DATA['numberB'])?$DATA['numberB']:null)))){

		// {@inc by 1 numberA}
		$DATA['numberA'] = SimpleTemplate_FN::call('inc', isset($DATA['numberA'])?$DATA['numberA']:0, 1);


	// {@/}
	};

	// {@return numberA}
	return (isset($DATA['numberA'])?$DATA['numberA']:null);

// {@/}
};

// {@call F into result 5, 3}
$DATA['result'] = call_user_func_array(
	(isset($DATA['F']) && is_callable($DATA['F'])
		? $DATA['F']
		: (SimpleTemplate_FN::method_exists('F')
			? SimpleTemplate_FN::call('F')
			: 'F'
		)
	),
	array(5, 3)
);

// {@echo result}
echo implode('', SimpleTemplate_FN::call('array_flat', (isset($DATA['result'])?$DATA['result']:null)));