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
."/helper.php";
43 require HAANGA_DIR
."/generator.php";
44 require HAANGA_DIR
."/extensions.php";
45 require HAANGA_DIR
."/tags.php";
46 require HAANGA_DIR
."/filters.php";
49 // Exception Class {{{
54 class Haanga_CompilerException
extends Exception
64 protected static $block_var=NULL;
66 protected $forloop = array();
68 protected $sub_template = FALSE;
70 protected $check_function = FALSE;
71 protected $blocks=array();
73 * number of blocks :-)
75 protected $in_block=0;
79 protected $ob_start=0;
81 protected $prepend_op;
83 * Table which contains all variables
84 * aliases defined in the template
88 * Flag the current variable as safe. This means
89 * that escape won't be called if autoescape is
90 * activated (which is activated by default)
92 public $var_is_safe=FALSE;
93 protected $autoescape=TRUE;
94 protected $force_whitespaces=0;
101 function __construct()
103 $this->generator
= new Haanga_CodeGenerator
;
104 if (self
::$block_var===NULL) {
105 self
::$block_var = '{{block.'.md5('super').'}}';
109 // setDebug($file) {{{
110 function setDebug($file)
112 $this->debug
= $file;
119 $avoid_cleaning = array('strip_whitespaces' => 1, 'block_var' => 1, 'autoescape'=>1);
120 foreach (array_keys(get_object_vars($this)) as $key) {
121 if (isset($avoid_cleaning[$key])) {
126 $this->generator
= new Haanga_CodeGenerator
;
127 $this->blocks
= array();
128 $this->cycle
= array();
132 // get_template_name() {{{
133 final function get_template_name()
139 // Set template name {{{
140 function set_template_name($path)
142 $file = basename($path);
143 $pos = strpos($file,'.');
144 return ($this->name
= substr($file, 0, $pos));
148 // get_function_name(string $name) {{{
149 function get_function_name($name)
151 return "{$name}_template";
155 // Compile ($code, $name=NULL) {{{
156 final function compile($code, $name=NULL)
160 $parsed = do_parsing($code);
162 $this->subtemplate
= FALSE;
164 if (isset($parsed[0]) && $parsed[0]['operation'] == 'base') {
165 /* {% base ... %} found */
166 $base = $parsed[0][0];
167 $code .= $this->get_base_template($base);
172 $func_name = $this->get_function_name($name);
173 if ($this->check_function
) {
174 $op_code[] = $this->op_if($this->expr("===", $this->expr_exec('function_exists', $this->expr_str($func_name)), $this->expr_FALSE()));
176 if (isset($this->_file
)) {
177 $op_code[] = $this->op_comment("Generated from {$this->_base_dir}/{$this->_file}");
179 $op_code[] = $this->op_declare_function($func_name);
180 $op_code[] = $this->op_expr($this->expr_exec('extract', $this->expr_var('vars')));
183 $this->ob_start($op_code);
184 $this->generate_op_code($parsed, $op_code);
185 if ($this->subtemplate
) {
186 $expr = $this->expr_call_base_template();
187 $this->generate_op_print($expr, $op_code);
192 $expr = $this->expr('==', $this->expr_var('return'), TRUE);
193 $op_code[] = $this->op_if($expr);
194 $op_code[] = $this->op_return($this->expr_var('buffer1'));
195 $op_code[] = $this->op_else();
196 $this->generate_op_print(array('variable' => 'buffer1'), $op_code);
197 $op_code[] = $this->op_end('if');
200 $op_code[] = $this->op_end('function');
201 if ($this->check_function
) {
202 $op_code[] = $this->op_end('if');
206 if (count($this->prepend_op
)) {
207 $op_code = array_merge($this->prepend_op
, $op_code);
210 $code .= $this->generator
->getCode($op_code);
211 if (!empty($this->append
)) {
212 $code .= $this->append
;
215 if (!empty($this->debug
)) {
216 $op_code['php'] = $code;
217 file_put_contents($this->debug
, print_r($op_code, TRUE));
223 // compile_file($file) {{{
227 * @param string $file File path
228 * @param bool $safe Whether or not add check if the function is already defined
230 * @return Generated PHP code
232 final function compile_file($file, $safe=FALSE)
234 if (!is_readable($file)) {
235 throw new Haanga_CompilerException("$file is not a file");
237 $this->_base_dir
= dirname($file);
238 $this->_file
= basename($file);
239 $this->check_function
= $safe;
240 $name = $this->set_template_name($file);
241 return $this->compile(file_get_contents($file), $name);
245 // is_expr methods {{{
246 function is_expr(Array $cmd, $type=NULL)
248 if (isset($cmd['op_expr'])) {
249 if (!$type ||
$type == $cmd['op_expr']) {
256 function is_exec(Array $cmd)
258 return $this->is_expr($cmd, 'exec');
261 function is_var(Array $cmd)
263 return isset($cmd['var']);
266 function is_var_filter($cmd)
268 return isset($cmd['var_filter']);
271 function is_string($cmd)
273 return isset($cmd['string']);
278 // op_* helper methods {{{
280 * Return an stand alone expression
283 function op_expr($expr)
285 return array('op' => 'expr', $expr);
288 function op_comment($comment)
290 return array('op' => 'comment', 'comment' => $comment);
293 function op_foreach($array, $value, $key=NULL)
295 foreach (array('array', 'value', 'key') as $var) {
297 if (is_array($var) && $this->is_var($var)) {
302 $def = array('op' => 'foreach', 'array' => $array, 'value' => $value);
309 function op_if($expr)
311 return array('op' => 'if', 'expr' => $expr);
316 return array('op' => 'else');
319 function op_return($expr)
321 return array('op' => 'return', $expr);
326 return array('op' => "end_{$op}");
329 function op_declare($name, $value)
331 if (is_array($name)) {
332 if ($this->is_var($name)) {
333 $name = $name['var'];
337 if (is_array($value)) {
338 if (isset($value['op_expr'])) {
339 $value = array('expr' => $value);
343 return array('op' => 'declare', 'name' => $name, $value);
346 function op_append($name, $expr)
348 return array('op' => 'append_var', 'name' => $name, $expr);
352 function op_inc($name)
354 return array('op' => 'inc', 'name' => $name);
357 function op_declare_function($name)
359 return array('op' => 'function', 'name' => $name);
364 // expr_* helper methods {{{
365 function expr_cond($expr, $true, $false)
367 return array('expr_cond' => $expr, 'true' => $true, 'false' => $false);
370 function expr_const($name)
372 return array('constant' => $name);
376 * Generate code to call base template
379 function expr_call_base_template()
381 return $this->expr_exec(
382 $this->get_function_name($this->subtemplate
),
383 $this->expr_var('vars'),
385 $this->expr_var('blocks')
390 * return a function call for isset($var) === $isset
394 final function expr_isset($var, $isset=TRUE)
396 return $this->expr('==', $this->expr_exec('isset', $this->expr_var($var)), $isset);
399 final function expr_isset_ex($var, $isset=TRUE)
401 return $this->expr('==', $this->expr_exec('isset', $var), $isset);
404 final function expr_array()
406 return $this->expr_array_ex(func_get_args());
409 final function expr_array_ex($values)
412 foreach ($values as $arg) {
413 if (count($arg) == 2) {
414 if (!is_array($arg[0])) {
415 $arg[0] = $this->expr_str($arg[0]);
417 $arg = array('key' => $arg);
421 return array("array" => $def);
426 * return an number definition of $num
430 final function expr_number($num=0)
432 return array('number' =>$num);
436 * return an string definition of $str
440 final function expr_str($str='')
442 return array('string' => $str);
446 * Generate expression that for
451 final function expr_TRUE()
453 return array('expr' => TRUE);
457 * Generate expression that for
462 final function expr_FALSE()
464 return array('expr' => FALSE);
468 * Return expr for variable reference
472 final function expr_var($var)
474 return $this->expr_var_ex(func_get_args());
478 * Return expr for variable reference
482 final function expr_var_ex(Array $var)
484 return array('var' => $var);
488 * Generate expression for
489 * a function calling inside an expression
493 final function expr_exec($function)
495 $args = func_get_args();
497 $args = array_values($args);
508 * Generate a generic expression
512 final function expr($operation, $expr1, $expr2=NULL)
514 $expr = array('op_expr' => $operation, $expr1);
515 if ($expr2 !== NULL) {
523 // get_base_template($base) {{{
525 * Handle {% base "" %} definition. By default only
526 * static (string) are supported, but this can be overrided
529 * This method load the base class, compile it and return
530 * the generated code.
532 * @param array $base Base structure
534 * @return string Generated source code
536 function get_base_template($base)
538 if (!$this->is_string($base)) {
539 throw new Haanga_CompilerException("Dynamic inheritance is not supported for compilated templates");
541 $file = $base['string'];
542 list($this->subtemplate
, $new_code) = $this->compile_required_template($file);
543 return $new_code."\n\n";
547 // {% base "foo.html" %} {{{
548 protected function generate_op_base()
550 throw new exception("{% base %} can be only as first statement");
555 protected function generate_op_code($parsed, &$out)
557 if (!is_array($parsed)) {
558 throw new Haanga_CompilerException("Invalid \$parsed array");
560 foreach ($parsed as $op) {
561 if (!is_array($op)) {
564 if (!isset($op['operation'])) {
565 throw new Haanga_CompilerException("Malformed array:".print_r($op, TRUE));
567 if ($this->subtemplate
&& $this->in_block
== 0 && $op['operation'] != 'block') {
568 /* ignore most of tokens in subtemplates */
571 $method = "generate_op_".$op['operation'];
572 if (!is_callable(array($this, $method))) {
573 throw new Haanga_CompilerException("Compiler: Missing method $method");
575 $this->$method($op, $out);
580 // Check the current expr {{{
581 protected function check_expr(&$expr)
583 if (is_array($expr) && isset($expr['op_expr'])) {
584 if ($expr['op_expr'] == 'in') {
585 if ($this->is_string($expr[1])) {
586 $expr = $this->expr("!==",
587 $this->expr_exec("strpos", $expr[1], $expr[0]),
591 $expr = $this->expr("!==", $this->expr_cond(
592 $this->expr("==", $this->expr_exec("is_array", $expr[1]), TRUE),
593 $this->expr_exec("array_search", $expr[0], $expr[1]),
594 $this->expr_exec("strpos", $expr[1], $expr[0])
599 $this->check_expr($expr[0]);
600 $this->check_expr($expr[1]);
602 if (is_array($expr)) {
603 if ($this->is_var_filter($expr)) {
604 $expr = $this->get_filtered_var($expr['var_filter'], $var);
605 } else if (isset($expr['args'])) {
606 /* check every arguments */
607 foreach ($expr['args'] as &$v) {
608 $this->check_expr($v);
611 } else if (isset($expr['expr_cond'])) {
612 /* Check expr conditions */
613 $this->check_expr($expr['expr_cond']);
614 $this->check_expr($expr['true']);
615 $this->check_expr($expr['false']);
622 // ifequal|ifnotequal <var_filtered|string|number> <var_fitlered|string|number> ... else ... {{{
623 protected function generate_op_ifequal($details, &$out)
625 $if['expr'] = $this->expr($details['cmp'], $details[1], $details[2]);
626 $if['body'] = $details['body'];
627 if (isset($details['else'])) {
628 $if['else'] = $details['else'];
630 $this->generate_op_if($if, $out);
634 // {% if <expr> %} HTML {% else %} TWO {% endif $} {{{
635 protected function generate_op_if($details, &$out)
637 $this->check_expr($details['expr']);
638 $out[] = $this->op_if($details['expr']);
639 $this->generate_op_code($details['body'], $out);
640 if (isset($details['else'])) {
641 $out[] = $this->op_else();
642 $this->generate_op_code($details['else'], $out);
644 $out[] = $this->op_end('if');
648 // Overload template {{{
649 protected function compile_required_template($file)
651 if (!is_file($file)) {
652 if (isset($this->_base_dir
)) {
653 $file = $this->_base_dir
.'/'.$file;
656 if (!is_file($file)) {
657 throw new Haanga_CompilerException("can't find {$file} file template");
659 $class = get_class($this);
662 $code = $comp->compile_file($file, $this->check_function
);
663 return array($comp->get_template_name(), $code);
667 // include "file.html" | include <var1> {{{
668 protected function generate_op_include($details, &$out)
670 if (!$details[0]['string']) {
671 throw new Haanga_CompilerException("Dynamic inheritance is not supported for compilated templates");
673 list($name,$code) = $this->compile_required_template($details[0]['string']);
674 $this->append
.= "\n\n{$code}";
675 $expr = $this->expr_exec(
676 $this->get_function_name($name),
677 $this->expr_var('vars'),
679 $this->expr_var('blocks')
681 $this->generate_op_print($expr, $out);
685 // Handle HTML code {{{
686 protected function generate_op_html($details, &$out)
688 $this->generate_op_print($details, $out);
692 // get_var_filtered {{{
694 * This method handles all the filtered variable (piped_list(X)'s
695 * output in the parser.
698 * @param array $variable (Output of piped_list(B) (parser))
699 * @param array &$varname Variable name
700 * @param bool $accept_string TRUE is string output are OK (ie: block.parent)
705 function get_filtered_var($variable, &$varname, $accept_string=FALSE)
707 $this->var_is_safe
= FALSE;
708 if (count($variable) > 1) {
709 $count = count($variable);
710 $target = $this->generate_variable_name($variable[0]);
712 if (!$this->is_var($target)) {
713 /* block.super can't have any filter */
714 throw new Haanga_CompilerException("This variable can't have any filter");
717 for ($i=1; $i < $count; $i++
) {
718 $func_name = $variable[$i];
719 if ($func_name == 'escape') {
720 /* to avoid double cleaning */
721 $this->var_is_safe
= TRUE;
723 $args = array(isset($exec) ?
$exec : $target);
724 $exec = $this->do_filtering($func_name, $args);
730 $details = $this->generate_variable_name($variable[0]);
731 $varname = $variable[0];
733 if (!$this->is_var($details) && !$accept_string) {
734 /* generate_variable_name didn't replied a variable, weird case
735 currently just used for {{block.super}}.
737 throw new Haanga_CompilerException("Invalid variable name {$variable[0]}");
745 // generate_op_print_var {{{
747 * Generate code to print a variable with its filters, if there is any.
749 * All variable (except those flagged as |safe) are automatically
750 * escaped if autoescape is "on".
753 protected function generate_op_print_var($details, &$out)
756 $details = $this->get_filtered_var($details['variable'], $variable, TRUE);
758 if (!$this->is_var($details) && !isset($details['exec'])) {
759 /* generate_variable_name didn't replied a variable, weird case
760 currently just used for {{block.super}}.
762 $this->generate_op_print($details, $out);
767 if (!$this->is_safe($details) && $this->autoescape
) {
768 $args = array($details);
769 $details = $this->do_filtering('escape', $args);
772 $this->generate_op_print($details, $out);
776 // is_last_op_print($out) {{{
778 * Return TRUE if the last stacked operation
779 * is a print (declare or append_var).
781 * @param array $out Stack of operations
785 protected function is_last_op_print($out)
787 $last = count($out)-1;
788 $sprint = array('print', 'declare', 'append_var');
789 return $last >= 0 && array_search($out[$last]['op'], $sprint) !== FALSE;
793 // {# something #} {{{
794 protected function generate_op_comment($details, &$out)
796 if ($this->is_last_op_print($out)) {
797 /* If there is a print declared previously, we pop it
798 and add it after the cycle declaration
800 $old_print = array_pop($out);
802 $out[] = $this->op_comment($details['comment']);
803 if (isset($old_print)) {
809 // {% block 'name' %} ... {% endblock %} {{{
810 protected function generate_op_block($details, &$out)
812 if (is_array($details['name'])) {
814 foreach ($details['name'] as $part) {
815 if (is_string($part)) {
817 } else if (is_array($part)) {
818 if ($this->is_string($part)) {
819 $name .= "{$part['string']}";
821 throw new Haanga_CompilerException("Invalid blockname");
826 $details['name'] = substr($name, 0, -1);
829 $this->blocks
[] = $details['name'];
830 $block_name = $this->expr_var('blocks', $details['name']);
832 $this->ob_start($out);
833 $buffer_var = 'buffer'.$this->ob_start
;
836 $this->generate_op_code($details['body'], $body);
838 $out = array_merge($out, $body);
841 $buffer = $this->expr_var($buffer_var);
845 * isset previous block (parent block)?
847 * has reference to self::$block_var ?
849 * replace self::$block_var for current block value (buffer)
853 * print current block
856 $declare = $this->expr_cond(
857 $this->expr_isset_ex($block_name),
859 $this->expr("===", $this->expr_exec('strpos', $block_name,
860 $this->expr_str(self
::$block_var)
861 ), $this->expr_FALSE()
864 $this->expr_exec('str_replace',
865 $this->expr_str(self
::$block_var),
874 if (!$this->subtemplate
) {
875 $this->generate_op_print($declare, $out);
877 $out[] = $this->op_declare($block_name, $declare);
878 if ($this->in_block
> 1) {
879 $this->generate_op_print($block_name, $out);
882 array_pop($this->blocks
);
888 // regroup <var1> by <field> as <foo> {{{
889 protected function generate_op_regroup($details, &$out)
891 $out[] = $this->op_comment("Temporary sorting");
892 $array = $this->get_filtered_var($details['array'], $varname);
894 if (isset($array['exec'])) {
895 $varname = $this->expr_var($details['as']);
896 $out[] = $this->op_declare($varname, $array);
898 $var = $this->expr_var('item', $details['row']);
900 $out[] = $this->op_declare('temp_group', $this->expr_array());
901 $out[] = $this->op_foreach($varname, 'item');
904 $out[] = $this->op_declare(array('temp_group', $var, NULL), $this->expr_var('item'));
905 $out[] = $this->op_end('foreach');
907 $out[] = $this->op_comment("Proper format");
908 $out[] = $this->op_declare($details['as'], $this->expr_array_ex(array()));
909 $out[] = $this->op_foreach('temp_group', 'item', 'group');
911 $array = $this->expr_array(
912 array("grouper", $this->expr_var('group')),
913 array("list", $this->expr_var('item'))
916 $out[] = $this->op_declare(array($details['as'], NULL), $array );
918 $out[] = $this->op_end('foreach');
919 $out[] = $this->op_comment("Sorting done");
923 // Get variable name {{{
924 protected function generate_variable_name($variable)
926 if (is_array($variable)) {
927 switch ($variable[0]) {
930 throw new Haanga_CompilerException("Invalid forloop reference outside of a loop");
932 switch ($variable[1]) {
934 $this->forloop
[$this->forid
]['counter'] = TRUE;
935 $variable = 'forcounter1_'.$this->forid
;
938 $this->forloop
[$this->forid
]['counter0'] = TRUE;
939 $variable = 'forcounter0_'.$this->forid
;
942 $this->forloop
[$this->forid
]['counter'] = TRUE;
943 $this->forloop
[$this->forid
]['last'] = TRUE;
944 $variable = 'islast_'.$this->forid
;
947 $this->forloop
[$this->forid
]['first'] = TRUE;
948 $variable = 'isfirst_'.$this->forid
;
951 $this->forloop
[$this->forid
]['revcounter'] = TRUE;
952 $variable = 'revcount_'.$this->forid
;
955 $this->forloop
[$this->forid
]['revcounter0'] = TRUE;
956 $variable = 'revcount0_'.$this->forid
;
961 $variable = $this->generate_variable_name(array_values($variable));
962 $variable = $variable['var'];
966 throw new Haanga_CompilerException("Unexpected forloop.{$variable[1]}");
968 /* no need to escape it */
969 $this->var_is_safe
= TRUE;
972 if ($this->in_block
== 0) {
973 throw new Haanga_CompilerException("Can't use block.super outside a block");
975 if (!$this->subtemplate
) {
976 throw new Haanga_CompilerException("Only subtemplates can call block.super");
978 /* no need to escape it */
979 $this->var_is_safe
= TRUE;
980 return $this->expr_str(self
::$block_var);
984 } else if (isset($this->var_alias
[$variable])) {
985 $variable = $this->var_alias
[$variable];
988 return $this->expr_var($variable);
993 public function generate_op_print($details, &$out)
995 $last = count($out)-1;
996 if (isset($details['variable'])) {
997 $content = $this->generate_variable_name($details['variable']);
998 } else if (isset($details['html'])) {
999 $html = $details['html'];
1000 $content = $this->expr_str($html);
1002 $content = $details;
1005 $var_name = 'buffer'.$this->ob_start
;
1007 if ($this->ob_start
== 0) {
1008 $operation = 'print';
1010 $operation = 'append_var';
1013 if ($last >= 0 && $out[$last]['op'] == $operation && ($operation != 'append_var' ||
$out[$last]['name'] === $var_name)) {
1014 /* try to append this to the previous print if it exists */
1015 $out[$last][] = $content;
1017 if ($this->ob_start
== 0) {
1018 $out[] = array('op' => 'print', $content);
1020 if (isset($out[$last]) && $out[$last]['op'] == 'declare' && $out[$last]['name'] == $var_name) {
1021 /* override an empty declaration of a empty buffer
1022 if the next operation is an 'append'
1024 $out[$last][] = $content;
1026 $out[] = $this->op_append('buffer'.$this->ob_start
, $content);
1033 // for [<key>,]<val> in <array> {{{
1034 protected function generate_op_loop($details, &$out)
1036 if (isset($details['empty'])) {
1037 $expr = $this->expr('==',
1038 $this->expr_exec('count', $this->expr_var($details['array'])),
1042 $out[] = $this->op_if($expr);
1043 $this->generate_op_code($details['empty'], $out);
1044 $out[] = $this->op_else();
1048 $oldid = $this->forid
;
1049 $this->forid
= $oldid+
1;
1050 $this->forloop
[$this->forid
] = array();
1053 $array = $this->get_filtered_var($details['array'], $varname);
1056 if ($this->is_safe($this->expr_var($varname))) {
1057 $this->set_safe($this->expr_var($details['variable']));
1060 $for_loop_body = array();
1061 $this->generate_op_code($details['body'], $for_loop_body);
1063 if ($this->is_safe($this->expr_var($varname))) {
1064 $this->set_unsafe($details['variable']);
1067 $oid = $this->forid
;
1068 $size = $this->expr_var('psize_'.$oid);
1071 if (isset($this->forloop
[$oid]['counter'])) {
1072 $var = 'forcounter1_'.$oid;
1073 $out[] = $this->op_declare($var, $this->expr_number(1));
1074 $for_loop_body[] = $this->op_inc($var);
1079 if (isset($this->forloop
[$oid]['counter0'])) {
1080 $var = 'forcounter0_'.$oid;
1081 $out[] = $this->op_declare($var, $this->expr_number(0) );
1082 $for_loop_body[] = $this->op_inc($var);
1087 if (isset($this->forloop
[$oid]['last'])) {
1089 $cnt = $this->op_declare('psize_'.$oid, $this->expr_exec('count', $this->expr_var($details['array'])));
1092 $var = 'islast_'.$oid;
1093 $expr = $this->op_declare($var, $this->expr("==", $this->expr_var('forcounter1_'.$oid), $size));
1097 $for_loop_body[] = $expr;
1102 if (isset($this->forloop
[$oid]['first'])) {
1103 $out[] = $this->op_declare('isfirst_'.$oid, $this->expr_TRUE());
1105 $for_loop_body[] = $this->op_declare('isfirst_'.$oid, $this->expr_FALSE());
1110 if (isset($this->forloop
[$oid]['revcounter'])) {
1112 $cnt = $this->op_declare('psize_'.$oid, $this->expr_exec('count', $this->expr_var($details['array'])));
1115 $var = $this->expr_var('revcount_'.$oid);
1116 $out[] = $this->op_declare($var, $size );
1118 $for_loop_body[] = $this->op_declare($var, $this->expr("-", $var, $this->expr_number(1)));
1123 if (isset($this->forloop
[$oid]['revcounter0'])) {
1125 $cnt = $this->op_declare('psize_'.$oid, $this->expr_exec('count', $this->expr_var($details['array'])));
1128 $var = $this->expr_var('revcount0_'.$oid);
1129 $out[] = $this->op_declare($var, $this->expr("-", $size, $this->expr_number(1)));
1131 $for_loop_body[] = $this->op_declare($var, $this->expr("-", $var, $this->expr_number(1)));
1135 /* Restore old ForID */
1136 $this->forid
= $oldid;
1138 /* Merge loop body */
1139 $loop = $this->op_foreach($array, $details['variable'], $details['index']);
1142 $out = array_merge($out, $for_loop_body);
1143 $out[] = $this->op_end('foreach');
1144 if (isset($details['empty'])) {
1145 $out[] = $this->op_end('if');
1150 // ifchanged [<var1> <var2] {{{
1151 protected function generate_op_ifchanged($details, &$out)
1153 static $ifchanged = 0;
1156 $var1 = 'ifchanged'.$ifchanged;
1157 if (!isset($details['check'])) {
1159 $this->ob_start($out);
1160 $var2 = 'buffer'.$this->ob_start
;
1162 $expr = $this->expr('OR',
1163 $this->expr_isset($var1, FALSE),
1165 $this->expr_var($var1),
1166 $this->expr_var($var2)
1170 $this->generate_op_code($details['body'], $out);
1172 $out[] = $this->op_if($expr);
1173 $this->generate_op_print(array('variable' => $var2), $out);
1174 $out[] = $this->op_declare($var1, $this->expr_var($var2));
1177 foreach ($details['check'] as $id=>$type) {
1178 if (!$this->is_var($type)) {
1179 throw new Haanga_CompilerException("Unexpected string {$type['string']}, expected a varabile");
1181 $this_expr = $this->expr('OR',
1182 $this->expr_isset("{$var1}[{$id}]", FALSE),
1184 $this->expr_var("{$var1}[{$id}]"),
1185 $this->expr_var($type['var'])
1189 $this_expr = $this->expr('AND',
1190 $this->expr('expr', $this_expr),
1198 $out[] = $this->op_if($expr);
1199 $this->generate_op_code($details['body'], $out);
1200 $out[] = $this->op_declare($var1, $this->expr_array_ex($details['check']));
1203 if (isset($details['else'])) {
1204 $out[] = $this->op_else();
1205 $this->generate_op_code($details['else'], $out);
1207 $out[] = $this->op_end('if');
1211 // autoescape ON|OFF {{{
1212 function generate_op_autoescape($details, &$out)
1214 $old_autoescape = $this->autoescape
;
1215 $this->autoescape
= strtolower($details['value']) == 'on';
1216 $this->generate_op_code($details['body'], $out);
1217 $this->autoescape
= $old_autoescape;
1221 // ob_Start(array &$out) {{{
1223 * Start a new buffering
1226 function ob_start(&$out)
1229 $out[] = $this->op_declare('buffer'.$this->ob_start
, array('string' => ''));
1234 function get_custom_tag($name)
1236 $function = $this->get_function_name($this->name
).'_tag_'.$name;
1237 $this->append
.= "\n\n".Haanga_Extensions
::getInstance('Haanga_Tag')->getFunctionBody($name, $function);
1242 * Generate needed code for custom tags (tags that aren't
1243 * handled by the compiler).
1246 function generate_op_custom_tag($details, &$out)
1250 $tags = Haanga_Extensions
::getInstance('Haanga_Tag');
1253 $tag_name = $details['name'];
1254 $tagFunction = $tags->getFunctionAlias($tag_name);
1256 if (!$tagFunction && !$tags->hasGenerator($tag_name)) {
1257 $function = $this->get_custom_tag($tag_name, isset($details['as']));
1259 $function = $tagFunction;
1262 if (isset($details['body'])) {
1264 if the custom tag has 'body'
1265 then it behave the same way as a filter
1267 $this->ob_start($out);
1268 $this->generate_op_code($details['body'], $out);
1269 $target = $this->expr_var('buffer'.$this->ob_start
);
1270 if ($tags->hasGenerator($tag_name)) {
1271 $exec = $tags->generator($tag_name, $this, array($target));
1273 $exec = $this->expr_exec($function, $target);
1276 $this->generate_op_print($exec, $out);
1280 $var = isset($details['as']) ?
$details['as'] : NULL;
1281 $args = array_merge(array($function), $details['list']);
1283 if ($tags->hasGenerator($tag_name)) {
1284 $exec = $tags->generator($tag_name, $this, $details['list'], $var);
1285 if ($exec InstanceOf ArrayIterator
) {
1287 The generator returned more than one statement,
1288 so we assume the output is already handled
1289 by one of those stmts.
1291 $out = array_merge($out, $exec->getArrayCopy());
1295 $exec = call_user_func_array(array($this, 'expr_exec'), $args);
1299 $out[] = $this->op_declare($var, $exec);
1301 $this->generate_op_print($exec, $out);
1306 // with <variable> as <var> {{{
1311 function generate_op_alias($details, &$out)
1313 $this->var_alias
[ $details['as'] ] = $details['var'];
1314 $this->generate_op_code($details['body'], $out);
1315 unset($this->var_alias
[ $details['as'] ] );
1319 // Custom Filters {{{
1320 function get_custom_filter($name)
1322 $function = $this->get_function_name($this->name
).'_filter_'.$name;
1323 $this->append
.= "\n\n".Haanga_Extensions
::getInstance('Haanga_Filter')->getFunctionBody($name, $function);
1328 function do_filtering($name, $args)
1332 $filter = Haanga_Extensions
::getInstance('Haanga_Filter');
1335 if (is_array($name)) {
1337 prepare array for ($func_name, $arg1, $arg2 ... )
1338 where $arg1 = last expression and $arg2.. $argX is
1339 defined in the template
1341 $args = array_merge($args, $name['args']);
1345 if (!$filter->isValid($name)) {
1346 throw new Haanga_CompilerException("{$name} is an invalid filter");
1348 if ($filter->hasGenerator($name)) {
1349 return $filter->generator($name, $this, $args);
1351 $fnc = $filter->getFunctionAlias($name);
1353 $fnc = $this->get_custom_filter($name);
1355 $args = array_merge(array($fnc), $args);
1356 $exec = call_user_func_array(array($this, 'expr_exec'), $args);
1361 function generate_op_filter($details, &$out)
1363 $this->ob_start($out);
1364 $this->generate_op_code($details['body'], $out);
1365 $target = $this->expr_var('buffer'.$this->ob_start
);
1366 foreach ($details['functions'] as $f) {
1367 $param = (isset($exec) ?
$exec : $target);
1368 $exec = $this->do_filtering($f, array($param));
1371 $this->generate_op_print($exec, $out);
1375 /* variable safety {{{ */
1376 function set_safe($name)
1378 if (is_string($name)) {
1379 $name = $this->expr_var($name);
1381 $this->safes
[serialize($name)] = TRUE;
1384 function set_unsafe($name)
1386 if (is_string($name)) {
1387 $name = $this->expr_var($name);
1389 unset($this->safes
[serialize($name)]);
1392 function is_safe($name)
1394 if ($this->var_is_safe
) {
1397 if (isset($this->safes
[serialize($name)])) {
1404 final static function main_cli()
1406 $argv = $GLOBALS['argv'];
1407 $haanga = new Haanga_Compiler
;
1408 $code = $haanga->compile_file($argv[1], TRUE);
1409 if (!isset($argv[2]) ||
$argv[2] != '--notags') {
1410 $code = "<?php\n\n$code";
1422 final class Haanga_Compiler_Runtime
extends Haanga_Compiler
1425 // get_function_name($name=NULL) {{{
1430 function get_function_name($name)
1432 return "haanga_".sha1($name);
1436 // set_template_name($path) {{{
1437 function set_template_name($path)
1443 // Override {% include %} {{{
1444 protected function generate_op_include($details, &$out)
1446 $expr = $this->expr_exec(
1449 $this->expr_var('vars'),
1451 $this->expr_var('blocks')
1453 $this->generate_op_print($expr, $out);
1457 // {% base "" %} {{{
1458 function expr_call_base_template()
1460 return $this->expr_exec(
1463 $this->expr_var('vars'),
1465 $this->expr_var('blocks')
1470 // get_base_template($base) {{{
1471 function get_base_template($base)
1473 $this->subtemplate
= $base;
1477 // Override get_Custom_tag {{{
1482 function get_custom_tag($name)
1484 $loaded = &$this->tags
;
1486 if (!isset($loaded[$name])) {
1487 $this->prepend_op
[] = $this->op_comment("Load tag {$name} definition");
1488 $this->prepend_op
[] = $this->op_expr($this->expr_exec("Haanga::doInclude", $this->Expr_str(Haanga_Extensions
::getInstance('Haanga_Tag')->getFilePath($name, FALSE))));
1489 $loaded[$name] = TRUE;
1492 $name = ucfirst($name);
1494 return "{$name}_Tag::main";
1498 // Override get_custom_filter {{{
1499 function get_custom_filter($name)
1501 $loaded = &$this->filters
;
1503 if (!isset($loaded[$name])) {
1504 $this->prepend_op
[] = $this->op_comment("Load filter {$name} definition");
1505 $this->prepend_op
[] = $this->op_expr($this->expr_exec("Haanga::doInclude", $this->Expr_str(Haanga_Extensions
::getInstance('Haanga_Filter')->getFilePath($name, FALSE))));
1506 $loaded[$name] = TRUE;
1509 $name = ucfirst($name);
1511 return "{$name}_Filter::main";
1522 * vim600: sw=4 ts=4 fdm=marker
1523 * vim<600: sw=4 ts=4