3 +---------------------------------------------------------------------------------+
4 | Copyright (c) 2010 Haanga |
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 +---------------------------------------------------------------------------------+
38 define('HAANGA_DIR', dirname(__FILE__
));
40 // Load needed files {{{
41 require HAANGA_DIR
."/lexer.php";
42 require HAANGA_DIR
."/generator.php";
43 require HAANGA_DIR
."/extensions.php";
44 require HAANGA_DIR
."/tags.php";
45 require HAANGA_DIR
."/filters.php";
48 // Exception Class {{{
53 class Haanga_CompilerException
extends Exception
63 protected static $block_var=NULL;
65 protected $forloop = array();
67 protected $sub_template = FALSE;
69 protected $blocks=array();
71 * number of blocks :-)
73 protected $in_block=0;
77 protected $ob_start=0;
79 protected $prepend_op;
81 * Table which contains all variables
82 * aliases defined in the template
86 * Flag the current variable as safe. This means
87 * that escape won't be called if autoescape is
88 * activated (which is activated by default)
90 public $var_is_safe=FALSE;
91 protected $autoescape=TRUE;
92 protected $force_whitespaces=0;
99 function __construct()
101 $this->generator
= new Haanga_CodeGenerator
;
102 if (self
::$block_var===NULL) {
103 self
::$block_var = '{{block.'.md5('super').'}}';
107 // setDebug($file) {{{
108 function setDebug($file)
110 $this->debug
= $file;
117 $avoid_cleaning = array('strip_whitespaces' => 1, 'block_var' => 1, 'autoescape'=>1);
118 foreach (array_keys(get_object_vars($this)) as $key) {
119 if (isset($avoid_cleaning[$key])) {
124 $this->generator
= new Haanga_CodeGenerator
;
125 $this->blocks
= array();
126 $this->cycle
= array();
130 // get_template_name() {{{
131 final function get_template_name()
137 // Set template name {{{
138 function set_template_name($path)
140 return ($this->name
= strstr(basename($path),'.', TRUE));
144 // get_function_name(string $name) {{{
145 function get_function_name($name)
147 return "{$name}_template";
151 // Compile ($code, $name=NULL) {{{
152 final function compile($code, $name=NULL)
156 $parsed = do_parsing($code);
158 $this->subtemplate
= FALSE;
160 if ($parsed[0]['operation'] == 'base') {
161 /* {% base ... %} found */
162 $base = $parsed[0][0];
163 $code .= $this->get_base_template($base);
168 if (isset($this->_file
)) {
169 $op_code[] = $this->op_comment("Generated from {$this->_base_dir}/{$this->_file}");
171 $op_code[] = $this->op_declare_function($this->get_function_name($name));
172 $op_code[] = $this->op_expr($this->expr_exec('extract', $this->expr_var('vars')));
175 $this->ob_start($op_code);
176 $this->generate_op_code($parsed, $op_code);
177 if ($this->subtemplate
) {
178 $expr = $this->expr_call_base_template();
179 $this->generate_op_print(array('expr' => $expr), $op_code);
184 $expr = $this->expr('==', $this->expr_var('return'), TRUE);
185 $op_code[] = $this->op_if($expr);
186 $op_code[] = $this->op_return($this->expr_var('buffer1'));
187 $op_code[] = $this->op_else();
188 $this->generate_op_print(array('variable' => 'buffer1'), $op_code);
189 $op_code[] = $this->op_end('if');
192 $op_code[] = $this->op_end('function');
195 if (count($this->prepend_op
)) {
196 $op_code = array_merge($this->prepend_op
, $op_code);
199 $code .= $this->generator
->getCode($op_code);
200 if (!empty($this->append
)) {
201 $code .= $this->append
;
203 if (!empty($this->debug
)) {
204 file_put_contents($this->debug
, print_r($op_code, TRUE));
210 // compile_file($file) {{{
214 * @param string $file File path
216 * @return Generated PHP code
218 final function compile_file($file)
220 if (!is_readable($file)) {
221 throw new Haanga_CompilerException("$file is not a file");
223 $this->_base_dir
= dirname($file);
224 $this->_file
= basename($file);
225 $name = $this->set_template_name($file);
226 return $this->compile(file_get_contents($file), $name);
230 // is_expr methods {{{
231 function is_expr(Array $cmd, $type=NULL)
233 if (isset($cmd['op_expr'])) {
234 if (!$type ||
$type == $cmd['op_expr']) {
241 function is_exec(Array $cmd)
243 return $this->is_expr($cmd, 'exec');
246 function is_var(Array $cmd)
248 return isset($cmd['var']);
252 // op_* helper methods {{{
254 * Return an stand alone expression
257 function op_expr($expr)
259 return array('op' => 'expr', $expr);
262 function op_comment($comment)
264 return array('op' => 'comment', 'comment' => $comment);
267 function op_foreach($array, $value, $key=NULL)
269 foreach (array('array', 'value', 'key') as $var) {
271 if (is_array($var) && isset($var['var'])) {
276 $def = array('op' => 'foreach', 'array' => $array, 'value' => $value);
283 function op_if($expr)
285 return array('op' => 'if', 'expr' => $expr);
290 return array('op' => 'else');
293 function op_return($expr)
295 return array('op' => 'return', $expr);
300 return array('op' => "end_{$op}");
303 function op_declare($name, $value)
305 if (is_array($name)) {
306 if (isset($name['var'])) {
307 $name = $name['var'];
311 if (is_array($value)) {
312 if (isset($value['op_expr'])) {
313 $value = array('expr' => $value);
317 return array('op' => 'declare', 'name' => $name, $value);
320 function op_append($name, $expr)
322 return array('op' => 'append_var', 'name' => $name, $expr);
326 function op_inc($name)
328 return array('op' => 'inc', 'name' => $name);
331 function op_declare_function($name)
333 return array('op' => 'function', 'name' => $name);
338 // expr_* helper methods {{{
339 function expr_cond($expr, $true, $false)
341 return array('expr_cond' => $expr, 'true' => $true, 'false' => $false);
344 function expr_const($name)
346 return array('constant' => $name);
350 * Generate code to call base template
353 function expr_call_base_template()
355 return $this->expr_exec(
356 $this->get_function_name($this->subtemplate
),
357 $this->expr_var('vars'),
359 $this->expr_var('blocks')
364 * return a function call for isset($var) === $isset
368 final function expr_isset($var, $isset=TRUE)
370 return $this->expr('==', $this->expr_exec('isset', $this->expr_var($var)), $isset);
373 final function expr_isset_ex($var, $isset=TRUE)
375 return $this->expr('==', $this->expr_exec('isset', $var), $isset);
378 final function expr_array()
381 foreach (func_get_args() as $arg) {
382 if (count($arg) == 2) {
383 if (!is_array($arg[0])) {
384 $arg[0] = $this->expr_str($arg[0]);
386 $arg = array('key' => $arg);
390 return array("array" => $def);
393 final function expr_array_first($values)
396 foreach ($values as $arg) {
397 if (count($arg) == 2) {
398 if (!is_array($arg[0])) {
399 $arg[0] = $this->expr_str($arg[0]);
401 $arg = array('key' => $arg);
405 return array("array" => $def);
410 * return an number definition of $num
414 final function expr_number($num=0)
416 return array('number' =>$num);
420 * return an string definition of $str
424 final function expr_str($str='')
426 return array('string' => $str);
430 * Generate expression that for
435 final function expr_TRUE()
437 return array('expr' => TRUE);
441 * Generate expression that for
446 final function expr_FALSE()
448 return array('expr' => FALSE);
452 * Return expr for variable reference
456 final function expr_var($var)
458 return array('var' => func_get_args());
462 * Return expr for variable reference
466 final function expr_var_ex(Array $var)
468 return array('var' => $var);
472 * Generate expression for
473 * a function calling inside an expression
477 final function expr_exec($function)
479 $args = func_get_args();
481 $args = array_values($args);
492 * Generate a generic expression
496 final function expr($operation, $expr1, $expr2=NULL)
498 $expr = array('op_expr' => $operation, $expr1);
499 if ($expr2 !== NULL) {
507 // get_base_template($base) {{{
509 * Handle {% base "" %} definition. By default only
510 * static (string) are supported, but this can be overrided
513 * This method load the base class, compile it and return
514 * the generated code.
516 * @param array $base Base structure
518 * @return string Generated source code
520 function get_base_template($base)
522 if (!isset($base['string'])) {
523 throw new Haanga_CompilerException("Dynamic inheritance is not supported for compilated templates");
525 $file = $base['string'];
526 list($this->subtemplate
, $new_code) = $this->compile_required_template($file);
527 return $new_code."\n\n";
531 // {% base "foo.html" %} {{{
532 protected function generate_op_base()
534 throw new exception("{% base %} can be only as first statement");
539 protected function generate_op_code($parsed, &$out)
541 if (!is_array($parsed)) {
542 throw new Haanga_CompilerException("Invalid \$parsed array");
544 foreach ($parsed as $op) {
545 if (!isset($op['operation'])) {
546 throw new Haanga_CompilerException("Malformed $parsed array");
548 if ($this->subtemplate
&& $this->in_block
== 0 && $op['operation'] != 'block') {
549 /* ignore most of tokens in subtemplates */
552 $method = "generate_op_".$op['operation'];
553 if (!is_callable(array($this, $method))) {
554 throw new Haanga_CompilerException("Compiler: Missing method $method");
556 $this->$method($op, $out);
561 // Check the current expr {{{
562 protected function check_expr(&$expr)
564 if (is_array($expr) && isset($expr['op_expr'])) {
565 if ($expr['op_expr'] == 'in') {
566 if (isset($expr[1]['string'])) {
567 $expr = $this->expr("!==",
568 $this->expr_exec("strpos", $expr[1], $expr[0]),
572 $expr = $this->expr("!==", $this->expr_cond(
573 $this->expr("==", $this->expr_exec("is_array", $expr[1]), TRUE),
574 $this->expr_exec("array_search", $expr[0], $expr[1]),
575 $this->expr_exec("strpos", $expr[1], $expr[0])
580 $this->check_expr($expr[0]);
581 $this->check_expr($expr[1]);
583 if (is_array($expr)) {
584 if (isset($expr['var'])) {
585 $expr = $this->generate_variable_name($expr['var']);
586 } else if (isset($expr['var_filter'])) {
587 foreach ($expr['var_filter'] as $id => $f) {
589 $exec = $this->generate_variable_name($f);
591 $exec = $this->expr_exec($f, $exec);
595 } else if (isset($expr['args'])) {
596 /* check every arguments */
597 foreach ($expr['args'] as &$v) {
598 $this->check_expr($v);
601 } else if (isset($expr['expr_cond'])) {
602 /* Check expr conditions */
603 $this->check_expr($expr['expr_cond']);
604 $this->check_expr($expr['true']);
605 $this->check_expr($expr['false']);
612 // {% if <expr> %} HTML {% else %} TWO {% endif $} {{{
613 protected function generate_op_if($details, &$out)
615 $this->check_expr($details['expr']);
616 $out[] = $this->op_if($details['expr']);
617 $this->generate_op_code($details['body'], $out);
618 if (isset($details['else'])) {
619 $out[] = $this->op_else();
620 $this->generate_op_code($details['else'], $out);
622 $out[] = $this->op_end('if');
626 // Overload template {{{
627 protected function compile_required_template($file)
629 if (!is_file($file)) {
630 if (isset($this->_base_dir
)) {
631 $file = $this->_base_dir
.'/'.$file;
634 if (!is_file($file)) {
635 throw new Haanga_CompilerException("can't find {$file} file template");
637 $class = get_class($this);
640 $code = $comp->compile_file($file);
641 return array($comp->get_template_name(), $code);
645 // include "file.html" | include <var1> {{{
646 protected function generate_op_include($details, &$out)
648 if (!$details[0]['string']) {
649 throw new Haanga_CompilerException("Dynamic inheritance is not supported for compilated templates");
651 list($name,$code) = $this->compile_required_template($details[0]['string']);
652 $this->append
.= "\n\n{$code}";
653 $expr = $this->expr_exec(
654 $this->get_function_name($this->subtemplate
),
655 $this->expr_var('vars'),
656 $this->expr_var('blocks'),
659 $this->generate_op_print($expr, $op_code);
660 $this->generate_op_print($expr, $out);
664 // Handle HTML code {{{
665 protected function generate_op_html($details, &$out)
667 $this->generate_op_print($details, $out);
671 // get_var_filtered {{{
673 * This method handles all the filtered variable (piped_list(X)'s
674 * output in the parser.
677 * @param array $variable (Output of piped_list(B) (parser))
678 * @param array &$varname Variable name
679 * @param bool $accept_string TRUE is string output are OK (ie: block.parent)
684 function get_filtered_var($variable, &$varname, $accept_string=FALSE)
686 $this->var_is_safe
= FALSE;
687 if (count($variable) > 1) {
688 $count = count($variable);
689 $target = $this->generate_variable_name($variable[0]);
691 if (!isset($target['var'])) {
692 /* block.super can't have any filter */
693 throw new Haanga_CompilerException("This variable can't have any filter");
696 for ($i=1; $i < $count; $i++
) {
697 $func_name = $variable[$i];
698 if ($func_name == 'escape') {
699 /* to avoid double cleaning */
700 $this->var_is_safe
= TRUE;
702 $args = array(isset($exec) ?
$exec : $target);
703 $exec = $this->do_filtering($func_name, $args);
709 $details = $this->generate_variable_name($variable[0]);
710 $varname = $variable[0];
712 if (!isset($details['var']) && !$accept_string) {
713 /* generate_variable_name didn't replied a variable, weird case
714 currently just used for {{block.super}}.
716 throw new Haanga_CompilerException("Invalid variable name {$variable[0]}");
724 // generate_op_print_var {{{
726 * Generate code to print a variable with its filters, if there is any.
728 * All variable (except those flagged as |safe) are automatically
729 * escaped if autoescape is "on".
732 protected function generate_op_print_var($details, &$out)
735 $details = $this->get_filtered_var($details['variable'], $variable, TRUE);
737 if (!isset($details['var']) && !isset($details['exec'])) {
738 /* generate_variable_name didn't replied a variable, weird case
739 currently just used for {{block.super}}.
741 $this->generate_op_print($details, $out);
746 if (!$this->var_is_safe
&& $this->autoescape
) {
747 $args = array($details);
748 $details = $this->do_filtering('escape', $args);
751 $this->generate_op_print($details, $out);
755 // is_last_op_print($out) {{{
757 * Return TRUE if the last stacked operation
758 * is a print (declare or append_var).
760 * @param array $out Stack of operations
764 protected function is_last_op_print($out)
766 $last = count($out)-1;
767 $sprint = array('print', 'declare', 'append_var');
768 return $last >= 0 && array_search($out[$last]['op'], $sprint) !== FALSE;
772 // {# something #} {{{
773 protected function generate_op_comment($details, &$out)
775 if ($this->is_last_op_print($out)) {
776 /* If there is a print declared previously, we pop it
777 and add it after the cycle declaration
779 $old_print = array_pop($out);
781 $out[] = $this->op_comment($details['comment']);
782 if (isset($old_print)) {
788 // {% block 'name' %} ... {% endblock %} {{{
789 protected function generate_op_block($details, &$out)
792 $this->blocks
[] = $details['name'];
793 $block_name = $this->expr_var('blocks', $details['name']);
795 $this->ob_start($out);
796 $buffer_var = 'buffer'.$this->ob_start
;
798 $this->generate_op_code($details['body'], $body);
800 $out = array_merge($out, $body);
803 $buffer = $this->expr_var($buffer_var);
807 * isset previous block (parent block)?
809 * has reference to self::$block_var ?
811 * replace self::$block_var for current block value (buffer)
815 * print current block
818 $declare = $this->expr_cond(
819 $this->expr_isset_ex($block_name),
821 $this->expr("===", $this->expr_exec('strpos', $block_name,
822 $this->expr_str(self
::$block_var)
823 ), $this->expr_FALSE()
826 $this->expr_exec('str_replace',
827 $this->expr_str(self
::$block_var),
836 if (!$this->subtemplate
) {
837 $this->generate_op_print($declare, $out);
839 $out[] = $this->op_declare($block_name, $declare);
840 if ($this->in_block
> 1) {
841 $this->generate_op_print($block_name, $out);
844 array_pop($this->blocks
);
850 // regroup <var1> by <field> as <foo> {{{
851 protected function generate_op_regroup($details, &$out)
853 $out[] = $this->op_comment("Temporary sorting");
854 $array = $this->get_filtered_var($details['array'], $varname);
856 if (isset($array['exec'])) {
857 $varname = $this->expr_var($details['as']);
858 $out[] = $this->op_declare($varname, $array);
860 $var = $this->expr_var('item', $details['row']);
862 $out[] = $this->op_declare('temp_group', $this->expr_array());
863 $out[] = $this->op_foreach($varname, 'item');
866 $out[] = $this->op_declare(array('temp_group', $var, NULL), $this->expr_var('item'));
867 $out[] = $this->op_end('foreach');
869 $out[] = $this->op_comment("Proper format");
870 $out[] = $this->op_declare($details['as'], $this->expr_array_first(array()));
871 $out[] = $this->op_foreach('temp_group', 'item', 'group');
873 $array = $this->expr_array(
874 array("grouper", $this->expr_var('group')),
875 array("list", $this->expr_var('item'))
878 $out[] = $this->op_declare(array($details['as'], NULL), $array );
880 $out[] = $this->op_end('foreach');
881 $out[] = $this->op_comment("Sorting done");
885 // Get variable name {{{
886 protected function generate_variable_name($variable)
888 if (is_array($variable)) {
889 switch ($variable[0]) {
892 throw new Haanga_CompilerException("Invalid forloop reference outside of a loop");
894 switch ($variable[1]) {
896 $this->forloop
[$this->forid
]['counter'] = TRUE;
897 $variable = 'forcounter1_'.$this->forid
;
900 $this->forloop
[$this->forid
]['counter0'] = TRUE;
901 $variable = 'forcounter0_'.$this->forid
;
904 $this->forloop
[$this->forid
]['counter'] = TRUE;
905 $this->forloop
[$this->forid
]['last'] = TRUE;
906 $variable = 'islast_'.$this->forid
;
909 $this->forloop
[$this->forid
]['first'] = TRUE;
910 $variable = 'isfirst_'.$this->forid
;
913 $this->forloop
[$this->forid
]['revcounter'] = TRUE;
914 $variable = 'revcount_'.$this->forid
;
917 $this->forloop
[$this->forid
]['revcounter0'] = TRUE;
918 $variable = 'revcount0_'.$this->forid
;
923 $variable = $this->generate_variable_name(array_values($variable));
924 $variable = $variable['var'];
928 throw new Haanga_CompilerException("Unexpected forloop.{$variable[1]}");
930 /* no need to escape it */
931 $this->var_is_safe
= TRUE;
934 if ($this->in_block
== 0) {
935 throw new Haanga_CompilerException("Can't use block.super outside a block");
937 if (!$this->subtemplate
) {
938 throw new Haanga_CompilerException("Only subtemplates can call block.super");
940 /* no need to escape it */
941 $this->var_is_safe
= TRUE;
942 return $this->expr_str(self
::$block_var);
946 } else if (isset($this->var_alias
[$variable])) {
947 $variable = $this->var_alias
[$variable];
950 return $this->expr_var($variable);
955 public function generate_op_print($details, &$out)
957 $last = count($out)-1;
958 if (isset($details['variable'])) {
959 $content = $this->generate_variable_name($details['variable']);
960 } else if (isset($details['html'])) {
961 $html = $details['html'];
962 $content = $this->expr_str($html);
967 $var_name = 'buffer'.$this->ob_start
;
969 if ($this->ob_start
== 0) {
970 $operation = 'print';
972 $operation = 'append_var';
975 if ($last >= 0 && $out[$last]['op'] == $operation && ($operation != 'append_var' ||
$out[$last]['name'] === $var_name)) {
976 /* try to append this to the previous print if it exists */
977 $out[$last][] = $content;
979 if ($this->ob_start
== 0) {
980 $out[] = array('op' => 'print', $content);
982 if (isset($out[$last]) && $out[$last]['op'] == 'declare' && $out[$last]['name'] == $var_name) {
983 /* override an empty declaration of a empty buffer
984 if the next operation is an 'append'
986 $out[$last][] = $content;
988 $out[] = $this->op_append('buffer'.$this->ob_start
, $content);
995 // for [<key>,]<val> in <array> {{{
996 protected function generate_op_loop($details, &$out)
998 if (isset($details['empty'])) {
999 $expr = $this->expr('==',
1000 $this->expr_exec('count', $this->expr_var($details['array'])),
1004 $out[] = $this->op_if($expr);
1005 $this->generate_op_code($details['empty'], $out);
1006 $out[] = $this->op_else();
1010 $oldid = $this->forid
;
1011 $this->forid
= $oldid+
1;
1013 $this->forloop
[$this->forid
] = array();
1016 $for_loop_body = array();
1017 $this->generate_op_code($details['body'], $for_loop_body);
1019 $oid = $this->forid
;
1020 $size = $this->expr_var('psize_'.$oid);
1023 if (isset($this->forloop
[$oid]['counter'])) {
1024 $var = 'forcounter1_'.$oid;
1025 $out[] = $this->op_declare($var, $this->expr_number(1));
1026 $for_loop_body[] = $this->op_inc($var);
1031 if (isset($this->forloop
[$oid]['counter0'])) {
1032 $var = 'forcounter0_'.$oid;
1033 $out[] = $this->op_declare($var, $this->expr_number(0) );
1034 $for_loop_body[] = $this->op_inc($var);
1039 if (isset($this->forloop
[$oid]['last'])) {
1041 $cnt = $this->op_declare('psize_'.$oid, $this->expr_exec('count', $this->expr_var($details['array'])));
1044 $var = 'islast_'.$oid;
1045 $expr = $this->op_declare($var, $this->expr("==", $this->expr_var('forcounter1_'.$oid), $size));
1049 $for_loop_body[] = $expr;
1054 if (isset($this->forloop
[$oid]['first'])) {
1055 $out[] = $this->op_declare('isfirst_'.$oid, $this->expr_TRUE());
1057 $for_loop_body[] = $this->op_declare('isfirst_'.$oid, $this->expr_FALSE());
1062 if (isset($this->forloop
[$oid]['revcounter'])) {
1064 $cnt = $this->op_declare('psize_'.$oid, $this->expr_exec('count', $this->expr_var($details['array'])));
1067 $var = $this->expr_var('revcount_'.$oid);
1068 $out[] = $this->op_declare($var, $size );
1070 $for_loop_body[] = $this->op_declare($var, $this->expr("-", $var, $this->expr_number(1)));
1075 if (isset($this->forloop
[$oid]['revcounter0'])) {
1077 $cnt = $this->op_declare('psize_'.$oid, $this->expr_exec('count', $this->expr_var($details['array'])));
1080 $var = $this->expr_var('revcount0_'.$oid);
1081 $out[] = $this->op_declare($var, $this->expr("-", $size, $this->expr_number(1)));
1083 $for_loop_body[] = $this->op_declare($var, $this->expr("-", $var, $this->expr_number(1)));
1087 /* Restore old ForID */
1088 $this->forid
= $oldid;
1090 /* Merge loop body */
1091 $array = $this->get_filtered_var($details['array'], $varname);
1092 $loop = $this->op_foreach($array, $details['variable'], $details['index']);
1095 $out = array_merge($out, $for_loop_body);
1096 $out[] = $this->op_end('foreach');
1097 if (isset($details['empty'])) {
1098 $out[] = $this->op_end('if');
1103 // ifchanged [<var1> <var2] {{{
1104 protected function generate_op_ifchanged($details, &$out)
1106 static $ifchanged = 0;
1109 $var1 = 'ifchanged'.$ifchanged;
1110 if (!isset($details['check'])) {
1112 $this->ob_start($out);
1113 $var2 = 'buffer'.$this->ob_start
;
1115 $expr = $this->expr('OR',
1116 $this->expr_isset($var1, FALSE),
1118 $this->expr_var($var1),
1119 $this->expr_var($var2)
1123 $this->generate_op_code($details['body'], $out);
1125 $out[] = $this->op_if($expr);
1126 $this->generate_op_print(array('variable' => $var2), $out);
1127 $out[] = $this->op_declare($var1, $this->expr_var($var2));
1130 foreach ($details['check'] as $id=>$type) {
1131 if (!isset($type['var'])) {
1132 throw new Haanga_CompilerException("Unexpected string {$type['string']}, expected a varabile");
1134 $this_expr = $this->expr('OR',
1135 $this->expr_isset("{$var1}[{$id}]", FALSE),
1137 $this->expr_var("{$var1}[{$id}]"),
1138 $this->expr_var($type['var'])
1142 $this_expr = $this->expr('AND',
1143 $this->expr('expr', $this_expr),
1151 $out[] = $this->op_if($expr);
1152 $this->generate_op_code($details['body'], $out);
1153 $out[] = $this->op_declare($var1, $this->expr_array_first($details['check']));
1156 if (isset($details['else'])) {
1157 $out[] = $this->op_else();
1158 $this->generate_op_code($details['else'], $out);
1160 $out[] = $this->op_end('if');
1164 // autoescape ON|OFF {{{
1165 function generate_op_autoescape($details, &$out)
1167 $old_autoescape = $this->autoescape
;
1168 $this->autoescape
= strtolower($details['value']) == 'on';
1169 $this->generate_op_code($details['body'], $out);
1170 $this->autoescape
= $old_autoescape;
1174 // ob_Start(array &$out) {{{
1176 * Start a new buffering
1179 function ob_start(&$out)
1182 $out[] = $this->op_declare('buffer'.$this->ob_start
, array('string' => ''));
1187 function get_custom_tag($name)
1189 $function = $this->get_function_name().'_tag_'.$name;
1190 $this->append
.= "\n\n".Haanga_Extensions
::getInstance('Haanga_Tag')->getFunctionBody($name, $function);
1195 * Generate needed code for custom tags (tags that aren't
1196 * handled by the compiler).
1199 function generate_op_custom_tag($details, &$out)
1203 $tags = Haanga_Extensions
::getInstance('Haanga_Tag');
1206 $tag_name = $details['name'];
1207 $tagFunction = $tags->getFunctionAlias($tag_name);
1209 if (!$tagFunction && !$tags->hasGenerator($tag_name)) {
1210 $function = $this->get_custom_tag($tag_name, isset($details['as']));
1212 $function = $tagFunction;
1215 if (isset($details['body'])) {
1217 if the custom tag has 'body'
1218 then it behave the same way as a filter
1220 $this->ob_start($out);
1221 $this->generate_op_code($details['body'], $out);
1222 $target = $this->expr_var('buffer'.$this->ob_start
);
1223 if ($tags->hasGenerator($tag_name)) {
1224 $exec = $tags->generator($tag_name, $this, array($target));
1226 $exec = $this->expr_exec($function, $target);
1229 $this->generate_op_print($exec, $out);
1233 $var = isset($details['as']) ?
$details['as'] : NULL;
1234 $args = array_merge(array($function), $details['list']);
1236 if ($tags->hasGenerator($tag_name)) {
1237 $exec = $tags->generator($tag_name, $this, $details['list'], $var);
1238 if ($exec InstanceOf ArrayIterator
) {
1240 The generator returned more than one statement,
1241 so we assume the output is already handled
1242 by one of those stmts.
1244 $out = array_merge($out, $exec->getArrayCopy());
1248 $exec = call_user_func_array(array($this, 'expr_exec'), $args);
1252 $out[] = $this->op_declare($var, $exec);
1254 $this->generate_op_print($exec, $out);
1259 // with <variable> as <var> {{{
1264 function generate_op_alias($details, &$out)
1266 $this->var_alias
[ $details['as'] ] = $details['var'];
1267 $this->generate_op_code($details['body'], $out);
1268 unset($this->var_alias
[ $details['as'] ] );
1272 // Custom Filters {{{
1273 function get_custom_filter($name)
1275 $function = $this->get_function_name().'_filter_'.$name;
1276 $this->append
.= "\n\n".Haanga_Extensions
::getInstance('Haanga_Filter')->getFunctionBody($name, $function);
1281 function do_filtering($name, $args)
1285 $filter = Haanga_Extensions
::getInstance('Haanga_Filter');
1288 if (is_array($name)) {
1290 prepare array for ($func_name, $arg1, $arg2 ... )
1291 where $arg1 = last expression and $arg2.. $argX is
1292 defined in the template
1294 $args = array_merge($args, $name['args']);
1298 if (!$filter->isValid($name)) {
1299 throw new Haanga_CompilerException("{$name} is an invalid filter");
1301 if ($filter->hasGenerator($name)) {
1302 return $filter->generator($name, $this, $args);
1304 $fnc = $filter->getFunctionAlias($name);
1306 $fnc = $this->get_custom_filter($name);
1308 $args = array_merge(array($fnc), $args);
1309 $exec = call_user_func_array(array($this, 'expr_exec'), $args);
1314 function generate_op_filter($details, &$out)
1316 $this->ob_start($out);
1317 $this->generate_op_code($details['body'], $out);
1318 $target = $this->expr_var('buffer'.$this->ob_start
);
1319 foreach ($details['functions'] as $f) {
1320 $param = (isset($exec) ?
$exec : $target);
1321 $exec = $this->do_filtering($f, array($param));
1324 $this->generate_op_print($exec, $out);
1328 final static function main_cli()
1330 $argv = $GLOBALS['argv'];
1331 $haanga = new Haanga_Compiler
;
1332 $code = $haanga->compile_file($argv[1]);
1333 echo "<?php\n\n$code\n";
1343 final class Haanga_Compiler_Runtime
extends Haanga_Compiler
1346 // get_function_name($name=NULL) {{{
1351 function get_function_name($name=NULL)
1353 if ($name === NULL) {
1354 $name = $this->name
;
1356 return "haanga_".sha1($name);
1360 // set_template_name($path) {{{
1361 function set_template_name($path)
1367 // Override {% include %} {{{
1368 protected function generate_op_include($details, &$out)
1370 $expr = $this->expr_exec(
1373 $this->expr_var('vars'),
1375 $this->expr_var('blocks')
1377 $this->generate_op_print(array('expr' => $expr), $out);
1381 // {% base "" %} {{{
1382 function expr_call_base_template()
1384 return $this->expr_exec(
1387 $this->expr_var('vars'),
1389 $this->expr_var('blocks')
1394 // get_base_template($base) {{{
1395 function get_base_template($base)
1397 $this->subtemplate
= $base;
1401 // Override get_Custom_tag {{{
1406 function get_custom_tag($name)
1408 $loaded = &$this->tags
;
1410 if (!isset($loaded[$name])) {
1411 $this->prepend_op
[] = $this->op_comment("Load tag {$name} definition");
1412 $this->prepend_op
[] = $this->op_expr($this->expr_exec("Haanga::doInclude", $this->Expr_str(Haanga_Extensions
::getInstance('Haanga_Tag')->getFilePath($name, FALSE))));
1413 $loaded[$name] = TRUE;
1416 $name = ucfirst($name);
1418 return "{$name}_Tag::main";
1422 // Override get_custom_filter {{{
1423 function get_custom_filter($name)
1425 $loaded = &$this->filters
;
1427 if (!isset($loaded[$name])) {
1428 $this->prepend_op
[] = $this->op_comment("Load filter {$name} definition");
1429 $this->prepend_op
[] = $this->op_expr($this->expr_exec("Haanga::doInclude", $this->Expr_str(Haanga_Extensions
::getInstance('Haanga_Filter')->getFilePath($name, FALSE))));
1430 $loaded[$name] = TRUE;
1433 $name = ucfirst($name);
1435 return "{$name}_Filter::main";
1446 * vim600: sw=4 ts=4 fdm=marker
1447 * vim<600: sw=4 ts=4