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_value'] . ')\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::parse_value(isset($bits['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. if(self::is_var($vars_var))
  490. {
  491. return <<<PHP
  492. {$tabs}// ~ optimization unfeasable ~ variable used in the first argument
  493. {$tabs}// loop variables - variable
  494. {$tabs}\${$tmp_name}val = isset({$vars_var}) ? \${$tmp_name}val = &{$vars_var} : null;
  495. {$tabs}\${$tmp_name}keys = gettype({$vars_var}) == 'array'
  496. {$tabs} ? array_keys({$vars_var})
  497. {$tabs} : ((\${$tmp_name}val = \${$tmp_name}val . '')
  498. {$tabs} ? array_keys(range(0, strlen(\${$tmp_name}val = \${$tmp_name}val . '') - 1))
  499. {$tabs} : array()
  500. {$tabs} );
  501. {$tabs}\${$tmp_name}key_last = end(\${$tmp_name}keys);
  502. {$tabs}
  503. {$tabs}// loop
  504. {$tabs}foreach(\${$tmp_name}keys as \${$tmp_name}index => \${$tmp_name}key){
  505. {$tabs} \${$var_name}['loop'] = array(
  506. {$tabs} 'index' => \${$tmp_name}index,
  507. {$tabs} 'i' => \${$tmp_name}index,
  508. {$tabs} 'key' => \${$tmp_name}key,
  509. {$tabs} 'k' => \${$tmp_name}key,
  510. {$tabs} 'value' => \${$tmp_name}val[\${$tmp_name}key],
  511. {$tabs} 'v' => \${$tmp_name}val[\${$tmp_name}key],
  512. {$tabs} 'first' => \${$tmp_name}key === \${$tmp_name}keys[0],
  513. {$tabs} 'last' => \${$tmp_name}key === \${$tmp_name}key_last,
  514. {$tabs} );
  515. {$tabs} {$vars_key} = \${$tmp_name}key;
  516. {$tabs} {$vars_as} = \${$tmp_name}val[\${$tmp_name}key];
  517. PHP;
  518. }
  519. else if($this->options['optimize'])
  520. {
  521. $keys = '';
  522.  
  523. if($vars_var !== '""')
  524. {
  525. $keys = implode(
  526. ',',
  527. 0,
  528. strlen($vars_var) - ($vars_var[0] === '"' ? 3 : 1)
  529. )
  530. );
  531. }
  532.  
  533. return <<<PHP
  534. {$tabs}// ~ optimization ENABLED ~ \${$tmp_name}keys was inlined
  535. {$tabs}// loop variables - inline value
  536. {$tabs}\${$tmp_name}val = ({$vars_var}) . ''; // convert to string
  537. {$tabs}\${$tmp_name}keys = array({$keys});
  538. {$tabs}\${$tmp_name}key_last = end(\${$tmp_name}keys);
  539. {$tabs}
  540. {$tabs}// loop
  541. {$tabs}foreach(\${$tmp_name}keys as \${$tmp_name}index => \${$tmp_name}key){
  542. {$tabs} \${$var_name}['loop'] = array(
  543. {$tabs} 'index' => \${$tmp_name}index,
  544. {$tabs} 'i' => \${$tmp_name}index,
  545. {$tabs} 'key' => \${$tmp_name}key,
  546. {$tabs} 'k' => \${$tmp_name}key,
  547. {$tabs} 'value' => \${$tmp_name}val[\${$tmp_name}key],
  548. {$tabs} 'v' => \${$tmp_name}val[\${$tmp_name}key],
  549. {$tabs} 'first' => \${$tmp_name}key === \${$tmp_name}keys[0],
  550. {$tabs} 'last' => \${$tmp_name}key === \${$tmp_name}key_last,
  551. {$tabs} );
  552. {$tabs} {$vars_key} = \${$tmp_name}key;
  553. {$tabs} {$vars_as} = \${$tmp_name}val[\${$tmp_name}key];
  554. PHP;
  555. }
  556. else
  557. {
  558. return <<<PHP
  559. {$tabs}// ~ optimization DISABLED ~ \${$tmp_name}keys could be inlined
  560. {$tabs}// loop variables - inline value
  561. {$tabs}\${$tmp_name}val = ({$vars_var}) . ''; // convert to string
  562. {$tabs}\${$tmp_name}keys = \${$tmp_name}val
  563. {$tabs} ? array_keys(range(0, strlen(\${$tmp_name}val) - 1))
  564. {$tabs} : array();
  565. {$tabs}\${$tmp_name}key_last = end(\${$tmp_name}keys);
  566. {$tabs}
  567. {$tabs}// loop
  568. {$tabs}foreach(\${$tmp_name}keys as \${$tmp_name}index => \${$tmp_name}key){
  569. {$tabs} \${$var_name}['loop'] = array(
  570. {$tabs} 'index' => \${$tmp_name}index,
  571. {$tabs} 'i' => \${$tmp_name}index,
  572. {$tabs} 'key' => \${$tmp_name}key,
  573. {$tabs} 'k' => \${$tmp_name}key,
  574. {$tabs} 'value' => \${$tmp_name}val[\${$tmp_name}key],
  575. {$tabs} 'v' => \${$tmp_name}val[\${$tmp_name}key],
  576. {$tabs} 'first' => \${$tmp_name}key === \${$tmp_name}keys[0],
  577. {$tabs} 'last' => \${$tmp_name}key === \${$tmp_name}key_last,
  578. {$tabs} );
  579. {$tabs} {$vars_key} = \${$tmp_name}key;
  580. {$tabs} {$vars_as} = \${$tmp_name}val[\${$tmp_name}key];
  581. PHP;
  582. }
  583. },
  584. 'while' => function($data)use(&$replacement, &$brackets, &$tabs){
  585. ++$brackets;
  586.  
  587. return $tabs . 'while(' . self::parse_boolean($data) . '){';
  588. },
  589. 'for' => function($data)use(&$replacement, &$brackets, &$tabs){
  590. ++$brackets;
  591.  
  592. '@(?<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'] . '))?@',
  593. function($matches)use(&$replacement, &$brackets, &$tabs){
  594.  
  595. $values = array(
  596. 'start' => isset($matches['start']) && $matches['start'] !== '' ? self::parse_value($matches['start']) : '0',
  597. 'end' => isset($matches['end']) ? self::parse_value($matches['end']) : self::parse_value($matches['start']),
  598. 'step' => isset($matches['step']) ? self::parse_value($matches['step']) : '1'
  599. );
  600.  
  601. $return = $tabs . 'foreach(';
  602.  
  603. if(self::is_value($values['start']) && self::is_value($values['end']) && self::is_value($values['step']))
  604. {
  605. if($this->options['optimize'])
  606. {
  607. $return = "{$tabs}// ~ optimization enabled ~ inlining the results\r\n{$return}" . self::format_code(
  608. preg_replace('@^"|"$@', '', $values['start']),
  609. preg_replace('@^"|"$@', '', $values['end']),
  610. abs($values['step'])
  611. ),
  612. true
  613. ),
  614. $tabs . "\t",
  615. true
  616. );
  617. }
  618. else
  619. {
  620. $return = "{$tabs}// ~ optimization DISABLED ~ results could be inlined\r\n{$return}range({$values['start']}, {$values['end']}, abs({$values['step']}))";
  621. }
  622. }
  623. else
  624. {
  625. $return .= 'range(' . $values['start'] . ', ' . $values['end'] . ', abs(' . $values['step'] . '))';
  626. }
  627.  
  628. return $return . ' as ' . self::render_var(isset($matches['var']) ? $matches['var'] : '', false) . '){';
  629. },
  630. $data
  631. );
  632. },
  633. 'do' => function($data)use(&$replacement, &$brackets, &$tabs){
  634. ++$brackets;
  635.  
  636. return $tabs . 'do{';
  637. },
  638. 'until' => function($data)use(&$replacement, &$brackets, &$tabs){
  639. --$brackets;
  640.  
  641. return substr($tabs, 1) . '}while(!(' . self::parse_boolean($data) . '));';
  642. },
  643. 'set' => function($data)use(&$replacement, &$brackets, &$tabs){
  644. preg_match('@^\s*(?<op>[\+\-\*\\\/\%])?\s*(?<var>' . self::$regex['var'] . ')\s*(?:(?<op_val>' . self::$regex['var_value'] . ')\s)?\s*(?<values>.*)$@', $data, $bits);
  645.  
  646. $values = self::parse_values($bits['values']);
  647. $count = count($values);
  648.  
  649. $return = $tabs . self::render_var($bits['var'], false) . ' = ';
  650.  
  651. $close = 0;
  652.  
  653. if(isset($bits['op']))
  654. {
  655. switch($bits['op'])
  656. {
  657. case '-':
  658. $return .= <<<PHP
  659. call_user_func_array(function(){
  660. {$tabs} \$args = func_get_args();
  661. {$tabs} \$initial = array_shift(\$args);
  662. {$tabs} return array_reduce(\$args, function(\$carry, \$value){
  663. {$tabs} return \$carry - \$value;
  664. {$tabs} }, \$initial);
  665. {$tabs}}, SimpleTemplate_FN::call('array_flat',
  666. PHP;
  667. $close = 2;
  668. break;
  669. case '+':
  670. $return .= 'array_sum(SimpleTemplate_FN::call(\'array_flat\', ';
  671. $close = 2;
  672. break;
  673. case '*':
  674. $return .= 'array_product(SimpleTemplate_FN::call(\'array_flat\', ';
  675. $close = 2;
  676. break;
  677. case '\\':
  678. case '/':
  679. case '%':
  680. $ops = array(
  681. '\\' => 'round(%s / $value)',
  682. '/' => '(%s / $value)',
  683. '%' => '(%s %% $value)'
  684. );
  685.  
  686. $return .= 'array_map(function($value)use(&$' . self::$var_name . '){'
  687. .'return ' . sprintf(
  688. $ops[$bits['op']],
  689. isset($bits['op_val'])
  690. ? self::parse_value($bits['op_val'])
  691. : self::render_var($bits['var'], false)
  692. )
  693. . ';}, SimpleTemplate_FN::call(\'array_flat\', ';
  694. $close = 2;
  695. break;
  696. }
  697. }
  698.  
  699. if($count > 1)
  700. {
  701. $return .= 'array(' . implode(',', $values) . ')';
  702. }
  703. else
  704. {
  705. $return .= ($count && strlen($values[0]) ? $values[0] : 'null');
  706. }
  707.  
  708. return $return . str_repeat(')', $close) . ';';
  709. },
  710. 'unset' => function($data)use(&$replacement, &$brackets, &$tabs){
  711. $values = self::parse_values($data, null, false);
  712. $vars = array_filter($values, function($var){
  713. return !self::is_value($var);
  714. });
  715.  
  716. $return = $tabs . (
  717. count($values) !== count($vars)
  718. ? '// Warning: invalid values were passed' . PHP_EOL . $tabs
  719. : ''
  720. );
  721.  
  722. return $return . (
  723. $vars
  724. ? 'unset(' . implode(',', $vars) . ');'
  725. : '// Warning: no values were passed or all were filtered out'
  726. );
  727. },
  728. 'global' => function($data)use(&$replacement, &$brackets, &$tabs){
  729. $data = self::split_values($data, ' ');
  730.  
  731. return $tabs . self::render_var(array_shift($data), false) . ' = $GLOBALS[\'' . join('\'][\'', $data) . '\'];';
  732. },
  733. 'call' => function($data)use(&$replacement, &$brackets, &$tabs){
  734. preg_match('@^\s*(?<fn>' . self::$regex['var'] . ')\s*(?:into\s*(?<into>' . self::$regex['var'] . ')\s*)?(?<args>.*?)$@', $data, $bits);
  735.  
  736. $var = self::render_var($bits['fn'], false);
  737.  
  738. $into = $bits['into'] ? self::render_var($bits['into'], false) . ' = ' : '';
  739. $args = implode(', ', self::parse_values($bits['args']));
  740. $alt_name = str_replace('.', '_', $bits['fn']);
  741.  
  742. return <<<PHP
  743. {$tabs}{$into}call_user_func_array(
  744. {$tabs} (isset({$var}) && is_callable({$var})
  745. {$tabs} ? {$var}
  746. {$tabs} : (SimpleTemplate_FN::method_exists('{$bits['fn']}')
  747. {$tabs} ? SimpleTemplate_FN::call('{$bits['fn']}')
  748. {$tabs} : '{$alt_name}'
  749. {$tabs} )
  750. {$tabs} ),
  751. {$tabs} array({$args})
  752. {$tabs});
  753. PHP;
  754. },
  755. 'php' => function($data)use(&$replacement, &$brackets, &$tabs){
  756. return $tabs . 'call_user_func_array(function(&$' . self::$var_name . '){' . PHP_EOL
  757. . "{$tabs}\t{$data};" . PHP_EOL
  758. . $tabs . '}, array(&$' . self::$var_name . '));';
  759. },
  760. 'return' => function($data)use(&$replacement, &$brackets, &$tabs){
  761. return $tabs . 'return' . ($data || $data === '0' ? ' ' . self::parse_value($data) : '') . ';';
  762. },
  763. 'inc' => function($data)use(&$replacement, &$brackets, &$tabs){
  764. preg_match('@^(?:\s*by\s*(?<by>' . self::$regex['var_value'] . ')\s*)?(?<values>.*?)$@', $data, $bits);
  765. $values = self::parse_values($bits['values'], '\s*,\s*', false);
  766. $inc = isset($bits['by']) && $bits['by'] !== '' ? self::parse_value($bits['by']): '1';
  767.  
  768. $return = '';
  769.  
  770. if(!$inc || $inc === '"0"' || $inc === 'null' || $inc === 'false')
  771. {
  772. if($this->options['optimize'])
  773. {
  774. return "{$tabs}// ~ optimization enabled ~ increment by {$inc} removed";
  775. }
  776. else
  777. {
  778. $return .= "{$tabs}// ~ optimization DISABLED ~ increment by {$inc} could be removed" . PHP_EOL;
  779. }
  780. }
  781.  
  782. $var_name = self::$var_name;
  783.  
  784. foreach($values as $value)
  785. {
  786. if(!isset($value[0]) && !self::is_value($value[0]))
  787. {
  788. continue;
  789. }
  790.  
  791. $return .= "{$tabs}{$value} = SimpleTemplate_FN::call('inc', isset({$value})?{$value}:0, {$inc});" . PHP_EOL;
  792. }
  793.  
  794. return $return;
  795.  
  796. },
  797. 'fn' => function($data)use(&$replacement, &$brackets, &$tabs){
  798. if(
  799. '@^\s*(' . self::$regex['var_simple'] . ')(?:\s+(' . self::$regex['var_name'] . '(?:,\s*' . self::$regex['var_name'] . ')*))?$@',
  800. $data, $bits
  801. ) === false
  802. )
  803. {
  804. return '';
  805. }
  806.  
  807. ++$brackets;
  808.  
  809. $version = SimpleTemplate::version();
  810. $var_name = self::$var_name;
  811.  
  812. $return = $tabs . self::render_var($bits ? $bits[1] : null, false) . <<<PHP
  813. = function()use(&\$_){
  814. {$tabs} \${$var_name} = array(
  815. {$tabs} 'argv' => func_get_args(),
  816. {$tabs} 'argc' => func_num_args(),
  817. {$tabs} 'VERSION' => '{$version}',
  818. {$tabs} 'EOL' => PHP_EOL,
  819. {$tabs} 'PARENT' => &\$_
  820. {$tabs} );
  821. {$tabs} \$_ = &\${$var_name};
  822.  
  823. PHP;
  824. if(isset($bits[2]) && $bits[2])
  825. {
  826. $args = array();
  827. foreach(self::split_values($bits[2]) as $value)
  828. {
  829. if(!self::is_value(self::parse_value($value)))
  830. {
  831. $args[] = $value;
  832. }
  833. }
  834.  
  835. foreach($args as $k => $arg)
  836. {
  837. $return .= "{$tabs} \${$var_name}[\"{$arg}\"] = &\${$var_name}[\"argv\"][{$k}];" . PHP_EOL;
  838. }
  839. }
  840.  
  841. return $return;
  842. },
  843. 'eval' => function($data)use(&$replacement, &$brackets, &$tabs){
  844. $return = '';
  845. $value = self::parse_value($data);
  846.  
  847. if($this->options['optimize'] && self::is_value($value))
  848. {
  849. $return = $tabs . '// ~ optimization enabled ~ trying to avoid compiling in runtime' . PHP_EOL;
  850.  
  851. static $cached = array();
  852.  
  853. $sha1 = sha1($value);
  854.  
  855. if(isset($cached[$sha1]))
  856. {
  857. $return .= $tabs . '// {@eval} cached code found: cache entry ';
  858. }
  859. else
  860. {
  861. $return .= $tabs . '// {@eval} no cached code found: creating entry ';
  862.  
  863. $compiler = new SimpleTemplate_Compiler($this->template, stripslashes(trim($value, '"')), $this->options);
  864.  
  865. $cached[$sha1] = self::format_code($compiler->getPHP() . '// {@eval} ended', $tabs);
  866.  
  867. unset($compiler);
  868. }
  869.  
  870. $return .= $sha1 . PHP_EOL . $cached[$sha1];
  871. }
  872. else
  873. {
  874. $options = self::format_code(var_export($this->options, true), $tabs . "\t\t", true);
  875.  
  876. $return = <<<PHP
  877. {$tabs}// ~ optimization DISABLED or unfeasable ~ compilation in runtime is required
  878. {$tabs}call_user_func_array(function()use(&\$DATA){
  879. {$tabs} \$compiler = new SimpleTemplate_Compiler(\$this, {$value}, {$options});
  880. {$tabs} \$fn = \$compiler->getFN();
  881. {$tabs} return \$fn(\$DATA);
  882. {$tabs}}, array());
  883. PHP;
  884. }
  885.  
  886. return $return;
  887. }
  888. );
  889.  
  890. $trim_fn = $this->options['trim'] ? 'trim' : '';
  891.  
  892. $this->php .= "\r\necho {$trim_fn}(<<<'" . self::$var_name . "{$UUID}'\r\n"
  893. // http://stackoverflow.com/a/6464500
  894. '~{@(eval|echoj?l?|print|if|else|for|while|each|do|until|(?:un)?set|call|global|php|return|inc|fn|//?)(?:\\s*(.*?))?}(?=(?:[^"\\\\]*(?:\\\\.|"(?:[^"\\\\]*\\\\.)*[^"\\\\]*"))*[^"]*$)~',
  895. function($matches)use(&$replacement, &$brackets, &$tabs, &$UUID, &$trim_fn){
  896.  
  897. $tabs = $brackets
  898. ? str_repeat("\t", $brackets - ($matches[1] === '/'))
  899. : '';
  900.  
  901. $var_name = self::$var_name;
  902.  
  903. $php = $replacement[$matches[1]](isset($matches[2]) ? $matches[2] : null);
  904.  
  905. 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";
  906. },
  907. $str . ''
  908. )
  909. . "\r\n" . self::$var_name . "{$UUID}\r\n);\r\n";
  910.  
  911. $this->php = preg_replace(
  912. '@\r\n\t*echo\s*' . $trim_fn . '\(<<<\'' . self::$var_name . $UUID . '\'(?:\s*\r\n)?' . self::$var_name . $UUID . '\r\n\);@',
  913. '@\r\n' . self::$var_name . $UUID . '\r\n\);(\r\n)*\t*echo\s*' . $trim_fn . '\(<<<\'' . self::$var_name . $UUID . '\'@'
  914. ),
  915. '', ''
  916. ),
  917. $this->php
  918. );
  919.  
  920. if($brackets)
  921. {
  922. $this->php .= "\r\n// AUTO-CLOSE\r\n" . str_repeat('};', $brackets);
  923. }
  924. }
  925.  
  926. function getPHP(){
  927. return $this->php;
  928. }
  929.  
  930. function getFN(){
  931. if(!$this->fn)
  932. {
  933. $this->fn = eval('return function(&$' . self::$var_name . '){'
  934. . PHP_EOL . $this->php . PHP_EOL
  935. . '};'
  936. );
  937.  
  938. $this->fn = $this->fn->bindTo($this->template);
  939. }
  940.  
  941. return $this->fn;
  942. }
  943.  
  944. function __construct(SimpleTemplate $template, $code, array $options = array()){
  945. $this->options = $options;
  946. $this->template = $template;
  947.  
  948. // ALMOST unguessable name, to avoid syntax errors
  949. $this->uuid = str_shuffle(mt_rand() . time() . sha1($code));
  950.  
  951. $this->compile($code);
  952. }
  953. }
  954.  
  955. // base class
  956. class SimpleTemplate {
  957. private static $version = '0.84';
  958.  
  959. private $data = array();
  960. private $settings = array(
  961. 'optimize' => true,
  962. 'trim' => false
  963. );
  964.  
  965. private $compiler = null;
  966.  
  967. function __construct($code, array $options = array()){
  968. if(!$code)
  969. {
  970. throw new Exception('No code was provided');
  971. }
  972.  
  973. if($options)
  974. {
  975. $this->settings = array_merge($this->settings, $options);
  976. }
  977.  
  978. $this->compiler = new SimpleTemplate_Compiler($this, $code, $this->settings);
  979. }
  980.  
  981. function setData($key, $value){
  982. $this->data[$key] = $value;
  983. }
  984.  
  985. function getData($key, $value){
  986. return isset($this->data[$key]) ? $this->data[$key] : null;
  987. }
  988.  
  989. function unsetData($key){
  990. unset($this->data[$key]);
  991. }
  992.  
  993. function loadData($data){
  994. foreach($data as $k => $value)
  995. {
  996. $this->data[$k] = $value;
  997. }
  998. }
  999.  
  1000. function clearData(){
  1001. $this->data = array();
  1002. }
  1003.  
  1004. function getPHP(){
  1005. return $this->compiler->getPHP();
  1006. }
  1007.  
  1008. function render(){
  1009. $this->data['argv'] = func_get_args();
  1010. $this->data['argc'] = func_num_args();
  1011.  
  1012. $this->data['VERSION'] = self::$version;
  1013. $this->data['EOL'] = PHP_EOL;
  1014.  
  1015. $fn = $this->compiler->getFN();
  1016.  
  1017. return $fn($this->data);
  1018. }
  1019.  
  1020. static function fromFile($path, array $options = array()){
  1021. return new self(file_get_contents($path), $options);
  1022. }
  1023.  
  1024. static function fromString($string, array $options = array()){
  1025. return new self($string, $options);
  1026. }
  1027.  
  1028. static function version(){
  1029. return self::$version;
  1030. }
  1031. }
  1032.  
  1033. $code = array(
  1034. 'golfed' => '{@setC 0}{@for_ from" "to"m"}{@echolC}{@incC}',
  1035. 'ungolfed' => <<<'CODE'
  1036. {@set counter 0}
  1037. {@for i from " " to "m"}
  1038.   {@echo counter, EOL}
  1039.   {@inc counter}
  1040. {@/}
  1041. CODE
  1042. );
  1043.  
  1044. $input = '';
  1045.  
  1046. $key = 'golfed'; // golfed - ungolfed
  1047.  
  1048. $x = new SimpleTemplate($c = $code[$key]);
  1049.  
  1050. echo $x->render($input), PHP_EOL, PHP_EOL, '----------------- v PHP v -----------------', PHP_EOL, $x->getPHP();
  1051. echo PHP_EOL, PHP_EOL, '----------------- v CODE (', strlen($c), 'b - ', $key, ') v -----------------', PHP_EOL, $c;
Success #stdin #stdout 0.03s 25256KB
stdin
Standard input is empty
stdout
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100


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

// {@setC 0}
$DATA['C'] = 0;

// {@for_ from"	"to"m"}
// ~ optimization enabled ~ inlining the results
foreach(array (
	0 => '	',
	1 => '
	',
	2 => '',
	3 => '',
	4 => '
	',
	5 => '',
	6 => '',
	7 => '',
	8 => '',
	9 => '',
	10 => '',
	11 => '',
	12 => '',
	13 => '',
	14 => '',
	15 => '',
	16 => '',
	17 => '',
	18 => '',
	19 => '',
	20 => '',
	21 => '',
	22 => '',
	23 => ' ',
	24 => '!',
	25 => '"',
	26 => '#',
	27 => '$',
	28 => '%',
	29 => '&',
	30 => '\'',
	31 => '(',
	32 => ')',
	33 => '*',
	34 => '+',
	35 => ',',
	36 => '-',
	37 => '.',
	38 => '/',
	39 => '0',
	40 => '1',
	41 => '2',
	42 => '3',
	43 => '4',
	44 => '5',
	45 => '6',
	46 => '7',
	47 => '8',
	48 => '9',
	49 => ':',
	50 => ';',
	51 => '<',
	52 => '=',
	53 => '>',
	54 => '?',
	55 => '@',
	56 => 'A',
	57 => 'B',
	58 => 'C',
	59 => 'D',
	60 => 'E',
	61 => 'F',
	62 => 'G',
	63 => 'H',
	64 => 'I',
	65 => 'J',
	66 => 'K',
	67 => 'L',
	68 => 'M',
	69 => 'N',
	70 => 'O',
	71 => 'P',
	72 => 'Q',
	73 => 'R',
	74 => 'S',
	75 => 'T',
	76 => 'U',
	77 => 'V',
	78 => 'W',
	79 => 'X',
	80 => 'Y',
	81 => 'Z',
	82 => '[',
	83 => '\\',
	84 => ']',
	85 => '^',
	86 => '_',
	87 => '`',
	88 => 'a',
	89 => 'b',
	90 => 'c',
	91 => 'd',
	92 => 'e',
	93 => 'f',
	94 => 'g',
	95 => 'h',
	96 => 'i',
	97 => 'j',
	98 => 'k',
	99 => 'l',
	100 => 'm',
) as $DATA['_']){

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

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



// AUTO-CLOSE
};

----------------- v CODE (45b - golfed) v -----------------
{@setC 0}{@for_ from"	"to"m"}{@echolC}{@incC}