PageRenderTime 55ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Haanga/Compiler.php

https://github.com/crodas/Haanga
PHP | 1541 lines | 1183 code | 110 blank | 248 comment | 159 complexity | 3050051fb05b4f09005465b47aa55f9f MD5 | raw file
  1. <?php
  2. /*
  3. +---------------------------------------------------------------------------------+
  4. | Copyright (c) 2010 César Rodas and Menéame Comunicacions S.L. |
  5. +---------------------------------------------------------------------------------+
  6. | Redistribution and use in source and binary forms, with or without |
  7. | modification, are permitted provided that the following conditions are met: |
  8. | 1. Redistributions of source code must retain the above copyright |
  9. | notice, this list of conditions and the following disclaimer. |
  10. | |
  11. | 2. Redistributions in binary form must reproduce the above copyright |
  12. | notice, this list of conditions and the following disclaimer in the |
  13. | documentation and/or other materials provided with the distribution. |
  14. | |
  15. | 3. All advertising materials mentioning features or use of this software |
  16. | must display the following acknowledgement: |
  17. | This product includes software developed by César D. Rodas. |
  18. | |
  19. | 4. Neither the name of the César D. Rodas nor the |
  20. | names of its contributors may be used to endorse or promote products |
  21. | derived from this software without specific prior written permission. |
  22. | |
  23. | THIS SOFTWARE IS PROVIDED BY CÉSAR D. RODAS ''AS IS'' AND ANY |
  24. | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
  25. | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
  26. | DISCLAIMED. IN NO EVENT SHALL CÉSAR D. RODAS BE LIABLE FOR ANY |
  27. | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
  28. | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
  29. | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
  30. | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
  31. | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
  32. | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE |
  33. +---------------------------------------------------------------------------------+
  34. | Authors: César Rodas <crodas@php.net> |
  35. +---------------------------------------------------------------------------------+
  36. */
  37. class Haanga_Compiler
  38. {
  39. // properties {{{
  40. protected static $block_var=NULL;
  41. protected $generator;
  42. protected $forloop = array();
  43. protected $forid = 0;
  44. protected $sub_template = FALSE;
  45. protected $name;
  46. protected $check_function = FALSE;
  47. protected $blocks=array();
  48. protected $line=0;
  49. protected $file;
  50. /**
  51. * number of blocks :-)
  52. */
  53. protected $in_block=0;
  54. /**
  55. * output buffers :-)
  56. */
  57. protected $ob_start=0;
  58. protected $append;
  59. protected $prepend_op;
  60. /**
  61. * Context at compile time
  62. */
  63. protected $context;
  64. /**
  65. * Table which contains all variables
  66. * aliases defined in the template
  67. */
  68. protected $var_alias;
  69. /**
  70. * Flag the current variable as safe. This means
  71. * that escape won't be called if autoescape is
  72. * activated (which is activated by default)
  73. */
  74. public $var_is_safe=FALSE;
  75. public $safes;
  76. /* compiler options */
  77. static protected $autoescape = TRUE;
  78. static protected $if_empty = TRUE;
  79. static protected $dot_as_object = TRUE;
  80. static protected $strip_whitespace = FALSE;
  81. static protected $is_exec_enabled = FALSE;
  82. static protected $global_context = array();
  83. static protected $echo_concat = '.';
  84. static protected $enable_load = TRUE;
  85. /**
  86. * Debug file
  87. */
  88. protected $debug;
  89. // }}}
  90. function __construct()
  91. {
  92. $this->generator = new Haanga_Generator_PHP;
  93. if (self::$block_var===NULL) {
  94. self::$block_var = '{{block.'.md5('super').'}}';
  95. }
  96. }
  97. public function getScopeVariable($part = NULL, $string = FALSE)
  98. {
  99. static $var = NULL;
  100. if ($var === NULL) {
  101. $var = 'vars' . uniqid(true);
  102. }
  103. if ($string) {
  104. return $var;
  105. }
  106. if ($part !== NULL) {
  107. return hvar($var, $part);
  108. }
  109. return hvar($var);
  110. }
  111. // getOption($option) {{{
  112. public static function getOption($option)
  113. {
  114. $value = NULL;
  115. switch (strtolower($option)) {
  116. case 'enable_load':
  117. $value = self::$enable_load;
  118. break;
  119. case 'if_empty':
  120. $value = self::$if_empty;
  121. break;
  122. case 'autoescape':
  123. $value = self::$autoescape;
  124. break;
  125. case 'dot_as_object':
  126. $value = self::$dot_as_object;
  127. break;
  128. case 'echo_concat':
  129. $value = self::$echo_concat;
  130. break;
  131. case 'strip_whitespace':
  132. $value = self::$strip_whitespace;
  133. break;
  134. case 'is_exec_enabled':
  135. case 'allow_exec':
  136. $value = self::$is_exec_enabled;
  137. break;
  138. case 'global':
  139. $value = self::$global_context;
  140. break;
  141. }
  142. return $value;
  143. }
  144. // }}}
  145. // setOption($option, $value) {{{
  146. /**
  147. * Set Compiler option.
  148. *
  149. * @return void
  150. */
  151. public static function setOption($option, $value)
  152. {
  153. switch (strtolower($option)) {
  154. case 'if_empty':
  155. self::$if_empty = (bool)$value;
  156. break;
  157. case 'enable_load':
  158. self::$enable_load = (bool)$value;
  159. case 'echo_concat':
  160. if ($value == '.' || $value == ',') {
  161. self::$echo_concat = $value;
  162. }
  163. break;
  164. case 'autoescape':
  165. self::$autoescape = (bool)$value;
  166. break;
  167. case 'dot_as_object':
  168. self::$dot_as_object = (bool)$value;
  169. break;
  170. case 'strip_whitespace':
  171. self::$strip_whitespace = (bool)$value;
  172. break;
  173. case 'is_exec_enabled':
  174. case 'allow_exec':
  175. self::$is_exec_enabled = (bool)$value;
  176. break;
  177. case 'global':
  178. if (!is_array($value)) {
  179. $value = array($value);
  180. }
  181. self::$global_context = $value;
  182. break;
  183. }
  184. }
  185. // }}}
  186. // setDebug($file) {{{
  187. function setDebug($file)
  188. {
  189. $this->debug = $file;
  190. }
  191. // }}}
  192. // reset() {{{
  193. function reset()
  194. {
  195. foreach (array_keys(get_object_vars($this)) as $key) {
  196. if (isset($avoid_cleaning[$key])) {
  197. continue;
  198. }
  199. $this->$key = NULL;
  200. }
  201. $this->generator = new Haanga_Generator_PHP;
  202. $this->blocks = array();
  203. $this->cycle = array();
  204. }
  205. // }}}
  206. // get_template_name() {{{
  207. final function get_template_name()
  208. {
  209. return $this->name;
  210. }
  211. // }}}
  212. // Set template name {{{
  213. function set_template_name($path)
  214. {
  215. $file = basename($path);
  216. $pos = strpos($file,'.');
  217. return ($this->name = substr($file, 0, $pos));
  218. }
  219. // }}}
  220. // get_function_name(string $name) {{{
  221. function get_function_name($name)
  222. {
  223. return "{$name}_template";
  224. }
  225. // }}}
  226. // Compile ($code, $name=NULL) {{{
  227. final function compile($code, $name=NULL, $file=NULL)
  228. {
  229. $this->name = $name;
  230. if (count(self::$global_context) > 0) {
  231. /* add global variables (if any) to the current context */
  232. foreach (self::$global_context as $var) {
  233. $this->set_context($var, $GLOBALS[$var]);
  234. }
  235. }
  236. $parsed = Haanga_Compiler_Tokenizer::init($code, $this, $file);
  237. $code = "";
  238. $this->subtemplate = FALSE;
  239. $body = new Haanga_AST;
  240. $this->prepend_op = hcode();
  241. if (isset($parsed[0]) && $parsed[0]['operation'] == 'base') {
  242. /* {% base ... %} found */
  243. $base = $parsed[0][0];
  244. $code .= $this->get_base_template($base);
  245. unset($parsed[0]);
  246. }
  247. if (defined('HAANGA_VERSION')) {
  248. $body->decl('HAANGA_VERSION', HAANGA_VERSION);
  249. }
  250. if ($name) {
  251. $func_name = $this->get_function_name($name);
  252. if ($this->check_function) {
  253. $body->do_if(hexpr(hexec('function_exists', $func_name), '===', FALSE));
  254. }
  255. if (!empty($this->file)) {
  256. $body->comment("Generated from ".$this->file);
  257. }
  258. $body->declare_function($func_name);
  259. }
  260. if (count(self::$global_context) > 0) {
  261. $body->do_global(self::$global_context);
  262. }
  263. $body->do_exec('extract', $this->getScopeVariable());
  264. $body->do_if(hexpr(hvar('return'), '==', TRUE));
  265. $body->do_exec('ob_start');
  266. $body->do_endif();
  267. $this->generate_op_code($parsed, $body);
  268. if ($this->subtemplate) {
  269. $expr = $this->expr_call_base_template();
  270. $this->do_print($body, $expr);
  271. }
  272. $body->do_if(hexpr(hvar('return'), '==', TRUE));
  273. $body->do_return(hexec('ob_get_clean'));
  274. $body->do_endif();
  275. if ($name) {
  276. $body->do_endfunction();
  277. if ($this->check_function) {
  278. $body->do_endif();
  279. }
  280. }
  281. if ($this->prepend_op->stack_size() > 0) {
  282. $this->prepend_op->append_ast($body);
  283. $body = $this->prepend_op;
  284. }
  285. $op_code = $body->getArray(TRUE);
  286. $code .= $this->generator->getCode($op_code, $this->getScopeVariable(NULL, TRUE));
  287. if (!empty($this->append)) {
  288. $code .= $this->append;
  289. }
  290. if (!empty($this->debug)) {
  291. $op_code['php'] = $code;
  292. file_put_contents($this->debug, print_r($op_code, TRUE), LOCK_EX);
  293. }
  294. return $code;
  295. }
  296. // }}}
  297. // compile_file($file) {{{
  298. /**
  299. * Compile a file
  300. *
  301. * @param string $file File path
  302. * @param bool $safe Whether or not add check if the function is already defined
  303. *
  304. * @return Generated PHP code
  305. */
  306. final function compile_file($file, $safe=FALSE, $context=array())
  307. {
  308. if (!is_readable($file)) {
  309. throw new Haanga_Compiler_Exception("$file is not a file");
  310. }
  311. $this->_base_dir = dirname($file);
  312. $this->file = realpath($file);
  313. $this->line = 0;
  314. $this->check_function = $safe;
  315. $this->context = $context;
  316. $name = $this->set_template_name($file);
  317. try {
  318. return $this->compile(file_get_contents($file), $name, $file);
  319. } catch (Exception $e) {
  320. $this->Error((string)$e);
  321. }
  322. }
  323. // }}}
  324. // getOpCodes($code, $file='') {{{
  325. /**
  326. * Compile the $code and return the "opcodes"
  327. * (the Abstract syntax tree).
  328. *
  329. * @param string $code Template content
  330. * @param string $file File path (used for erro reporting)
  331. *
  332. * @return Haanga_AST
  333. *
  334. */
  335. public function getOpCodes($code, $file)
  336. {
  337. $oldfile = $this->file;
  338. $this->file = $file;
  339. $parsed = Haanga_Compiler_Tokenizer::init($code, $this, $file);
  340. $body = new Haanga_AST;
  341. if (isset($parsed[0]) && $parsed[0]['operation'] == 'base') {
  342. throw new Exception("{% base is not supported on inlines %}");
  343. }
  344. $body = new Haanga_AST;
  345. $this->generate_op_code($parsed, $body);
  346. $this->file = $oldfile;
  347. return $body;
  348. }
  349. // }}}
  350. // Error($errtxt) {{{
  351. /**
  352. * Throw an exception and appends information about the template (the path and
  353. * the last processed line).
  354. *
  355. *
  356. */
  357. public function Error($err)
  358. {
  359. throw new Haanga_Compiler_Exception("{$err} in {$this->file}:$this->line");
  360. }
  361. // }}}
  362. // is_expr methods {{{
  363. function is_var_filter($cmd)
  364. {
  365. return isset($cmd['var_filter']);
  366. }
  367. // }}}
  368. // expr_call_base_template() {{{
  369. /**
  370. * Generate code to call base template
  371. *
  372. */
  373. function expr_call_base_template()
  374. {
  375. return hexec(
  376. $this->get_function_name($this->subtemplate),
  377. $this->getScopeVariable(), TRUE,
  378. hvar('blocks')
  379. );
  380. }
  381. // }}}
  382. // get_base_template($base) {{{
  383. /**
  384. * Handle {% base "" %} definition. By default only
  385. * static (string) are supported, but this can be overrided
  386. * on subclasses.
  387. *
  388. * This method load the base class, compile it and return
  389. * the generated code.
  390. *
  391. * @param array $base Base structure
  392. *
  393. * @return string Generated source code
  394. */
  395. function get_base_template($base)
  396. {
  397. if (!Haanga_AST::is_str($base)) {
  398. throw new Exception("Dynamic inheritance is not supported for compilated templates");
  399. }
  400. $file = $base['string'];
  401. list($this->subtemplate, $new_code) = $this->compile_required_template($file);
  402. return $new_code."\n\n";
  403. }
  404. // }}}
  405. // {% base "foo.html" %} {{{
  406. protected function generate_op_base()
  407. {
  408. throw new Exception("{% base %} can be only as first statement");
  409. }
  410. // }}}
  411. // Main Loop {{{
  412. protected function generate_op_code($parsed, &$body)
  413. {
  414. if (!is_array($parsed)) {
  415. throw new Exception("Invalid \$parsed array");
  416. }
  417. foreach ($parsed as $op) {
  418. if (!is_array($op)) {
  419. continue;
  420. }
  421. if (!isset($op['operation'])) {
  422. throw new Exception("Malformed array:".print_r($op, TRUE));
  423. }
  424. if (isset($op['line'])) {
  425. $this->line = $op['line'];
  426. }
  427. if ($this->subtemplate && $this->in_block == 0 && $op['operation'] != 'block') {
  428. /* ignore most of tokens in subtemplates */
  429. continue;
  430. }
  431. $method = "generate_op_".$op['operation'];
  432. if (!is_callable(array($this, $method))) {
  433. throw new Exception("Compiler: Missing method $method");
  434. }
  435. $this->$method($op, $body);
  436. }
  437. }
  438. // }}}
  439. // Check the current expr {{{
  440. protected function check_expr(&$expr)
  441. {
  442. if (Haanga_AST::is_expr($expr)) {
  443. if ($expr['op_expr'] == 'in') {
  444. for ($id=0; $id < 2; $id++) {
  445. if ($this->is_var_filter($expr[$id])) {
  446. $expr[$id] = $this->get_filtered_var($expr[$id]['var_filter'], $var);
  447. }
  448. }
  449. if (Haanga_AST::is_str($expr[1])) {
  450. $expr = hexpr(hexec('strpos', $expr[1], $expr[0]), '!==', FALSE);
  451. } else {
  452. $expr = hexpr(
  453. hexpr_cond(
  454. hexec('is_array', $expr[1]),
  455. hexec('array_search', $expr[0], $expr[1]),
  456. hexec('strpos', $expr[1], $expr[0])
  457. )
  458. ,'!==', FALSE
  459. );
  460. }
  461. }
  462. if (is_object($expr)) {
  463. $expr = $expr->getArray();
  464. }
  465. $this->check_expr($expr[0]);
  466. $this->check_expr($expr[1]);
  467. } else if (is_array($expr)) {
  468. if ($this->is_var_filter($expr)) {
  469. $expr = $this->get_filtered_var($expr['var_filter'], $var);
  470. } else if (isset($expr['args'])) {
  471. /* check every arguments */
  472. foreach ($expr['args'] as &$v) {
  473. $this->check_expr($v);
  474. }
  475. unset($v);
  476. } else if (isset($expr['expr_cond'])) {
  477. /* Check expr conditions */
  478. $this->check_expr($expr['expr_cond']);
  479. $this->check_expr($expr['true']);
  480. $this->check_expr($expr['false']);
  481. }
  482. }
  483. }
  484. // }}}
  485. // ifequal|ifnot equal <var_filtered|string|number> <var_fitlered|string|number> ... else ... {{{
  486. protected function generate_op_ifequal($details, &$body)
  487. {
  488. $if['expr'] = hexpr($details[1], $details['cmp'], $details[2])->getArray();
  489. $if['body'] = $details['body'];
  490. if (isset($details['else'])) {
  491. $if['else'] = $details['else'];
  492. }
  493. $this->generate_op_if($if, $body);
  494. }
  495. // }}}
  496. // {% if <expr> %} HTML {% else %} TWO {% endif $} {{{
  497. protected function generate_op_if($details, &$body)
  498. {
  499. if (self::$if_empty && $this->is_var_filter($details['expr']) && count($details['expr']['var_filter']) == 1) {
  500. /* if we are doing if <Variable> it should check
  501. if it exists without throw any warning */
  502. $expr = $details['expr'];
  503. $expr['var_filter'][] = 'empty';
  504. $variable = $this->get_filtered_var($expr['var_filter'], $var);
  505. $details['expr'] = hexpr($variable, '===', FALSE)->getArray();
  506. }
  507. $this->check_expr($details['expr']);
  508. $expr = Haanga_AST::fromArrayGetAST($details['expr']);
  509. $body->do_if($expr);
  510. $this->generate_op_code($details['body'], $body);
  511. if (isset($details['else'])) {
  512. $body->do_else();
  513. $this->generate_op_code($details['else'], $body);
  514. }
  515. $body->do_endif();
  516. }
  517. // }}}
  518. // Override template {{{
  519. protected function compile_required_template($file)
  520. {
  521. if (!is_file($file)) {
  522. if (isset($this->_base_dir)) {
  523. $file = $this->_base_dir.'/'.$file;
  524. }
  525. }
  526. if (!is_file($file)) {
  527. throw new Exception("can't find {$file} file template");
  528. }
  529. $class = get_class($this);
  530. $comp = new $class;
  531. $comp->reset();
  532. $code = $comp->compile_file($file, $this->check_function);
  533. return array($comp->get_template_name(), $code);
  534. }
  535. // }}}
  536. // include "file.html" | include <var1> {{{
  537. protected function generate_op_include($details, &$body)
  538. {
  539. if (!$details[0]['string']) {
  540. throw new Exception("Dynamic inheritance is not supported for compilated templates");
  541. }
  542. list($name,$code) = $this->compile_required_template($details[0]['string']);
  543. $this->append .= "\n\n{$code}";
  544. $this->do_print($body,
  545. hexec($this->get_function_name($name),
  546. $this->getScopeVariable(), TRUE, hvar('blocks'))
  547. );
  548. }
  549. // }}}
  550. // Handle HTML code {{{
  551. protected function generate_op_html($details, &$body)
  552. {
  553. $string = Haanga_AST::str($details['html']);
  554. $this->do_print($body, $string);
  555. }
  556. // }}}
  557. function isMethod($varname, &$expr)
  558. {
  559. if (is_array($varname)) {
  560. $tmp = $varname;
  561. $method = array_pop($tmp);
  562. $object = $this->get_context($tmp);
  563. if (!empty($method['object']) && is_string($method['object'])) {
  564. $property = $method['object'];
  565. if (is_object($object) && !isset($object->$property) && is_callable(array($object, $property))) {
  566. $expr = hexec($varname);
  567. return TRUE;
  568. }
  569. }
  570. }
  571. return FALSE;
  572. }
  573. // get_var_filtered {{{
  574. /**
  575. * This method handles all the filtered variable (piped_list(X)'s
  576. * output in the parser.
  577. *
  578. *
  579. * @param array $variable (Output of piped_list(B) (parser))
  580. * @param array &$varname Variable name
  581. * @param bool $accept_string TRUE is string output are OK (ie: block.parent)
  582. *
  583. * @return expr
  584. *
  585. */
  586. function get_filtered_var($variable, &$varname, $accept_string=NULL)
  587. {
  588. $this->var_is_safe = FALSE;
  589. if ($accept_string === NULL && is_array($variable[0]) &&
  590. !Haanga_AST::is_exec($variable[0])) {
  591. $accept_string = !empty($variable[0]['string'])
  592. || $variable[0][0] === 'block';
  593. }
  594. if (count($variable) > 1) {
  595. $count = count($variable);
  596. if ($accept_string && isset($variable[0]['string'])) {
  597. $target = $variable[0];
  598. } else if (Haanga_AST::is_exec($variable[0])) {
  599. $target = $variable[0];
  600. } else {
  601. $target = $this->generate_variable_name($variable[0]);
  602. }
  603. if (!empty($variable[0][0]) && $variable[0][0] == 'block') {
  604. /* block.super can't have any filter */
  605. throw new Exception("This variable can't have any filter");
  606. }
  607. if (!empty($target['var']) && $this->isMethod($target['var'], $return)) {
  608. $target = $return;
  609. }
  610. for ($i=1; $i < $count; $i++) {
  611. $func_name = $variable[$i];
  612. if ($func_name == 'escape') {
  613. /* to avoid double cleaning */
  614. $this->var_is_safe = TRUE;
  615. }
  616. $args = array(isset($exec) ? $exec : $target);
  617. $exec = $this->do_filtering($func_name, $args);
  618. }
  619. unset($variable);
  620. $varname = $args[0];
  621. $details = $exec;
  622. } else {
  623. if (Haanga_AST::is_exec($variable[0])) {
  624. return $variable[0];
  625. }
  626. $details = $this->generate_variable_name($variable[0]);
  627. $varname = $variable[0];
  628. if ($this->isMethod($varname, $return)) {
  629. return $return;
  630. }
  631. if (!Haanga_AST::is_var($details) && !$accept_string) {
  632. /* generate_variable_name didn't replied a variable, weird case
  633. currently just used for {{block.super}}.
  634. */
  635. throw new Exception("Invalid variable name {$variable[0]}");
  636. }
  637. }
  638. return $details;
  639. }
  640. // }}}
  641. // generate_op_print_var {{{
  642. /**
  643. * Generate code to print a variable with its filters, if there is any.
  644. *
  645. * All variable (except those flagged as |safe) are automatically
  646. * escaped if autoescape is "on".
  647. *
  648. */
  649. protected function generate_op_print_var($details, &$body)
  650. {
  651. $expr = $details['expr'];
  652. $this->check_expr($expr);
  653. if (!$this->is_safe($expr) && self::$autoescape) {
  654. $args = array($expr);
  655. $expr = $this->do_filtering('escape', $args);
  656. }
  657. $this->do_print($body, $expr);
  658. }
  659. // }}}
  660. // {# something #} {{{
  661. protected function generate_op_comment($details, &$body)
  662. {
  663. /* comments are annoying */
  664. //$body->comment($details['comment']);
  665. }
  666. // }}}
  667. // {% block 'name' %} ... {% endblock %} {{{
  668. protected function generate_op_block($details, &$body)
  669. {
  670. if (is_array($details['name'])) {
  671. $name = "";
  672. foreach ($details['name'] as $part) {
  673. if (is_string($part)) {
  674. $name .= "{$part}";
  675. } else if (is_array($part)) {
  676. if (Haanga_AST::is_str($part)) {
  677. $name .= "{$part['string']}";
  678. } elseif (isset($part['object'])) {
  679. $name .= "{$part['object']}";
  680. } else {
  681. throw new Exception("Invalid blockname");
  682. }
  683. }
  684. $name .= ".";
  685. }
  686. $details['name'] = substr($name, 0, -1);
  687. }
  688. $this->in_block++;
  689. $this->blocks[] = $details['name'];
  690. $block_name = hvar('blocks', $details['name']);
  691. $this->ob_start($body);
  692. $buffer_var = 'buffer'.$this->ob_start;
  693. $content = hcode();
  694. $this->generate_op_code($details['body'], $content);
  695. $body->append_ast($content);
  696. $this->ob_start--;
  697. $buffer = hvar($buffer_var);
  698. /* {{{ */
  699. /**
  700. * isset previous block (parent block)?
  701. * TRUE
  702. * has reference to self::$block_var ?
  703. * TRUE
  704. * replace self::$block_var for current block value (buffer)
  705. * FALSE
  706. * print parent block
  707. * FALSE
  708. * print current block
  709. *
  710. */
  711. $declare = hexpr_cond(
  712. hexec('isset', $block_name),
  713. hexpr_cond(
  714. hexpr(hexec('strpos', $block_name, self::$block_var), '===', FALSE),
  715. $block_name,
  716. hexec('str_replace', self::$block_var, $buffer, $block_name)
  717. ), $buffer);
  718. /* }}} */
  719. if (!$this->subtemplate) {
  720. $this->do_print($body, $declare);
  721. } else {
  722. $body->decl($block_name, $declare);
  723. if ($this->in_block > 1) {
  724. $this->do_print($body, $block_name);
  725. }
  726. }
  727. array_pop($this->blocks);
  728. $this->in_block--;
  729. }
  730. // }}}
  731. // regroup <var1> by <field> as <foo> {{{
  732. protected function generate_op_regroup($details, &$body)
  733. {
  734. $body->comment("Temporary sorting");
  735. $array = $this->get_filtered_var($details['array'], $varname);
  736. if (Haanga_AST::is_exec($array)) {
  737. $varname = hvar($details['as']);
  738. $body->decl($varname, $array);
  739. }
  740. $var = hvar('item', $details['row']);
  741. $body->decl('temp_group', array());
  742. $body->do_foreach($varname, 'item', NULL,
  743. hcode()->decl(hvar('temp_group', $var, NULL), hvar('item'))
  744. );
  745. $body->comment("Proper format");
  746. $body->decl($details['as'], array());
  747. $body->do_foreach('temp_group', 'item', 'group',
  748. hcode()->decl(
  749. hvar($details['as'], NULL),
  750. array("grouper" => hvar('group'), "list" => hvar('item'))
  751. )
  752. );
  753. $body->comment("Sorting done");
  754. }
  755. // }}}
  756. // variable context {{{
  757. /**
  758. * Variables context
  759. *
  760. * These two functions are useful to detect if a variable
  761. * separated by dot (foo.bar) is an array or object. To avoid
  762. * overhead we decide it at compile time, rather than
  763. * ask over and over at rendering time.
  764. *
  765. * foo.bar:
  766. * + If foo exists at compile time,
  767. * and it is an array, it would be foo['bar']
  768. * otherwise it'd be foo->bar.
  769. * + If foo don't exists at compile time,
  770. * it would be foo->bar if the compiler option
  771. * dot_as_object is TRUE (by default) otherwise
  772. * it'd be foo['bar']
  773. *
  774. * @author crodas
  775. * @author gallir (ideas)
  776. *
  777. */
  778. function set_context($varname, $value)
  779. {
  780. $this->context[$varname] = $value;
  781. }
  782. function get_context($variable)
  783. {
  784. if (!is_array($variable)) {
  785. $variable = array($variable);
  786. }
  787. $varname = $variable[0];
  788. if (isset($this->context[$varname])) {
  789. if (count($variable) == 1) {
  790. return $this->context[$varname];
  791. }
  792. $var = & $this->context[$varname];
  793. foreach ($variable as $id => $part) {
  794. if ($id != 0) {
  795. if (is_array($part) && isset($part['object'])) {
  796. if (is_array($part['object']) && isset($part['object']['var'])) {
  797. /* object $foo->$bar */
  798. $name = $part['object']['var'];
  799. $name = $this->get_context($name);
  800. if (!isset($var->$name)) {
  801. return NULL;
  802. }
  803. $var = &$var->$name;
  804. } else {
  805. if (!isset($var->{$part['object']})) {
  806. return NULL;
  807. }
  808. $var = &$var->{$part['object']};
  809. }
  810. } else if (is_object($var)) {
  811. if (!isset($var->$part)) {
  812. return NULL;
  813. }
  814. $var = &$var->$part;
  815. } else {
  816. if (!is_scalar($part) || empty($part) || !isset($var[$part])) {
  817. return NULL;
  818. }
  819. $var = &$var[$part];
  820. }
  821. }
  822. }
  823. $variable = $var;
  824. unset($var);
  825. return $variable;
  826. }
  827. return NULL;
  828. }
  829. function var_is_object(Array $variable, $default=NULL)
  830. {
  831. $varname = $variable[0];
  832. switch ($varname) {
  833. case 'GLOBALS':
  834. case '_SERVER':
  835. case '_GET':
  836. case '_POST':
  837. case '_FILES':
  838. case '_COOKIE':
  839. case '_SESSION':
  840. case '_REQUEST':
  841. case '_ENV':
  842. case 'forloop':
  843. case 'block':
  844. return FALSE; /* these are arrays */
  845. }
  846. $variable = $this->get_context($variable);
  847. if (is_array($variable) || is_object($variable)) {
  848. return $default ? is_object($variable) : is_object($variable) && !$variable InstanceOf Iterator && !$variable Instanceof ArrayAccess;
  849. }
  850. return $default===NULL ? self::$dot_as_object : $default;
  851. }
  852. // }}}
  853. // Get variable name {{{
  854. function generate_variable_name($variable, $special=true)
  855. {
  856. if (is_array($variable)) {
  857. switch ($variable[0]) {
  858. case 'forloop':
  859. if (!$special) {
  860. return array('var' => $variable);
  861. }
  862. if (!$this->forid) {
  863. throw new Exception("Invalid forloop reference outside of a loop");
  864. }
  865. switch ($variable[1]['object']) {
  866. case 'counter':
  867. $this->forloop[$this->forid]['counter'] = TRUE;
  868. $variable = 'forcounter1_'.$this->forid;
  869. break;
  870. case 'counter0':
  871. $this->forloop[$this->forid]['counter0'] = TRUE;
  872. $variable = 'forcounter0_'.$this->forid;
  873. break;
  874. case 'last':
  875. $this->forloop[$this->forid]['counter'] = TRUE;
  876. $this->forloop[$this->forid]['last'] = TRUE;
  877. $variable = 'islast_'.$this->forid;
  878. break;
  879. case 'first':
  880. $this->forloop[$this->forid]['first'] = TRUE;
  881. $variable = 'isfirst_'.$this->forid;
  882. break;
  883. case 'revcounter':
  884. $this->forloop[$this->forid]['revcounter'] = TRUE;
  885. $variable = 'revcount_'.$this->forid;
  886. break;
  887. case 'revcounter0':
  888. $this->forloop[$this->forid]['revcounter0'] = TRUE;
  889. $variable = 'revcount0_'.$this->forid;
  890. break;
  891. case 'parentloop':
  892. unset($variable[1]);
  893. $this->forid--;
  894. $variable = $this->generate_variable_name(array_values($variable));
  895. $variable = $variable['var'];
  896. $this->forid++;
  897. break;
  898. default:
  899. throw new Exception("Unexpected forloop.{$variable[1]}");
  900. }
  901. /* no need to escape it */
  902. $this->var_is_safe = TRUE;
  903. break;
  904. case 'block':
  905. if (!$special) {
  906. return array('var' => $variable);
  907. }
  908. if ($this->in_block == 0) {
  909. throw new Exception("Can't use block.super outside a block");
  910. }
  911. if (!$this->subtemplate) {
  912. throw new Exception("Only subtemplates can call block.super");
  913. }
  914. /* no need to escape it */
  915. $this->var_is_safe = TRUE;
  916. return Haanga_AST::str(self::$block_var);
  917. break;
  918. default:
  919. /* choose array or objects */
  920. if ($special) {
  921. // this section is resolved on the parser.y
  922. return array('var' => $variable);
  923. }
  924. for ($i=1; $i < count($variable); $i++) {
  925. $var_part = array_slice($variable, 0, $i);
  926. $def_arr = TRUE;
  927. if (is_array($variable[$i])) {
  928. if (isset($variable[$i]['class'])) {
  929. // no type guess for static properties
  930. continue;
  931. }
  932. if (isset($variable[$i]['object'])) {
  933. $def_arr = FALSE;
  934. }
  935. if (!Haanga_AST::is_var($variable[$i])) {
  936. $variable[$i] = current($variable[$i]);
  937. } else {
  938. $variable[$i] = $this->generate_variable_name($variable[$i]['var']);
  939. }
  940. }
  941. $is_obj = $this->var_is_object($var_part, 'unknown');
  942. if ( $is_obj === TRUE || ($is_obj == 'unknown' && !$def_arr)) {
  943. $variable[$i] = array('object' => $variable[$i]);
  944. }
  945. }
  946. break;
  947. }
  948. } else if (isset($this->var_alias[$variable])) {
  949. $variable = $this->var_alias[$variable]['var'];
  950. }
  951. return hvar($variable)->getArray();
  952. }
  953. // }}}
  954. // Print {{{
  955. public function do_print(Haanga_AST $code, $stmt)
  956. {
  957. /* Flag this object as a printing one */
  958. $code->doesPrint = TRUE;
  959. if (self::$strip_whitespace && Haanga_AST::is_str($stmt)) {
  960. $stmt['string'] = preg_replace('/\s+/', ' ', $stmt['string']);
  961. }
  962. if ($this->ob_start == 0) {
  963. $code->do_echo($stmt);
  964. return;
  965. }
  966. $buffer = hvar('buffer'.$this->ob_start);
  967. $code->append($buffer, $stmt);
  968. }
  969. // }}}
  970. // for [<key>,]<val> in <array> {{{
  971. protected function generate_op_loop($details, &$body)
  972. {
  973. if (isset($details['empty'])) {
  974. $body->do_if(hexpr(hexec('count', hvar($details['array'])), '==', 0));
  975. $this->generate_op_code($details['empty'], $body);
  976. $body->do_else();
  977. }
  978. /* ForID */
  979. $oldid = $this->forid;
  980. $this->forid = $oldid+1;
  981. $this->forloop[$this->forid] = array();
  982. if (isset($details['range'])) {
  983. $this->set_safe($details['variable']);
  984. } else {
  985. /* check variable context */
  986. /* Check if the array to iterate is an object */
  987. $var = &$details['array'][0];
  988. if (is_string($var) && $this->var_is_object(array($var), FALSE)) {
  989. /* It is an object, call to get_object_vars */
  990. $body->decl($var.'_arr', hexec('get_object_vars', hvar($var)));
  991. $var .= '_arr';
  992. }
  993. unset($var);
  994. /* variables */
  995. $array = $this->get_filtered_var($details['array'], $varname);
  996. /* Loop body */
  997. if ($this->is_safe(hvar($varname))) {
  998. $this->set_safe(hvar($details['variable']));
  999. }
  1000. if ($array Instanceof Haanga_AST) {
  1001. // filtered var
  1002. $tmp = hvar('tmp'.($oldid+1));
  1003. $body->decl($tmp, $array);
  1004. $array = $tmp;
  1005. }
  1006. $details['array'] = $array;
  1007. }
  1008. /* for_body {{{ */
  1009. $for_body = hcode();
  1010. $this->generate_op_code($details['body'], $for_body);
  1011. $oid = $this->forid;
  1012. $size = hvar('psize_'.$oid);
  1013. // counter {{{
  1014. if (isset($this->forloop[$oid]['counter'])) {
  1015. $var = hvar('forcounter1_'.$oid);
  1016. $body->decl($var, 1);
  1017. $for_body->decl($var, hexpr($var, '+', 1));
  1018. }
  1019. // }}}
  1020. // counter0 {{{
  1021. if (isset($this->forloop[$oid]['counter0'])) {
  1022. $var = hvar('forcounter0_'.$oid);
  1023. $body->decl($var, 0);
  1024. $for_body->decl($var, hexpr($var, '+', 1));
  1025. }
  1026. // }}}
  1027. // last {{{
  1028. if (isset($this->forloop[$oid]['last'])) {
  1029. if (!isset($cnt)) {
  1030. $body->decl('psize_'.$oid, hexec('count', hvar_ex($details['array'])));
  1031. $cnt = TRUE;
  1032. }
  1033. $var = 'islast_'.$oid;
  1034. $body->decl($var, hexpr(hvar('forcounter1_'.$oid), '==', $size));
  1035. $for_body->decl($var, hexpr(hvar('forcounter1_'.$oid), '==', $size));
  1036. }
  1037. // }}}
  1038. // first {{{
  1039. if (isset($this->forloop[$oid]['first'])) {
  1040. $var = hvar('isfirst_'.$oid);
  1041. $body->decl($var, TRUE);
  1042. $for_body->decl($var, FALSE);
  1043. }
  1044. // }}}
  1045. // revcounter {{{
  1046. if (isset($this->forloop[$oid]['revcounter'])) {
  1047. if (!isset($cnt)) {
  1048. $body->decl('psize_'.$oid, hexec('count', hvar_ex($details['array'])));
  1049. $cnt = TRUE;
  1050. }
  1051. $var = hvar('revcount_'.$oid);
  1052. $body->decl($var, $size);
  1053. $for_body->decl($var, hexpr($var, '-', 1));
  1054. }
  1055. // }}}
  1056. // revcounter0 {{{
  1057. if (isset($this->forloop[$oid]['revcounter0'])) {
  1058. if (!isset($cnt)) {
  1059. $body->decl('psize_'.$oid, hexec('count', hvar_ex($details['array'])));
  1060. $cnt = TRUE;
  1061. }
  1062. $var = hvar('revcount0_'.$oid);
  1063. $body->decl($var, hexpr($size, "-", 1));
  1064. $for_body->decl($var, hexpr($var, '-', 1));
  1065. }
  1066. // }}}
  1067. /* }}} */
  1068. /* Restore old ForID */
  1069. $this->forid = $oldid;
  1070. /* Merge loop body */
  1071. if (!isset($details['range'])) {
  1072. $body->do_foreach($array, $details['variable'], $details['index'], $for_body);
  1073. if ($this->is_safe(hvar($varname))) {
  1074. $this->set_unsafe($details['variable']);
  1075. }
  1076. } else {
  1077. for ($i=0; $i < 2; $i++) {
  1078. if (Haanga_AST::is_var($details['range'][$i])) {
  1079. $details['range'][$i] = $this->generate_variable_name($details['range'][$i]['var']);
  1080. }
  1081. }
  1082. if (Haanga_AST::is_var($details['step'])) {
  1083. $details['step'] = $this->generate_variable_name($details['step']['var']);
  1084. }
  1085. $body->do_for($details['variable'], $details['range'][0], $details['range'][1], $details['step'], $for_body);
  1086. $this->set_unsafe(hvar($details['variable']));
  1087. }
  1088. if (isset($details['empty'])) {
  1089. $body->do_endif();
  1090. }
  1091. }
  1092. // }}}
  1093. function generate_op_set($details, &$body)
  1094. {
  1095. $var = $this->generate_variable_name($details['var']);
  1096. $this->check_expr($details['expr']);
  1097. $body->decl_raw($var, $details['expr']);
  1098. $body->decl($this->getScopeVariable($var['var']), $var);
  1099. }
  1100. // ifchanged [<var1> <var2] {{{
  1101. protected function generate_op_ifchanged($details, &$body)
  1102. {
  1103. static $ifchanged = 0;
  1104. $ifchanged++;
  1105. $var1 = 'ifchanged'.$ifchanged;
  1106. if (!isset($details['check'])) {
  1107. /* ugly */
  1108. $this->ob_start($body);
  1109. $var2 = hvar('buffer'.$this->ob_start);
  1110. $this->generate_op_code($details['body'], $body);
  1111. $this->ob_start--;
  1112. $body->do_if(hexpr(hexec('isset', hvar($var1)), '==', FALSE, '||', hvar($var1), '!=', $var2));
  1113. $this->do_print($body, $var2);
  1114. $body->decl($var1, $var2);
  1115. } else {
  1116. /* beauty :-) */
  1117. foreach ($details['check'] as $id=>$type) {
  1118. if (!Haanga_AST::is_var($type)) {
  1119. throw new Exception("Unexpected string {$type['string']}, expected a varabile");
  1120. }
  1121. $this_expr = hexpr(hexpr(
  1122. hexec('isset', hvar($var1, $id)), '==', FALSE,
  1123. '||', hvar($var1, $id), '!=', $type
  1124. ));
  1125. if (isset($expr)) {
  1126. $this_expr = hexpr($expr, '||', $this_expr);
  1127. }
  1128. $expr = $this_expr;
  1129. }
  1130. $body->do_if($expr);
  1131. $this->generate_op_code($details['body'], $body);
  1132. $body->decl($var1, $details['check']);
  1133. }
  1134. if (isset($details['else'])) {
  1135. $body->do_else();
  1136. $this->generate_op_code($details['else'], $body);
  1137. }
  1138. $body->do_endif();
  1139. }
  1140. // }}}
  1141. // autoescape ON|OFF {{{
  1142. function generate_op_autoescape($details, &$body)
  1143. {
  1144. $old_autoescape = self::$autoescape;
  1145. self::$autoescape = strtolower($details['value']) == 'on';
  1146. $this->generate_op_code($details['body'], $body);
  1147. self::$autoescape = $old_autoescape;
  1148. }
  1149. // }}}
  1150. // {% spacefull %} Set to OFF strip_whitespace for a block (the compiler option) {{{
  1151. function generate_op_spacefull($details, &$body)
  1152. {
  1153. $old_strip_whitespace = self::$strip_whitespace;
  1154. self::$strip_whitespace = FALSE;
  1155. $this->generate_op_code($details['body'], $body);
  1156. self::$strip_whitespace = $old_strip_whitespace;
  1157. }
  1158. // }}}
  1159. // ob_Start(array &$body) {{{
  1160. /**
  1161. * Start a new buffering
  1162. *
  1163. */
  1164. function ob_start(&$body)
  1165. {
  1166. $this->ob_start++;
  1167. $body->decl('buffer'.$this->ob_start, "");
  1168. }
  1169. // }}}
  1170. // Custom Tags {{{
  1171. function get_custom_tag($name)
  1172. {
  1173. $function = $this->get_function_name($this->name).'_tag_'.$name;
  1174. $this->append .= "\n\n".Haanga_Extension::getInstance('Tag')->getFunctionBody($name, $function);
  1175. return $function;
  1176. }
  1177. /**
  1178. * Generate needed code for custom tags (tags that aren't
  1179. * handled by the compiler).
  1180. *
  1181. */
  1182. function generate_op_custom_tag($details, &$body)
  1183. {
  1184. static $tags;
  1185. if (!$tags) {
  1186. $tags = Haanga_Extension::getInstance('Tag');
  1187. }
  1188. foreach ($details['list'] as $id => $arg) {
  1189. if (Haanga_AST::is_var($arg)) {
  1190. $details['list'][$id] = $this->generate_variable_name($arg['var']);
  1191. }
  1192. }
  1193. $tag_name = $details['name'];
  1194. $tagFunction = $tags->getFunctionAlias($tag_name);
  1195. if (!$tagFunction && !$tags->hasGenerator($tag_name)) {
  1196. $function = $this->get_custom_tag($tag_name, isset($details['as']));
  1197. } else {
  1198. $function = $tagFunction;
  1199. }
  1200. if (isset($details['body'])) {
  1201. /*
  1202. if the custom tag has 'body'
  1203. then it behave the same way as a filter
  1204. */
  1205. $this->ob_start($body);
  1206. $this->generate_op_code($details['body'], $body);
  1207. $target = hvar('buffer'.$this->ob_start);
  1208. if ($tags->hasGenerator($tag_name)) {
  1209. $args = array_merge(array($target), $details['list']);
  1210. $exec = $tags->generator($tag_name, $this, $args);
  1211. if (!$exec InstanceOf Haanga_AST) {
  1212. throw new Exception("Invalid output of custom filter {$tag_name}");
  1213. }
  1214. if ($exec->stack_size() >= 2 || $exec->doesPrint) {
  1215. /*
  1216. The generator returned more than one statement,
  1217. so we assume the output is already handled
  1218. by one of those stmts.
  1219. */
  1220. $body->append_ast($exec);
  1221. $this->ob_start--;
  1222. return;
  1223. }
  1224. } else {
  1225. $exec = hexec($function, $target);
  1226. }
  1227. $this->ob_start--;
  1228. $this->do_print($body, $exec);
  1229. return;
  1230. }
  1231. $var = isset($details['as']) ? $details['as'] : NULL;
  1232. $args = array_merge(array($function), $details['list']);
  1233. if ($tags->hasGenerator($tag_name)) {
  1234. $exec = $tags->generator($tag_name, $this, $details['list'], $var);
  1235. if ($exec InstanceOf Haanga_AST) {
  1236. if ($exec->stack_size() >= 2 || $exec->doesPrint || $var !== NULL) {
  1237. /*
  1238. The generator returned more than one statement,
  1239. so we assume the output is already handled
  1240. by one of those stmts.
  1241. */
  1242. $body->append_ast($exec);
  1243. return;
  1244. }
  1245. } else {
  1246. throw new Exception("Invalid output of the custom tag {$tag_name}");
  1247. }
  1248. } else {
  1249. $fnc = array_shift($args);
  1250. $exec = hexec($fnc);
  1251. foreach ($args as $arg) {
  1252. $exec->param($arg);
  1253. }
  1254. }
  1255. if ($var) {
  1256. $body->decl($var, $exec);
  1257. } else {
  1258. $this->do_print($body, $exec);
  1259. }
  1260. }
  1261. // }}}
  1262. // with <variable> as <var> {{{
  1263. /**
  1264. *
  1265. *
  1266. */
  1267. function generate_op_alias($details, &$body)
  1268. {
  1269. $this->var_alias[ $details['as'] ] = $this->generate_variable_name($details['var']);
  1270. $this->generate_op_code($details['body'], $body);
  1271. unset($this->var_alias[ $details['as'] ] );
  1272. }
  1273. // }}}
  1274. // Custom Filters {{{
  1275. function get_custom_filter($name)
  1276. {
  1277. $function = $this->get_function_name($this->name).'_filter_'.$name;
  1278. $this->append .= "\n\n".Haanga_Extension::getInstance('Filter')->getFunctionBody($name, $function);
  1279. return $function;
  1280. }
  1281. function do_filtering($name, $args)
  1282. {
  1283. static $filter;
  1284. if (!$filter) {
  1285. $filter = Haanga_Extension::getInstance('Filter');
  1286. }
  1287. if (is_array($name)) {
  1288. /*
  1289. prepare array for ($func_name, $arg1, $arg2 ... )
  1290. where $arg1 = last expression and $arg2.. $argX is
  1291. defined in the template
  1292. */
  1293. $args = array_merge($args, $name['args']);
  1294. $name = $name[0];
  1295. }
  1296. if (!$filter->isValid($name)) {
  1297. throw new Exception("{$name} is an invalid filter");
  1298. }
  1299. if ($filter->isSafe($name)) {
  1300. /* check if the filter is return HTML-safe data (to avoid double scape) */
  1301. $this->var_is_safe = TRUE;
  1302. }
  1303. if ($filter->hasGenerator($name)) {
  1304. return $filter->generator($name, $this, $args);
  1305. }
  1306. $fnc = $filter->getFunctionAlias($name);
  1307. if (!$fnc) {
  1308. $fnc = $this->get_custom_filter($name);
  1309. }
  1310. $args = array_merge(array($fnc), $args);
  1311. $exec = call_user_func_array('hexec', $args);
  1312. return $exec;
  1313. }
  1314. function generate_op_filter($details, &$body)
  1315. {
  1316. $this->ob_start($body);
  1317. $this->generate_op_code($details['body'], $body);
  1318. $target = hvar('buffer'.$this->ob_start);
  1319. foreach ($details['functions'] as $f) {
  1320. $param = (isset($exec) ? $exec : $target);
  1321. $exec = $this->do_filtering($f, array($param));
  1322. }
  1323. $this->ob_start--;
  1324. $this->do_print($body, $exec);
  1325. }
  1326. // }}}
  1327. /* variable safety {{{ */
  1328. function set_safe($name)
  1329. {
  1330. if (!Haanga_AST::is_Var($name)) {
  1331. $name = hvar($name)->getArray();
  1332. }
  1333. $this->safes[serialize($name)] = TRUE;
  1334. }
  1335. function set_unsafe($name)
  1336. {
  1337. if (!Haanga_AST::is_Var($name)) {
  1338. $name = hvar($name)->getArray();
  1339. }
  1340. unset($this->safes[serialize($name)]);
  1341. }
  1342. function is_safe($name)
  1343. {
  1344. if ($this->var_is_safe) {
  1345. return TRUE;
  1346. }
  1347. if (isset($this->safes[serialize($name)])) {
  1348. return TRUE;
  1349. }
  1350. return FALSE;
  1351. }
  1352. /* }}} */
  1353. final static function main_cli()
  1354. {
  1355. $argv = $GLOBALS['argv'];
  1356. $haanga = new Haanga_Compiler;
  1357. $code = $haanga->compile_file($argv[1], TRUE);
  1358. if (!isset($argv[2]) || $argv[2] != '--notags') {
  1359. $code = "<?php\n\n$code";
  1360. }
  1361. echo $code;
  1362. }
  1363. }
  1364. /*
  1365. * Local variables:
  1366. * tab-width: 4
  1367. * c-basic-offset: 4
  1368. * End:
  1369. * vim600: sw=4 ts=4 fdm=marker
  1370. * vim<600: sw=4 ts=4
  1371. */