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 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 $strip_whitespaces=FALSE;
93 protected $force_whitespaces=0;
100 function __construct()
102 $this->generator
= new Haanga_CodeGenerator
;
103 if (self
::$block_var===NULL) {
104 self
::$block_var = '{{block.'.md5('super').'}}';
108 // setDebug($file) {{{
109 function setDebug($file)
111 $this->debug
= $file;
118 $avoid_cleaning = array('strip_whitespaces' => 1, 'block_var' => 1, 'autoescape'=>1);
119 foreach (array_keys(get_object_vars($this)) as $key) {
120 if (isset($avoid_cleaning[$key])) {
125 $this->generator
= new Haanga_CodeGenerator
;
126 $this->blocks
= array();
127 $this->cycle
= array();
131 // get_template_name() {{{
132 final function get_template_name()
138 // Set template name {{{
139 function set_template_name($path)
141 return ($this->name
= strstr(basename($path),'.', TRUE));
145 // get_function_name(string $name) {{{
146 function get_function_name($name)
148 return "{$name}_template";
152 // Compile ($code, $name=NULL) {{{
153 final function compile($code, $name=NULL)
157 $parsed = do_parsing($code);
159 $this->subtemplate
= FALSE;
161 if ($parsed[0]['operation'] == 'base') {
162 /* {% base ... %} found */
163 $base = $parsed[0][0];
164 $code .= $this->get_base_template($base);
169 if (isset($this->_file
)) {
170 $op_code[] = $this->op_comment("Generated from {$this->_base_dir}/{$this->_file}");
172 $op_code[] = $this->op_declare_function($this->get_function_name($name));
173 $op_code[] = $this->op_expr($this->expr_exec('extract', $this->expr_var('vars')));
176 $this->ob_start($op_code);
177 $this->generate_op_code($parsed, $op_code);
178 if ($this->subtemplate
) {
179 $expr = $this->expr_call_base_template();
180 $this->generate_op_print(array('expr' => $expr), $op_code);
185 $expr = $this->expr('==', $this->expr_var('return'), TRUE);
186 $op_code[] = $this->op_if($expr);
187 $op_code[] = $this->op_return($this->expr_var('buffer1'));
188 $op_code[] = $this->op_else();
189 $this->generate_op_print(array('variable' => 'buffer1'), $op_code);
190 $op_code[] = $this->op_end('if');
193 $op_code[] = $this->op_end('function');
196 if (count($this->prepend_op
)) {
197 $op_code = array_merge($this->prepend_op
, $op_code);
200 $code .= $this->generator
->getCode($op_code);
201 if (!empty($this->append
)) {
202 $code .= $this->append
;
204 if (!empty($this->debug
)) {
205 file_put_contents($this->debug
, print_r($op_code, TRUE));
211 // compile_file($file) {{{
215 * @param string $file File path
217 * @return Generated PHP code
219 final function compile_file($file)
221 if (!is_readable($file)) {
222 throw new CompilerException("$file is not a file");
224 $this->_base_dir
= dirname($file);
225 $this->_file
= basename($file);
226 $name = $this->set_template_name($file);
227 return $this->compile(file_get_contents($file), $name);
231 // is_expr methods {{{
232 function is_expr(Array $cmd, $type=NULL)
234 if (isset($cmd['op_expr'])) {
235 if (!$type ||
$type == $cmd['op_expr']) {
242 function is_exec(Array $cmd)
244 return $this->is_expr($cmd, 'exec');
248 // op_* helper methods {{{
250 * Return an stand alone expression
253 function op_expr($expr)
255 return array('op' => 'expr', $expr);
258 function op_comment($comment)
260 return array('op' => 'comment', 'comment' => $comment);
263 function op_foreach($array, $value, $key=NULL)
265 foreach (array('array', 'value', 'key') as $var) {
267 if (is_array($var) && isset($var['var'])) {
272 $def = array('op' => 'foreach', 'array' => $array, 'value' => $value);
279 function op_if($expr)
281 return array('op' => 'if', 'expr' => $expr);
286 return array('op' => 'else');
289 function op_return($expr)
291 return array('op' => 'return', $expr);
296 return array('op' => "end_{$op}");
299 function op_declare($name, $value)
301 if (is_array($name)) {
302 if (isset($name['var'])) {
303 $name = $name['var'];
307 if (is_array($value)) {
308 if (isset($value['op_expr'])) {
309 $value = array('expr' => $value);
313 return array('op' => 'declare', 'name' => $name, $value);
316 function op_append($name, $expr)
318 return array('op' => 'append_var', 'name' => $name, $expr);
322 function op_inc($name)
324 return array('op' => 'inc', 'name' => $name);
327 function op_declare_function($name)
329 return array('op' => 'function', 'name' => $name);
334 // expr_* helper methods {{{
335 function expr_cond($expr, $true, $false)
337 return array('expr_cond' => $expr, 'true' => $true, 'false' => $false);
340 function expr_const($name)
342 return array('constant' => $name);
346 * Generate code to call base template
349 function expr_call_base_template()
351 return $this->expr_exec(
352 $this->get_function_name($this->subtemplate
),
353 $this->expr_var('vars'),
355 $this->expr_var('blocks')
360 * return a function call for isset($var) === $isset
364 final function expr_isset($var, $isset=TRUE)
366 return $this->expr('==', $this->expr_exec('isset', $this->expr_var($var)), $isset);
369 final function expr_isset_ex($var, $isset=TRUE)
371 return $this->expr('==', $this->expr_exec('isset', $var), $isset);
374 final function expr_array()
377 foreach (func_get_args() as $arg) {
378 if (count($arg) == 2) {
379 if (!is_array($arg[0])) {
380 $arg[0] = $this->expr_str($arg[0]);
382 $arg = array('key' => $arg);
386 return array("array" => $def);
389 final function expr_array_first($values)
392 foreach ($values as $arg) {
393 if (count($arg) == 2) {
394 if (!is_array($arg[0])) {
395 $arg[0] = $this->expr_str($arg[0]);
397 $arg = array('key' => $arg);
401 return array("array" => $def);
406 * return an number definition of $num
410 final function expr_number($num=0)
412 return array('number' =>$num);
416 * return an string definition of $str
420 final function expr_str($str='')
422 return array('string' => $str);
426 * Generate expression that for
431 final function expr_TRUE()
433 return array('expr' => TRUE);
437 * Generate expression that for
442 final function expr_FALSE()
444 return array('expr' => FALSE);
448 * Return expr for variable reference
452 final function expr_var($var)
454 return array('var' => func_get_args());
458 * Generate expression for
459 * a function calling inside an expression
463 final function expr_exec($function)
465 $args = func_get_args();
467 $args = array_values($args);
478 * Generate a generic expression
482 final function expr($operation, $expr1, $expr2=NULL)
484 $expr = array('op_expr' => $operation, $expr1);
485 if ($expr2 !== NULL) {
493 // get_base_template($base) {{{
495 * Handle {% base "" %} definition. By default only
496 * static (string) are supported, but this can be overrided
499 * This method load the base class, compile it and return
500 * the generated code.
502 * @param array $base Base structure
504 * @return string Generated source code
506 function get_base_template($base)
508 if (!isset($base['string'])) {
509 throw new CompilerException("Dynamic inheritance is not supported for compilated templates");
511 $file = $base['string'];
512 list($this->subtemplate
, $new_code) = $this->compile_required_template($file);
513 return $new_code."\n\n";
517 // {% base "foo.html" %} {{{
518 protected function generate_op_base()
520 throw new exception("{% base %} can be only as first statement");
525 protected function generate_op_code($parsed, &$out)
527 if (!is_array($parsed)) {
528 throw new CompilerException("Invalid \$parsed array");
530 foreach ($parsed as $op) {
531 if (!isset($op['operation'])) {
532 throw new CompilerException("Malformed $parsed array");
534 if ($this->subtemplate
&& $this->in_block
== 0 && $op['operation'] != 'block') {
535 /* ignore most of tokens in subtemplates */
538 $method = "generate_op_".$op['operation'];
539 if (!is_callable(array($this, $method))) {
540 throw new CompilerException("Compiler: Missing method $method");
542 $this->$method($op, $out);
547 // Check the current expr {{{
548 protected function check_expr(&$expr)
550 if (is_array($expr) && isset($expr['op_expr'])) {
551 if ($expr['op_expr'] == 'in') {
552 if (isset($expr[1]['string'])) {
553 $expr = $this->expr("!==",
554 $this->expr_exec("strpos", $expr[1], $expr[0]),
558 $expr = $this->expr("!==", $this->expr_cond(
559 $this->expr("==", $this->expr_exec("is_array", $expr[1]), TRUE),
560 $this->expr_exec("array_search", $expr[0], $expr[1]),
561 $this->expr_exec("strpos", $expr[1], $expr[0])
566 $this->check_expr($expr[0]);
567 $this->check_expr($expr[1]);
569 if (is_array($expr)) {
570 if (isset($expr['var'])) {
571 $expr = $this->generate_variable_name($expr['var']);
572 } else if (isset($expr['var_filter'])) {
573 foreach ($expr['var_filter'] as $id => $f) {
575 $exec = $this->generate_variable_name($f);
577 $exec = $this->expr_exec($f, $exec);
581 } else if (isset($expr['args'])) {
582 /* check every arguments */
583 foreach ($expr['args'] as &$v) {
584 $this->check_expr($v);
587 } else if (isset($expr['expr_cond'])) {
588 /* Check expr conditions */
589 $this->check_expr($expr['expr_cond']);
590 $this->check_expr($expr['true']);
591 $this->check_expr($expr['false']);
598 // {% if <expr> %} HTML {% else %} TWO {% endif $} {{{
599 protected function generate_op_if($details, &$out)
601 $this->check_expr($details['expr']);
602 $out[] = $this->op_if($details['expr']);
603 $this->generate_op_code($details['body'], $out);
604 if (isset($details['else'])) {
605 $out[] = $this->op_else();
606 $this->generate_op_code($details['else'], $out);
608 $out[] = $this->op_end('if');
612 // Overload template {{{
613 protected function compile_required_template($file)
615 if (!is_file($file)) {
616 if (isset($this->_base_dir
)) {
617 $file = $this->_base_dir
.'/'.$file;
620 if (!is_file($file)) {
621 throw new CompilerException("can't find {$file} file template");
623 $class = get_class($this);
626 $code = $comp->compile_file($file);
627 return array($comp->get_template_name(), $code);
631 // include "file.html" | include <var1> {{{
632 protected function generate_op_include($details, &$out)
634 if (!$details[0]['string']) {
635 throw new CompilerException("Dynamic inheritance is not supported for compilated templates");
637 list($name,$code) = $this->compile_required_template($details[0]['string']);
638 $this->append
.= "\n\n{$code}";
639 $expr = $this->expr_exec(
640 $this->get_function_name($this->subtemplate
),
641 $this->expr_var('vars'),
642 $this->expr_var('blocks'),
645 $this->generate_op_print($expr, $op_code);
646 $this->generate_op_print($expr, $out);
650 // Handle HTML code {{{
651 protected function generate_op_html($details, &$out)
653 $this->generate_op_print($details, $out);
657 // get_var_filtered {{{
659 * This method handles all the filtered variable (piped_list(X)'s
660 * output in the parser.
663 * @param array $variable (Output of piped_list(B) (parser))
664 * @param array &$varname Variable name
669 function get_var_filtering($variable, &$varname)
671 $this->var_is_safe
= FALSE;
672 if (count($variable) > 1) {
673 $count = count($variable);
674 $target = $this->generate_variable_name($variable[0]);
676 if (!isset($target['var'])) {
677 /* block.super can't have any filter */
678 throw new CompilerException("This variable can't have any filter");
681 for ($i=1; $i < $count; $i++
) {
682 $func_name = $variable[$i];
683 if ($func_name == 'escape') {
684 /* to avoid double cleaning */
685 $this->var_is_safe
= TRUE;
687 $args = array(isset($exec) ?
$exec : $target);
688 $exec = $this->do_filtering($func_name, $args);
694 $details = $this->generate_variable_name($variable[0]);
695 $varname = $variable[0];
703 // generate_op_print_var {{{
705 * Generate code to print a variable with its filters, if there is any.
707 * All variable (except those flagged as |safe) are automatically
708 * escaped if autoescape is "on".
711 protected function generate_op_print_var($details, &$out)
714 $details = $this->get_var_filtering($details['variable'], $variable);
716 if (!isset($details['var']) && !isset($details['exec'])) {
717 /* generate_variable_name didn't replied a variable, weird case
718 currently just used for {{block.super}}.
720 $this->generate_op_print($details, $out);
725 if (!$this->var_is_safe
&& $this->autoescape
) {
726 $args = array($details);
727 $details = $this->do_filtering('escape', $args);
730 $this->generate_op_print($details, $out);
734 // is_last_op_print($out) {{{
736 * Return TRUE if the last stacked operation
737 * is a print (declare or append_var).
739 * @param array $out Stack of operations
743 protected function is_last_op_print($out)
745 $last = count($out)-1;
746 $sprint = array('print', 'declare', 'append_var');
747 return $last >= 0 && array_search($out[$last]['op'], $sprint) !== FALSE;
751 // first_of var1 var2 'name' {{{
752 protected function generate_op_first_of($details, &$out)
755 foreach ($details['vars'] as $var) {
756 if (isset($var['string'])) {
760 $texpr[] = $this->expr_cond(
761 $this->expr_isset($var['var']),
766 $texpr = array_reverse($texpr);
767 for ($i=1; $i < count($texpr); $i++
) {
768 $texpr[$i]['false'] = $texpr[$i-1];
770 $expr = $texpr[$i-1];
772 $this->generate_op_print($expr, $out);
776 // {# something #} {{{
777 protected function generate_op_comment($details, &$out)
779 if ($this->is_last_op_print($out)) {
780 /* If there is a print declared previously, we pop it
781 and add it after the cycle declaration
783 $old_print = array_pop($out);
785 $out[] = $this->op_comment($details['comment']);
786 if (isset($old_print)) {
792 // {% block 'name' %} ... {% endblock %} {{{
793 protected function generate_op_block($details, &$out)
796 $this->blocks
[] = $details['name'];
797 $block_name = $this->expr_var('blocks', $details['name']);
799 $this->ob_start($out);
800 $buffer_var = 'buffer'.$this->ob_start
;
802 $this->generate_op_code($details['body'], $body);
804 $out = array_merge($out, $body);
807 $buffer = $this->expr_var($buffer_var);
811 * isset previous block (parent block)?
813 * has reference to self::$block_var ?
815 * replace self::$block_var for current block value (buffer)
819 * print current block
822 $declare = $this->expr_cond(
823 $this->expr_isset_ex($block_name),
825 $this->expr("===", $this->expr_exec('strpos', $block_name,
826 $this->expr_str(self
::$block_var)
827 ), $this->expr_FALSE()
830 $this->expr_exec('str_replace',
831 $this->expr_str(self
::$block_var),
840 if (!$this->subtemplate
) {
841 $this->generate_op_print($declare, $out);
843 $out[] = $this->op_declare($block_name, $declare);
844 if ($this->in_block
> 1) {
845 $this->generate_op_print($block_name, $out);
848 array_pop($this->blocks
);
854 // regroup <var1> by <field> as <foo> {{{
855 protected function generate_op_regroup($details, &$out)
857 $out[] = $this->op_declare($details['as'], $this->expr_array_first(array()));
858 $array = $this->get_var_filtering($details['array'], $varname);
859 if (!isset($details['var']) && !isset($varname['var'])) {
860 /* generate_variable_name didn't replied a variable, weird case
861 currently just used for {{block.super}}.
863 throw new CompilerException("Invalid variable name {$details['array']}");
865 if (isset($array['exec'])) {
866 $out[] = $this->op_declare($varname, $array);
868 $var = $this->expr_var('item', $details['row']);
870 $out[] = $this->op_comment("Temporary sorting");
871 $out[] = $this->op_foreach($varname, 'item');
874 $out[] = $this->op_declare(array('temp_group', $var, NULL), $this->expr_var('item'));
875 $out[] = $this->op_end('foreach');
877 $out[] = $this->op_comment("Proper format");
879 $out[] = $this->op_foreach('temp_group', 'item', 'group');
881 $array = $this->expr_array(
882 array("grouper", $this->expr_var('group')),
883 array("list", $this->expr_var('item'))
886 $out[] = $this->op_declare(array($details['as'], NULL), $array );
888 $out[] = $this->op_end('foreach');
889 $out[] = $this->op_comment("Sorting done");
893 // Get variable name {{{
894 protected function generate_variable_name($variable)
896 if (is_array($variable)) {
897 switch ($variable[0]) {
900 throw new CompilerException("Invalid forloop reference outside of a loop");
902 switch ($variable[1]) {
904 $this->forloop
[$this->forid
]['counter'] = TRUE;
905 $variable = 'forcounter1_'.$this->forid
;
908 $this->forloop
[$this->forid
]['counter0'] = TRUE;
909 $variable = 'forcounter0_'.$this->forid
;
912 $this->forloop
[$this->forid
]['counter'] = TRUE;
913 $this->forloop
[$this->forid
]['last'] = TRUE;
914 $variable = 'islast_'.$this->forid
;
917 $this->forloop
[$this->forid
]['first'] = TRUE;
918 $variable = 'isfirst_'.$this->forid
;
921 $this->forloop
[$this->forid
]['revcounter'] = TRUE;
922 $variable = 'revcount_'.$this->forid
;
925 $this->forloop
[$this->forid
]['revcounter0'] = TRUE;
926 $variable = 'revcount0_'.$this->forid
;
931 $variable = $this->generate_variable_name(array_values($variable));
932 $variable = $variable['var'];
936 throw new CompilerException("Unexpected forloop.{$variable[1]}");
938 /* no need to escape it */
939 $this->var_is_safe
= TRUE;
942 if ($this->in_block
== 0) {
943 throw new CompilerException("Can't use block.super outside a block");
945 if (!$this->subtemplate
) {
946 throw new CompilerException("Only subtemplates can call block.super");
948 /* no need to escape it */
949 $this->var_is_safe
= TRUE;
950 return $this->expr_str(self
::$block_var);
954 } else if (isset($this->var_alias
[$variable])) {
955 $variable = $this->var_alias
[$variable];
958 return $this->expr_var($variable);
963 public function generate_op_print($details, &$out)
965 $last = count($out)-1;
966 if (isset($details['variable'])) {
967 $content = $this->generate_variable_name($details['variable']);
968 } else if (isset($details['html'])) {
969 $html = $details['html'];
970 if ($this->strip_whitespaces
&& $this->force_whitespaces
== 0) {
971 $html = str_replace("\n", " ", $html);
972 $html = preg_replace("/\s\s+/", " ", $html);
974 $content = $this->expr_str($html);
975 } else if (isset($details['function'])) {
976 $content = $this->expr_exec($details['function'][0], $details['function'][1]);
981 $var_name = 'buffer'.$this->ob_start
;
983 if ($this->ob_start
== 0) {
984 $operation = 'print';
986 $operation = 'append_var';
989 if ($last >= 0 && $out[$last]['op'] == $operation && ($operation != 'append_var' ||
$out[$last]['name'] === $var_name)) {
990 /* try to append this to the previous print if it exists */
991 $out[$last][] = $content;
993 if ($this->ob_start
== 0) {
994 $out[] = array('op' => 'print', $content);
996 if (isset($out[$last]) && $out[$last]['op'] == 'declare' && $out[$last]['name'] == $var_name) {
997 /* override an empty declaration of a empty buffer
998 if the next operation is an 'append'
1000 $out[$last][] = $content;
1002 $out[] = $this->op_append('buffer'.$this->ob_start
, $content);
1009 // for [<key>,]<val> in <array> {{{
1010 protected function generate_op_loop($details, &$out)
1012 if (isset($details['empty'])) {
1013 $expr = $this->expr('==',
1014 $this->expr_exec('count', $this->expr_var($details['array'])),
1018 $out[] = $this->op_if($expr);
1019 $this->generate_op_code($details['empty'], $out);
1020 $out[] = $this->op_else();
1024 $oldid = $this->forid
;
1025 $this->forid
= $oldid+
1;
1027 $this->forloop
[$this->forid
] = array();
1030 $for_loop_body = array();
1031 $this->generate_op_code($details['body'], $for_loop_body);
1033 $oid = $this->forid
;
1034 $size = $this->expr_var('psize_'.$oid);
1037 if (isset($this->forloop
[$oid]['counter'])) {
1038 $var = 'forcounter1_'.$oid;
1039 $out[] = $this->op_declare($var, $this->expr_number(1));
1040 $for_loop_body[] = $this->op_inc($var);
1045 if (isset($this->forloop
[$oid]['counter0'])) {
1046 $var = 'forcounter0_'.$oid;
1047 $out[] = $this->op_declare($var, $this->expr_number(0) );
1048 $for_loop_body[] = $this->op_inc($var);
1053 if (isset($this->forloop
[$oid]['last'])) {
1055 $cnt = $this->op_declare('psize_'.$oid, $this->expr_exec('count', $this->expr_var($details['array'])));
1058 $var = 'islast_'.$oid;
1059 $expr = $this->op_declare($var, $this->expr("==", $this->expr_var('forcounter1_'.$oid), $size));
1063 $for_loop_body[] = $expr;
1068 if (isset($this->forloop
[$oid]['first'])) {
1069 $out[] = $this->op_declare('isfirst_'.$oid, $this->expr_TRUE());
1071 $for_loop_body[] = $this->op_declare('isfirst_'.$oid, $this->expr_FALSE());
1076 if (isset($this->forloop
[$oid]['revcounter'])) {
1078 $cnt = $this->op_declare('psize_'.$oid, $this->expr_exec('count', $this->expr_var($details['array'])));
1081 $var = $this->expr_var('revcount_'.$oid);
1082 $out[] = $this->op_declare($var, $size );
1084 $for_loop_body[] = $this->op_declare($var, $this->expr("-", $var, $this->expr_number(1)));
1089 if (isset($this->forloop
[$oid]['revcounter0'])) {
1091 $cnt = $this->op_declare('psize_'.$oid, $this->expr_exec('count', $this->expr_var($details['array'])));
1094 $var = $this->expr_var('revcount0_'.$oid);
1095 $out[] = $this->op_declare($var, $this->expr("-", $size, $this->expr_number(1)));
1097 $for_loop_body[] = $this->op_declare($var, $this->expr("-", $var, $this->expr_number(1)));
1101 /* Restore old ForID */
1102 $this->forid
= $oldid;
1104 /* Merge loop body */
1105 $loop = $this->op_foreach($details['array'], $details['variable'], $details['index']);
1108 $out = array_merge($out, $for_loop_body);
1109 $out[] = $this->op_end('foreach');
1110 if (isset($details['empty'])) {
1111 $out[] = $this->op_end('if');
1116 // ifchanged [<var1> <var2] {{{
1117 protected function generate_op_ifchanged($details, &$out)
1119 static $ifchanged = 0;
1122 $var1 = 'ifchanged'.$ifchanged;
1123 if (!isset($details['check'])) {
1125 $this->ob_start($out);
1126 $var2 = 'buffer'.$this->ob_start
;
1128 $expr = $this->expr('OR',
1129 $this->expr_isset($var1, FALSE),
1131 $this->expr_var($var1),
1132 $this->expr_var($var2)
1136 $this->generate_op_code($details['body'], $out);
1138 $out[] = $this->op_if($expr);
1139 $this->generate_op_print(array('variable' => $var2), $out);
1140 $out[] = $this->op_declare($var1, $this->expr_var($var2));
1143 foreach ($details['check'] as $id=>$type) {
1144 if (!isset($type['var'])) {
1145 throw new CompilerException("Unexpected string {$type['string']}, expected a varabile");
1147 $this_expr = $this->expr('OR',
1148 $this->expr_isset("{$var1}[{$id}]", FALSE),
1150 $this->expr_var("{$var1}[{$id}]"),
1151 $this->expr_var($type['var'])
1155 $this_expr = $this->expr('AND',
1156 $this->expr('expr', $this_expr),
1164 $out[] = $this->op_if($expr);
1165 $this->generate_op_code($details['body'], $out);
1166 $out[] = $this->op_declare($var1, $this->expr_array_first($details['check']));
1169 if (isset($details['else'])) {
1170 $out[] = $this->op_else();
1171 $this->generate_op_code($details['else'], $out);
1173 $out[] = $this->op_end('if');
1177 // autoescape ON|OFF {{{
1178 function generate_op_autoescape($details, &$out)
1180 $old_autoescape = $this->autoescape
;
1181 $this->autoescape
= strtolower($details['value']) == 'on';
1182 $this->generate_op_code($details['body'], $out);
1183 $this->autoescape
= $old_autoescape;
1187 // ob_Start(array &$out) {{{
1189 * Start a new buffering
1192 function ob_start(&$out)
1195 $out[] = $this->op_declare('buffer'.$this->ob_start
, array('string' => ''));
1200 function get_custom_tag($name)
1202 $function = $this->get_function_name().'_tag_'.$name;
1203 $this->append
.= "\n\n".Extensions
::getInstance('Haanga_Tag')->getFunctionBody($name, $function);
1208 * Generate needed code for custom tags (tags that aren't
1209 * handled by the compiler).
1212 function generate_op_custom_tag($details, &$out)
1216 $tags = Extensions
::getInstance('Haanga_Tag');
1219 $tag_name = $details['name'];
1220 $tagFunction = $tags->getFunctionAlias($tag_name);
1222 if (!$tagFunction && !$tags->hasGenerator($tag_name)) {
1223 $function = $this->get_custom_tag($tag_name, isset($details['as']));
1225 $function = $tagFunction;
1228 if (isset($details['body'])) {
1230 if the custom tag has 'body'
1231 then it behave the same way as a filter
1233 $this->ob_start($out);
1234 $this->generate_op_code($details['body'], $out);
1235 $target = $this->expr_var('buffer'.$this->ob_start
);
1236 if ($tags->hasGenerator($tag_name)) {
1237 $exec = $tags->generator($tag_name, $this, array($target));
1239 $exec = $this->expr_exec($function, $target);
1242 $this->generate_op_print($exec, $out);
1246 $var = isset($details['as']) ?
$details['as'] : NULL;
1247 $args = array_merge(array($function), $details['list']);
1249 if ($tags->hasGenerator($tag_name)) {
1250 $exec = $tags->generator($tag_name, $this, $details['list'], $var);
1251 if ($exec InstanceOf ArrayIterator
) {
1253 The generator returned more than one statement,
1254 so we assume the output is already handled
1255 by one of those stmts.
1257 $out = array_merge($out, $exec->getArrayCopy());
1261 $exec = call_user_func_array(array($this, 'expr_exec'), $args);
1264 if (isset($details['for'])) {
1265 $new_args = array($this->expr_var('var'));
1266 $print['function'][1] = $new_args;
1267 $arr['args'] = $new_args;
1270 $out[] = $this->op_declare($var, $this->expr_str());
1273 $out[] = $this->op_foreach($details['for'], 'var');
1275 $out[] = $this->append_var($var, $exec);
1277 $this->generate_op_print($exec, $out);
1279 $out[] = $this->op_end('foreach');
1282 $out[] = $this->op_declare($var, $exec);
1284 $this->generate_op_print($exec, $out);
1290 // with <variable> as <var> {{{
1295 function generate_op_alias($details, &$out)
1297 $this->var_alias
[ $details['as'] ] = $details['var'];
1298 $this->generate_op_code($details['body'], $out);
1299 unset($this->var_alias
[ $details['as'] ] );
1303 // Custom Filters {{{
1304 function get_custom_filter($name)
1306 $function = $this->get_function_name().'_filter_'.$name;
1307 $this->append
.= "\n\n".Extensions
::getInstance('Haanga_Filter')->getFunctionBody($name, $function);
1312 function do_filtering($name, $args)
1316 $filter = Extensions
::getInstance('Haanga_Filter');
1319 if (is_array($name)) {
1321 prepare array for ($func_name, $arg1, $arg2 ... )
1322 where $arg1 = last expression and $arg2.. $argX is
1323 defined in the template
1325 $args = array_merge($args, $name['args']);
1329 if (!$filter->isValid($name)) {
1330 throw new CompilerException("{$name} is an invalid filter");
1332 if ($filter->hasGenerator($name)) {
1333 return $filter->generator($name, $this, $args);
1335 $fnc = $filter->getFunctionAlias($name);
1337 $fnc = $this->get_custom_filter($name);
1339 $args = array_merge(array($fnc), $args);
1340 $exec = call_user_func_array(array($this, 'expr_exec'), $args);
1345 function generate_op_filter($details, &$out)
1347 $this->ob_start($out);
1348 $this->generate_op_code($details['body'], $out);
1349 $target = $this->expr_var('buffer'.$this->ob_start
);
1350 foreach ($details['functions'] as $f) {
1351 $param = (isset($exec) ?
$exec : $target);
1352 $exec = $this->do_filtering($f, array($param));
1355 $this->generate_op_print($exec, $out);
1359 final static function main_cli()
1361 $argv = $GLOBALS['argv'];
1362 $haanga = new Haanga_Main
;
1363 $code = $haanga->compile_file($argv[1]);
1364 echo "<?php\n\n$code\n";
1374 final class Haanga_Main_Runtime
extends Haanga_Main
1377 // get_function_name($name=NULL) {{{
1382 function get_function_name($name=NULL)
1384 if ($name === NULL) {
1385 $name = $this->name
;
1387 return "haanga_".sha1($name);
1391 // set_template_name($path) {{{
1392 function set_template_name($path)
1398 // Override {% include %} {{{
1399 protected function generate_op_include($details, &$out)
1401 $expr = $this->expr_exec(
1404 $this->expr_var('vars'),
1406 $this->expr_var('blocks')
1408 $this->generate_op_print(array('expr' => $expr), $out);
1412 // {% base "" %} {{{
1413 function expr_call_base_template()
1415 return $this->expr_exec(
1418 $this->expr_var('vars'),
1420 $this->expr_var('blocks')
1425 // get_base_template($base) {{{
1426 function get_base_template($base)
1428 $this->subtemplate
= $base;
1432 // Override get_Custom_tag {{{
1437 function get_custom_tag($name)
1439 $loaded = &$this->tags
;
1441 if (!isset($loaded[$name])) {
1442 $this->prepend_op
[] = $this->op_comment("Load tag {$name} definition");
1443 $this->prepend_op
[] = $this->op_expr($this->expr_exec("Haanga::doInclude", $this->Expr_str(Extensions
::getInstance('Haanga_Tag')->getFilePath($name, FALSE))));
1444 $loaded[$name] = TRUE;
1447 $name = ucfirst($name);
1449 return "{$name}_Tag::main";
1453 // Override get_custom_filter {{{
1454 function get_custom_filter($name)
1456 $loaded = &$this->filters
;
1458 if (!isset($loaded[$name])) {
1459 $this->prepend_op
[] = $this->op_comment("Load filter {$name} definition");
1460 $this->prepend_op
[] = $this->op_expr($this->expr_exec("Haanga::doInclude", $this->Expr_str(Extensions
::getInstance('Haanga_Filter')->getFilePath($name, FALSE))));
1461 $loaded[$name] = TRUE;
1464 $name = ucfirst($name);
1466 return "{$name}_Filter::main";
1477 * vim600: sw=4 ts=4 fdm=marker
1478 * vim<600: sw=4 ts=4