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. |
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. |
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. |
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. |
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 +---------------------------------------------------------------------------------+
42 protected static $block_var=NULL;
44 protected $forloop = array();
46 protected $sub_template = FALSE;
48 protected $check_function = FALSE;
49 protected $blocks=array();
53 * number of blocks :-)
55 protected $in_block=0;
59 protected $ob_start=0;
61 protected $prepend_op;
63 * Context at compile time
67 * Table which contains all variables
68 * aliases defined in the template
72 * Flag the current variable as safe. This means
73 * that escape won't be called if autoescape is
74 * activated (which is activated by default)
76 public $var_is_safe=FALSE;
79 /* compiler options */
80 static protected $autoescape = TRUE;
81 static protected $if_empty = TRUE;
82 static protected $dot_as_object = TRUE;
83 static protected $strip_whitespace = FALSE;
84 static protected $is_exec_enabled = FALSE;
85 static protected $global_context = array();
86 static protected $echo_concat = '.';
87 static protected $enable_load = TRUE;
95 function __construct()
97 $this->generator
= new Haanga_Generator_PHP
;
98 if (self
::$block_var===NULL) {
99 self
::$block_var = '{{block.'.md5('super').'}}';
103 // getOption($option) {{{
104 public static function getOption($option)
107 switch (strtolower($option)) {
109 $value = self
::$enable_load;
112 $value = self
::$if_empty;
115 $value = self
::$autoescape;
117 case 'dot_as_object':
118 $value = self
::$dot_as_object;
121 $value = self
::$echo_concat;
123 case 'strip_whitespace':
124 $value = self
::$strip_whitespace;
126 case 'is_exec_enabled':
128 $value = self
::$is_exec_enabled;
131 $value = self
::$global_context;
138 // setOption($option, $value) {{{
140 * Set Compiler option.
144 public static function setOption($option, $value)
146 switch (strtolower($option)) {
148 self
::$if_empty = (bool)$value;
151 self
::$enable_load = (bool)$value;
153 if ($value == '.' ||
$value == ',') {
154 self
::$echo_concat = $value;
159 self
::$autoescape = (bool)$value;
161 case 'dot_as_object':
162 self
::$dot_as_object = (bool)$value;
164 case 'strip_whitespace':
165 self
::$strip_whitespace = (bool)$value;
167 case 'is_exec_enabled':
169 self
::$is_exec_enabled = (bool)$value;
172 if (!is_array($value)) {
173 $value = array($value);
175 self
::$global_context = $value;
181 // setDebug($file) {{{
182 function setDebug($file)
184 $this->debug
= $file;
191 foreach (array_keys(get_object_vars($this)) as $key) {
192 if (isset($avoid_cleaning[$key])) {
197 $this->generator
= new Haanga_Generator_PHP
;
198 $this->blocks
= array();
199 $this->cycle
= array();
203 // get_template_name() {{{
204 final function get_template_name()
210 // Set template name {{{
211 function set_template_name($path)
213 $file = basename($path);
214 $pos = strpos($file,'.');
215 return ($this->name
= substr($file, 0, $pos));
219 // get_function_name(string $name) {{{
220 function get_function_name($name)
222 return "{$name}_template";
226 // Compile ($code, $name=NULL) {{{
227 final function compile($code, $name=NULL, $file=NULL)
231 if (count(self
::$global_context) > 0) {
232 /* add global variables (if any) to the current context */
233 foreach (self
::$global_context as $var) {
234 $this->set_context($var, $GLOBALS[$var]);
238 $parsed = Haanga_Compiler_Tokenizer
::init($code, $this, $file);
240 $this->subtemplate
= FALSE;
242 $body = new Haanga_AST
;
243 $this->prepend_op
= hcode();
245 if (isset($parsed[0]) && $parsed[0]['operation'] == 'base') {
246 /* {% base ... %} found */
247 $base = $parsed[0][0];
248 $code .= $this->get_base_template($base);
252 if (defined('HAANGA_VERSION')) {
253 $body->decl('HAANGA_VERSION', HAANGA_VERSION
);
257 $func_name = $this->get_function_name($name);
258 if ($this->check_function
) {
259 $body->do_if(hexpr(hexec('function_exists', $func_name), '===', FALSE));
261 if (!empty($this->file
)) {
262 $body->comment("Generated from ".$this->file
);
265 $body->declare_function($func_name);
267 if (count(self
::$global_context) > 0) {
268 $body->do_global(self
::$global_context);
272 $body->do_exec('extract', hvar('vars'));
273 $body->do_if(hexpr(hvar('return'), '==', TRUE));
274 $body->do_exec('ob_start');
278 $this->generate_op_code($parsed, $body);
279 if ($this->subtemplate
) {
280 $expr = $this->expr_call_base_template();
281 $this->do_print($body, $expr);
284 $body->do_if(hexpr(hvar('return'), '==', TRUE));
285 $body->do_return(hexec('ob_get_clean'));
289 $body->do_endfunction();
290 if ($this->check_function
) {
295 if ($this->prepend_op
->stack_size() > 0) {
296 $this->prepend_op
->append_ast($body);
297 $body = $this->prepend_op
;
300 $op_code = $body->getArray(TRUE);
303 $code .= $this->generator
->getCode($op_code);
304 if (!empty($this->append
)) {
305 $code .= $this->append
;
308 if (!empty($this->debug
)) {
309 $op_code['php'] = $code;
310 file_put_contents($this->debug
, print_r($op_code, TRUE), LOCK_EX
);
316 // compile_file($file) {{{
320 * @param string $file File path
321 * @param bool $safe Whether or not add check if the function is already defined
323 * @return Generated PHP code
325 final function compile_file($file, $safe=FALSE, $context=array())
327 if (!is_readable($file)) {
328 throw new Haanga_Compiler_Exception("$file is not a file");
331 $this->_base_dir
= dirname($file);
332 $this->file
= realpath($file);
334 $this->check_function
= $safe;
335 $this->context
= $context;
336 $name = $this->set_template_name($file);
337 return $this->compile(file_get_contents($file), $name, $file);
341 // getOpCodes($code, $file='') {{{
343 * Compile the $code and return the "opcodes"
344 * (the Abstract syntax tree).
346 * @param string $code Template content
347 * @param string $file File path (used for erro reporting)
352 public function getOpCodes($code, $file)
354 $oldfile = $this->file
;
356 $parsed = Haanga_Compiler_Tokenizer
::init($code, $this, $file);
357 $body = new Haanga_AST
;
358 if (isset($parsed[0]) && $parsed[0]['operation'] == 'base') {
359 $this->Error("{% base is not supported on inlines %}");
361 $body = new Haanga_AST
;
362 $this->generate_op_code($parsed, $body);
363 $this->file
= $oldfile;
368 // Error($errtxt) {{{
370 * Throw an exception and appends information about the template (the path and
371 * the last processed line).
375 public function Error($err)
377 throw new Haanga_Compiler_Exception("{$err} in {$this->file}:$this->line");
381 // is_expr methods {{{
382 function is_var_filter($cmd)
384 return isset($cmd['var_filter']);
389 // expr_call_base_template() {{{
391 * Generate code to call base template
394 function expr_call_base_template()
397 $this->get_function_name($this->subtemplate
),
404 // get_base_template($base) {{{
406 * Handle {% base "" %} definition. By default only
407 * static (string) are supported, but this can be overrided
410 * This method load the base class, compile it and return
411 * the generated code.
413 * @param array $base Base structure
415 * @return string Generated source code
417 function get_base_template($base)
419 if (!Haanga_AST
::is_str($base)) {
420 $this->Error("Dynamic inheritance is not supported for compilated templates");
422 $file = $base['string'];
423 list($this->subtemplate
, $new_code) = $this->compile_required_template($file);
424 return $new_code."\n\n";
428 // {% base "foo.html" %} {{{
429 protected function generate_op_base()
431 $this->Error("{% base %} can be only as first statement");
436 protected function generate_op_code($parsed, &$body)
438 if (!is_array($parsed)) {
439 $this->Error("Invalid \$parsed array");
441 foreach ($parsed as $op) {
442 if (!is_array($op)) {
445 if (!isset($op['operation'])) {
446 $this->Error("Malformed array:".print_r($op, TRUE));
448 if (isset($op['line'])) {
449 $this->line
= $op['line'];
452 if ($this->subtemplate
&& $this->in_block
== 0 && $op['operation'] != 'block') {
453 /* ignore most of tokens in subtemplates */
457 $method = "generate_op_".$op['operation'];
458 if (!is_callable(array($this, $method))) {
459 $this->Error("Compiler: Missing method $method");
461 $this->$method($op, $body);
466 // Check the current expr {{{
467 protected function check_expr(&$expr)
469 if (Haanga_AST
::is_expr($expr)) {
470 if ($expr['op_expr'] == 'in') {
471 for ($id=0; $id < 2; $id++
) {
472 if ($this->is_var_filter($expr[$id])) {
473 $expr[$id] = $this->get_filtered_var($expr[$id]['var_filter'], $var);
476 if (Haanga_AST
::is_str($expr[1])) {
477 $expr = hexpr(hexec('strpos', $expr[1], $expr[0]), '!==', FALSE);
481 hexec('is_array', $expr[1]),
482 hexec('array_search', $expr[0], $expr[1]),
483 hexec('strpos', $expr[1], $expr[0])
489 if (is_object($expr)) {
490 $expr = $expr->getArray();
492 $this->check_expr($expr[0]);
493 $this->check_expr($expr[1]);
494 } else if (is_array($expr)) {
495 if ($this->is_var_filter($expr)) {
496 $expr = $this->get_filtered_var($expr['var_filter'], $var);
497 } else if (isset($expr['args'])) {
498 /* check every arguments */
499 foreach ($expr['args'] as &$v) {
500 $this->check_expr($v);
503 } else if (isset($expr['expr_cond'])) {
504 /* Check expr conditions */
505 $this->check_expr($expr['expr_cond']);
506 $this->check_expr($expr['true']);
507 $this->check_expr($expr['false']);
513 // ifequal|ifnot equal <var_filtered|string|number> <var_fitlered|string|number> ... else ... {{{
514 protected function generate_op_ifequal($details, &$body)
516 $if['expr'] = hexpr($details[1], $details['cmp'], $details[2])->getArray();
517 $if['body'] = $details['body'];
518 if (isset($details['else'])) {
519 $if['else'] = $details['else'];
521 $this->generate_op_if($if, $body);
525 // {% if <expr> %} HTML {% else %} TWO {% endif $} {{{
526 protected function generate_op_if($details, &$body)
528 if (self
::$if_empty && $this->is_var_filter($details['expr']) && count($details['expr']['var_filter']) == 1) {
529 /* if we are doing if <Variable> it should check
530 if it exists without throw any warning */
531 $expr = $details['expr'];
532 $expr['var_filter'][] = 'empty';
534 $variable = $this->get_filtered_var($expr['var_filter'], $var);
536 $details['expr'] = hexpr($variable, '===', FALSE)->getArray();
538 $this->check_expr($details['expr']);
539 $expr = Haanga_AST
::fromArrayGetAST($details['expr']);
541 $this->generate_op_code($details['body'], $body);
542 if (isset($details['else'])) {
544 $this->generate_op_code($details['else'], $body);
550 // Override template {{{
551 protected function compile_required_template($file)
553 if (!is_file($file)) {
554 if (isset($this->_base_dir
)) {
555 $file = $this->_base_dir
.'/'.$file;
558 if (!is_file($file)) {
559 $this->Error("can't find {$file} file template");
561 $class = get_class($this);
564 $code = $comp->compile_file($file, $this->check_function
);
565 return array($comp->get_template_name(), $code);
569 // include "file.html" | include <var1> {{{
570 protected function generate_op_include($details, &$body)
572 if (!$details[0]['string']) {
573 $this->Error("Dynamic inheritance is not supported for compilated templates");
575 list($name,$code) = $this->compile_required_template($details[0]['string']);
576 $this->append
.= "\n\n{$code}";
577 $this->do_print($body,
578 hexec($this->get_function_name($name),
579 hvar('vars'), TRUE, hvar('blocks'))
584 // Handle HTML code {{{
585 protected function generate_op_html($details, &$body)
587 $string = Haanga_AST
::str($details['html']);
588 $this->do_print($body, $string);
592 // get_var_filtered {{{
594 * This method handles all the filtered variable (piped_list(X)'s
595 * output in the parser.
598 * @param array $variable (Output of piped_list(B) (parser))
599 * @param array &$varname Variable name
600 * @param bool $accept_string TRUE is string output are OK (ie: block.parent)
605 function get_filtered_var($variable, &$varname, $accept_string=FALSE)
607 $this->var_is_safe
= FALSE;
609 if (count($variable) > 1) {
610 $count = count($variable);
611 $target = $this->generate_variable_name($variable[0]);
613 if (!Haanga_AST
::is_var($target)) {
614 /* block.super can't have any filter */
615 $this->Error("This variable can't have any filter");
618 for ($i=1; $i < $count; $i++
) {
619 $func_name = $variable[$i];
620 if ($func_name == 'escape') {
621 /* to avoid double cleaning */
622 $this->var_is_safe
= TRUE;
624 $args = array(isset($exec) ?
$exec : $target);
625 $exec = $this->do_filtering($func_name, $args);
631 $details = $this->generate_variable_name($variable[0]);
632 $varname = $variable[0];
634 if (!Haanga_AST
::is_var($details) && !$accept_string) {
635 /* generate_variable_name didn't replied a variable, weird case
636 currently just used for {{block.super}}.
638 $this->Error("Invalid variable name {$variable[0]}");
646 // generate_op_print_var {{{
648 * Generate code to print a variable with its filters, if there is any.
650 * All variable (except those flagged as |safe) are automatically
651 * escaped if autoescape is "on".
654 protected function generate_op_print_var($details, &$body)
657 $details = $this->get_filtered_var($details['variable'], $variable, TRUE);
659 if (!Haanga_AST
::is_var($details) && !Haanga_AST
::is_exec($details)) {
660 /* generate_variable_name didn't replied a variable, weird case
661 currently just used for {{block.super}}.
663 $this->do_print($body, $details);
667 if (!$this->is_safe($details) && self
::$autoescape) {
668 $args = array($details);
669 $details = $this->do_filtering('escape', $args);
673 if (is_array($details)) {
674 $details = Haanga_AST
::fromArrayGetAST($details);
676 $this->do_print($body, $details);
680 // {# something #} {{{
681 protected function generate_op_comment($details, &$body)
683 /* comments are annoying */
684 //$body->comment($details['comment']);
688 // {% block 'name' %} ... {% endblock %} {{{
689 protected function generate_op_block($details, &$body)
691 if (is_array($details['name'])) {
693 foreach ($details['name'] as $part) {
694 if (is_string($part)) {
696 } else if (is_array($part)) {
697 if (Haanga_AST
::is_str($part)) {
698 $name .= "{$part['string']}";
699 } elseif (isset($part['object'])) {
700 $name .= "{$part['object']}";
702 $this->Error("Invalid blockname");
707 $details['name'] = substr($name, 0, -1);
710 $this->blocks
[] = $details['name'];
711 $block_name = hvar('blocks', $details['name']);
713 $this->ob_start($body);
714 $buffer_var = 'buffer'.$this->ob_start
;
717 $this->generate_op_code($details['body'], $content);
719 $body->append_ast($content);
722 $buffer = hvar($buffer_var);
726 * isset previous block (parent block)?
728 * has reference to self::$block_var ?
730 * replace self::$block_var for current block value (buffer)
734 * print current block
737 $declare = hexpr_cond(
738 hexec('isset', $block_name),
740 hexpr(hexec('strpos', $block_name, self
::$block_var), '===', FALSE),
742 hexec('str_replace', self
::$block_var, $buffer, $block_name)
746 if (!$this->subtemplate
) {
747 $this->do_print($body, $declare);
749 $body->decl($block_name, $declare);
750 if ($this->in_block
> 1) {
751 $this->do_print($body, $block_name);
754 array_pop($this->blocks
);
760 // regroup <var1> by <field> as <foo> {{{
761 protected function generate_op_regroup($details, &$body)
763 $body->comment("Temporary sorting");
765 $array = $this->get_filtered_var($details['array'], $varname);
767 if (Haanga_AST
::is_exec($array)) {
768 $varname = hvar($details['as']);
769 $body->decl($varname, $array);
771 $var = hvar('item', $details['row']);
773 $body->decl('temp_group', array());
775 $body->do_foreach($varname, 'item', NULL,
776 hcode()->decl(hvar('temp_group', $var, NULL), hvar('item'))
779 $body->comment("Proper format");
780 $body->decl($details['as'], array());
781 $body->do_foreach('temp_group', 'item', 'group',
783 hvar($details['as'], NULL),
784 array("grouper" => hvar('group'), "list" => hvar('item'))
787 $body->comment("Sorting done");
791 // variable context {{{
795 * These two functions are useful to detect if a variable
796 * separated by dot (foo.bar) is an array or object. To avoid
797 * overhead we decide it at compile time, rather than
798 * ask over and over at rendering time.
801 * + If foo exists at compile time,
802 * and it is an array, it would be foo['bar']
803 * otherwise it'd be foo->bar.
804 * + If foo don't exists at compile time,
805 * it would be foo->bar if the compiler option
806 * dot_as_object is TRUE (by default) otherwise
810 * @author gallir (ideas)
813 function set_context($varname, $value)
815 $this->context
[$varname] = $value;
818 function get_context($variable)
820 if (!is_array($variable)) {
821 $variable = array($variable);
823 $varname = $variable[0];
824 if (isset($this->context
[$varname])) {
825 if (count($variable) == 1) {
826 return $this->context
[$varname];
828 $var = & $this->context
[$varname];
829 foreach ($variable as $id => $part) {
831 if (is_array($part) && isset($part['object'])) {
832 if (is_array($part['object']) && isset($part['object']['var'])) {
833 /* object $foo->$bar */
834 $name = $part['object']['var'];
835 $name = $this->get_context($name);
836 if (!isset($var->$name)) {
841 if (!isset($var->$part['object'])) {
844 $var = &$var->$part['object'];
846 } else if (is_object($var)) {
847 if (!isset($var->$part)) {
852 if (!is_scalar($part) ||
empty($part) ||
!isset($var[$part])) {
867 function var_is_object(Array $variable, $default=NULL)
869 $varname = $variable[0];
882 return FALSE; /* these are arrays */
885 $variable = $this->get_context($variable);
886 if (is_array($variable) ||
is_object($variable)) {
887 return is_object($variable);
890 return $default===NULL ? self
::$dot_as_object : $default;
894 // Get variable name {{{
895 protected function generate_variable_name($variable)
897 if (is_array($variable)) {
898 switch ($variable[0]) {
901 $this->Error("Invalid forloop reference outside of a loop");
904 switch ($variable[1]['object']) {
906 $this->forloop
[$this->forid
]['counter'] = TRUE;
907 $variable = 'forcounter1_'.$this->forid
;
910 $this->forloop
[$this->forid
]['counter0'] = TRUE;
911 $variable = 'forcounter0_'.$this->forid
;
914 $this->forloop
[$this->forid
]['counter'] = TRUE;
915 $this->forloop
[$this->forid
]['last'] = TRUE;
916 $variable = 'islast_'.$this->forid
;
919 $this->forloop
[$this->forid
]['first'] = TRUE;
920 $variable = 'isfirst_'.$this->forid
;
923 $this->forloop
[$this->forid
]['revcounter'] = TRUE;
924 $variable = 'revcount_'.$this->forid
;
927 $this->forloop
[$this->forid
]['revcounter0'] = TRUE;
928 $variable = 'revcount0_'.$this->forid
;
933 $variable = $this->generate_variable_name(array_values($variable));
934 $variable = $variable['var'];
938 $this->Error("Unexpected forloop.{$variable[1]}");
940 /* no need to escape it */
941 $this->var_is_safe
= TRUE;
944 if ($this->in_block
== 0) {
945 $this->Error("Can't use block.super outside a block");
947 if (!$this->subtemplate
) {
948 $this->Error("Only subtemplates can call block.super");
950 /* no need to escape it */
951 $this->var_is_safe
= TRUE;
952 return Haanga_AST
::str(self
::$block_var);
955 /* choose array or objects */
957 for ($i=1; $i < count($variable); $i++
) {
958 $var_part = array_slice($variable, 0, $i);
961 if (is_array($variable[$i])) {
962 if (isset($variable[$i]['object'])) {
965 if (!Haanga_AST
::is_var($variable[$i])) {
966 $variable[$i] = current($variable[$i]);
968 $variable[$i] = $this->generate_variable_name($variable[$i]['var']);
972 $is_obj = $this->var_is_object($var_part, 'unknown');
974 if ( $is_obj === TRUE ||
($is_obj == 'unknown' && !$def_arr)) {
975 $variable[$i] = array('object' => $variable[$i]);
982 } else if (isset($this->var_alias
[$variable])) {
983 $variable = $this->var_alias
[$variable]['var'];
986 return hvar($variable)->getArray();
991 public function do_print(Haanga_AST
$code, $stmt)
993 /* Flag this object as a printing one */
994 $code->doesPrint
= TRUE;
996 if (self
::$strip_whitespace && Haanga_AST
::is_str($stmt)) {
997 $stmt['string'] = preg_replace('/\s+/', ' ', $stmt['string']);
1000 if ($this->ob_start
== 0) {
1001 $code->do_echo($stmt);
1005 $buffer = hvar('buffer'.$this->ob_start
);
1006 $code->append($buffer, $stmt);
1012 // for [<key>,]<val> in <array> {{{
1013 protected function generate_op_loop($details, &$body)
1015 if (isset($details['empty'])) {
1016 $body->do_if(hexpr(hexec('count', hvar($details['array'])), '==', 0));
1017 $this->generate_op_code($details['empty'], $body);
1022 $oldid = $this->forid
;
1023 $this->forid
= $oldid+
1;
1024 $this->forloop
[$this->forid
] = array();
1026 if (isset($details['range'])) {
1027 $this->set_safe($details['variable']);
1029 /* variable context */
1030 $var = $this->get_context(is_array($details['array'][0]) ?
$details['array'][0] : array($details['array'][0]));
1031 if (is_array($var)) {
1032 /* let's check if it is an object or array */
1033 $this->set_context($details['variable'], current($var));
1036 /* Check if the array to iterate is an object */
1037 $var = &$details['array'][0];
1038 if (is_string($var) && $this->var_is_object(array($var), FALSE)) {
1039 /* It is an object, call to get_object_vars */
1040 $body->decl($var.'_arr', hexec('get_object_vars', hvar($var)));
1045 $array = $this->get_filtered_var($details['array'], $varname);
1048 if ($this->is_safe(hvar($varname))) {
1049 $this->set_safe(hvar($details['variable']));
1052 $details['array'] = $this->generate_variable_name($details['array']);
1056 $for_body = hcode();
1057 $this->generate_op_code($details['body'], $for_body);
1060 $oid = $this->forid
;
1061 $size = hvar('psize_'.$oid);
1064 if (isset($this->forloop
[$oid]['counter'])) {
1065 $var = hvar('forcounter1_'.$oid);
1066 $body->decl($var, 1);
1067 $for_body->decl($var, hexpr($var, '+', 1));
1072 if (isset($this->forloop
[$oid]['counter0'])) {
1073 $var = hvar('forcounter0_'.$oid);
1074 $body->decl($var, 0);
1075 $for_body->decl($var, hexpr($var, '+', 1));
1080 if (isset($this->forloop
[$oid]['last'])) {
1082 $body->decl('psize_'.$oid, hexec('count', hvar_ex($details['array'])));
1085 $var = 'islast_'.$oid;
1086 $body->decl($var, hexpr(hvar('forcounter1_'.$oid), '==', $size));
1087 $for_body->decl($var, hexpr(hvar('forcounter1_'.$oid), '==', $size));
1092 if (isset($this->forloop
[$oid]['first'])) {
1093 $var = hvar('isfirst_'.$oid);
1094 $body->decl($var, TRUE);
1095 $for_body->decl($var, FALSE);
1100 if (isset($this->forloop
[$oid]['revcounter'])) {
1102 $body->decl('psize_'.$oid, hexec('count', hvar_ex($details['array'])));
1105 $var = hvar('revcount_'.$oid);
1106 $body->decl($var, $size);
1107 $for_body->decl($var, hexpr($var, '-', 1));
1112 if (isset($this->forloop
[$oid]['revcounter0'])) {
1114 $body->decl('psize_'.$oid, hexec('count', hvar_ex($details['array'])));
1117 $var = hvar('revcount0_'.$oid);
1118 $body->decl($var, hexpr($size, "-", 1));
1119 $for_body->decl($var, hexpr($var, '-', 1));
1125 /* Restore old ForID */
1126 $this->forid
= $oldid;
1128 /* Merge loop body */
1129 if (!isset($details['range'])) {
1130 $body->do_foreach($array, $details['variable'], $details['index'], $for_body);
1132 if ($this->is_safe(hvar($varname))) {
1133 $this->set_unsafe($details['variable']);
1136 for ($i=0; $i < 2; $i++
) {
1137 if (Haanga_AST
::is_var($details['range'][$i])) {
1138 $details['range'][$i] = $this->generate_variable_name($details['range'][$i]['var']);
1142 if (Haanga_AST
::is_var($details['step'])) {
1143 $details['step'] = $this->generate_variable_name($details['step']['var']);
1145 $body->do_for($details['variable'], $details['range'][0], $details['range'][1], $details['step'], $for_body);
1146 $this->set_unsafe(hvar($details['variable']));
1149 if (isset($details['empty'])) {
1155 function generate_op_set($details, &$body)
1157 $var = $this->generate_variable_name($details['var']);
1158 $this->check_expr($details['expr']);
1159 $body->decl_raw($var, $details['expr']);
1160 $body->decl(hvar('vars', $var['var']), $var);
1164 // ifchanged [<var1> <var2] {{{
1165 protected function generate_op_ifchanged($details, &$body)
1167 static $ifchanged = 0;
1170 $var1 = 'ifchanged'.$ifchanged;
1171 if (!isset($details['check'])) {
1173 $this->ob_start($body);
1174 $var2 = hvar('buffer'.$this->ob_start
);
1177 $this->generate_op_code($details['body'], $body);
1179 $body->do_if(hexpr(hexec('isset', hvar($var1)), '==', FALSE, '||', hvar($var1), '!=', $var2));
1180 $this->do_print($body, $var2);
1181 $body->decl($var1, $var2);
1184 foreach ($details['check'] as $id=>$type) {
1185 if (!Haanga_AST
::is_var($type)) {
1186 $this->Error("Unexpected string {$type['string']}, expected a varabile");
1189 $this_expr = hexpr(hexpr(
1190 hexec('isset', hvar($var1, $id)), '==', FALSE,
1191 '||', hvar($var1, $id), '!=', $type
1195 $this_expr = hexpr($expr, '&&', $this_expr);
1201 $body->do_if($expr);
1202 $this->generate_op_code($details['body'], $body);
1203 $body->decl($var1, $details['check']);
1206 if (isset($details['else'])) {
1208 $this->generate_op_code($details['else'], $body);
1214 // autoescape ON|OFF {{{
1215 function generate_op_autoescape($details, &$body)
1217 $old_autoescape = self
::$autoescape;
1218 self
::$autoescape = strtolower($details['value']) == 'on';
1219 $this->generate_op_code($details['body'], $body);
1220 self
::$autoescape = $old_autoescape;
1224 // {% spacefull %} Set to OFF strip_whitespace for a block (the compiler option) {{{
1225 function generate_op_spacefull($details, &$body)
1227 $old_strip_whitespace = self
::$strip_whitespace;
1228 self
::$strip_whitespace = FALSE;
1229 $this->generate_op_code($details['body'], $body);
1230 self
::$strip_whitespace = $old_strip_whitespace;
1234 // ob_Start(array &$body) {{{
1236 * Start a new buffering
1239 function ob_start(&$body)
1242 $body->decl('buffer'.$this->ob_start
, "");
1247 function get_custom_tag($name)
1249 $function = $this->get_function_name($this->name
).'_tag_'.$name;
1250 $this->append
.= "\n\n".Haanga_Extension
::getInstance('Tag')->getFunctionBody($name, $function);
1255 * Generate needed code for custom tags (tags that aren't
1256 * handled by the compiler).
1259 function generate_op_custom_tag($details, &$body)
1263 $tags = Haanga_Extension
::getInstance('Tag');
1266 foreach ($details['list'] as $id => $arg) {
1267 if (Haanga_AST
::is_var($arg)) {
1268 $details['list'][$id] = $this->generate_variable_name($arg['var']);
1273 $tag_name = $details['name'];
1274 $tagFunction = $tags->getFunctionAlias($tag_name);
1276 if (!$tagFunction && !$tags->hasGenerator($tag_name)) {
1277 $function = $this->get_custom_tag($tag_name, isset($details['as']));
1279 $function = $tagFunction;
1281 if (isset($details['body'])) {
1283 if the custom tag has 'body'
1284 then it behave the same way as a filter
1286 $this->ob_start($body);
1287 $this->generate_op_code($details['body'], $body);
1288 $target = hvar('buffer'.$this->ob_start
);
1289 if ($tags->hasGenerator($tag_name)) {
1290 $args = array_merge(array($target), $details['list']);
1291 $exec = $tags->generator($tag_name, $this, $args);
1292 if (!$exec InstanceOf Haanga_AST
) {
1293 $this->Error("Invalid output of custom filter {$tag_name}");
1295 if ($exec->stack_size() >= 2 ||
$exec->doesPrint
) {
1297 The generator returned more than one statement,
1298 so we assume the output is already handled
1299 by one of those stmts.
1301 $body->append_ast($exec);
1306 $exec = hexec($function, $target);
1309 $this->do_print($body, $exec);
1313 $var = isset($details['as']) ?
$details['as'] : NULL;
1314 $args = array_merge(array($function), $details['list']);
1316 if ($tags->hasGenerator($tag_name)) {
1317 $exec = $tags->generator($tag_name, $this, $details['list'], $var);
1318 if ($exec InstanceOf Haanga_AST
) {
1319 if ($exec->stack_size() >= 2 ||
$exec->doesPrint ||
$var !== NULL) {
1321 The generator returned more than one statement,
1322 so we assume the output is already handled
1323 by one of those stmts.
1325 $body->append_ast($exec);
1329 $this->Error("Invalid output of the custom tag {$tag_name}");
1332 $fnc = array_shift($args);
1333 $exec = hexec($fnc);
1334 foreach ($args as $arg) {
1340 $body->decl($var, $exec);
1342 $this->do_print($body, $exec);
1347 // with <variable> as <var> {{{
1352 function generate_op_alias($details, &$body)
1354 $this->var_alias
[ $details['as'] ] = $this->generate_variable_name($details['var']);
1355 $this->generate_op_code($details['body'], $body);
1356 unset($this->var_alias
[ $details['as'] ] );
1360 // Custom Filters {{{
1361 function get_custom_filter($name)
1363 $function = $this->get_function_name($this->name
).'_filter_'.$name;
1364 $this->append
.= "\n\n".Haanga_Extension
::getInstance('Filter')->getFunctionBody($name, $function);
1369 function do_filtering($name, $args)
1373 $filter = Haanga_Extension
::getInstance('Filter');
1376 if (is_array($name)) {
1378 prepare array for ($func_name, $arg1, $arg2 ... )
1379 where $arg1 = last expression and $arg2.. $argX is
1380 defined in the template
1382 $args = array_merge($args, $name['args']);
1386 if (!$filter->isValid($name)) {
1387 $this->Error("{$name} is an invalid filter");
1390 if ($filter->isSafe($name)) {
1391 /* check if the filter is return HTML-safe data (to avoid double scape) */
1392 $this->var_is_safe
= TRUE;
1396 if ($filter->hasGenerator($name)) {
1397 return $filter->generator($name, $this, $args);
1399 $fnc = $filter->getFunctionAlias($name);
1401 $fnc = $this->get_custom_filter($name);
1404 $args = array_merge(array($fnc), $args);
1405 $exec = call_user_func_array('hexec', $args);
1410 function generate_op_filter($details, &$body)
1412 $this->ob_start($body);
1413 $this->generate_op_code($details['body'], $body);
1414 $target = hvar('buffer'.$this->ob_start
);
1415 foreach ($details['functions'] as $f) {
1416 $param = (isset($exec) ?
$exec : $target);
1417 $exec = $this->do_filtering($f, array($param));
1420 $this->do_print($body, $exec);
1424 /* variable safety {{{ */
1425 function set_safe($name)
1427 if (!Haanga_AST
::is_Var($name)) {
1428 $name = hvar($name)->getArray();
1430 $this->safes
[serialize($name)] = TRUE;
1433 function set_unsafe($name)
1435 if (!Haanga_AST
::is_Var($name)) {
1436 $name = hvar($name)->getArray();
1438 unset($this->safes
[serialize($name)]);
1441 function is_safe($name)
1443 if ($this->var_is_safe
) {
1446 if (isset($this->safes
[serialize($name)])) {
1453 final static function main_cli()
1455 $argv = $GLOBALS['argv'];
1456 $haanga = new Haanga_Compiler
;
1457 $code = $haanga->compile_file($argv[1], TRUE);
1458 if (!isset($argv[2]) ||
$argv[2] != '--notags') {
1459 $code = "<?php\n\n$code";
1471 * vim600: sw=4 ts=4 fdm=marker
1472 * vim<600: sw=4 ts=4