- Removed empty echo when strip_whitespace is enabled
[haanga.git] / lib / Haanga / Generator / PHP.php
blob855880f488e82bebc2af7f20aece3d66983cd877
1 <?php
2 /*
3 +---------------------------------------------------------------------------------+
4 | Copyright (c) 2010 César Rodas and Menéame Comunicacions S.L. |
5 +---------------------------------------------------------------------------------+
6 | Redistribution and use in source and binary forms, with or without |
7 | modification, are permitted provided that the following conditions are met: |
8 | 1. Redistributions of source code must retain the above copyright |
9 | notice, this list of conditions and the following disclaimer. |
10 | |
11 | 2. Redistributions in binary form must reproduce the above copyright |
12 | notice, this list of conditions and the following disclaimer in the |
13 | documentation and/or other materials provided with the distribution. |
14 | |
15 | 3. All advertising materials mentioning features or use of this software |
16 | must display the following acknowledgement: |
17 | This product includes software developed by César D. Rodas. |
18 | |
19 | 4. Neither the name of the César D. Rodas nor the |
20 | names of its contributors may be used to endorse or promote products |
21 | derived from this software without specific prior written permission. |
22 | |
23 | THIS SOFTWARE IS PROVIDED BY CÉSAR D. RODAS ''AS IS'' AND ANY |
24 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
25 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
26 | DISCLAIMED. IN NO EVENT SHALL CÉSAR D. RODAS BE LIABLE FOR ANY |
27 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
28 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
29 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
30 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE |
33 +---------------------------------------------------------------------------------+
34 | Authors: César Rodas <crodas@php.net> |
35 +---------------------------------------------------------------------------------+
38 // addslashes_ex($string) {{{
39 /**
40 * addslashes like function for single quote string ('foo')
42 * @return string
44 function addslashes_ex($string)
46 return str_replace("'", "\\'", $string);
48 // }}}
50 /**
51 * Haanga_Generator_PHP class
53 * This class takes the generated AST structure (arrays),
54 * and generated the PHP represantion.
58 class Haanga_Generator_PHP
60 protected $ident;
61 protected $tab = " ";
63 // getCode (AST $op_code) {{{
64 /**
65 * Transform the AST generated by the Haanga_Compiler class
66 * and return the equivalent PHP code.
68 * @param array $op_code
70 * @return string
72 final function getCode($op_code)
74 $this->ident = 0;
75 $code = "";
76 $size = count($op_code);
77 for ($i=0; $i < $size; $i++) {
78 $op = $op_code[$i];
79 if (!isset($op['op'])) {
80 throw new Haanga_Compiler_Exception("Invalid \$op_code ".print_r($op, TRUE));
83 /* echo optimization {{{ */
84 if ($op['op'] == 'print') {
85 do {
86 $next_op = $op_code[$i+1];
87 if (!isset($next_op) || $next_op['op'] != 'print') {
88 break;
90 for ($e=0; $e < count($next_op); $e++) {
91 if (!isset($next_op[$e])) {
92 break;
94 $op[] = $next_op[$e];
96 $i++;
97 } while(TRUE);
99 /* }}} */
101 /* declare optimization {{{ */
102 if ($op['op'] == 'declare' || $op['op'] == 'append_var') {
103 /* Code optimization
105 ** If a variable declaration, or append variable is followed
106 ** by several append_var, then merge everything into a
107 ** single STMT.
110 do {
111 $next_op = $op_code[$i+1];
112 if (!isset($next_op) || $next_op['op'] != 'append_var' || $next_op['name'] != $op['name']) {
113 break;
115 for ($e=0; $e < count($next_op); $e++) {
116 if (!isset($next_op[$e])) {
117 break;
119 $op[] = $next_op[$e];
121 $i++;
122 } while(TRUE);
124 /* }}} */
126 $method = "php_{$op['op']}";
127 if (!is_callable(array($this, $method))) {
128 throw new Exception("CodeGenerator: Missing method $method");
130 switch ($op['op']) {
131 case 'end_foreach':
132 case 'end_if':
133 case 'end_function':
134 case 'else':
135 break;
136 default:
137 $code .= $this->ident();
139 $code .= $this->$method($op);
141 return $code;
143 // }}}
145 // ident() {{{
147 * Get the string for the current tabulation
149 * @return string
151 protected function ident()
153 $code = PHP_EOL;
154 $code .= str_repeat($this->tab, $this->ident);
156 return $code;
158 // }}}
160 // php_else() {{{
162 * Return code for "else"
164 * @return string
166 protected function php_else()
168 $this->ident--;
169 $code = $this->ident()."} else {";
170 $this->ident++;
171 return $code;
173 // }}}
175 // php_comment() {{{
177 * Return code for "comments"
179 * @return string
181 function php_comment($op)
183 return "/* {$op['comment']} */";
185 // }}}
187 // php_function(array $op) {{{
188 /**
189 * Return the function declaration of the class, for now
190 * it has fixed params, this should change soon to generate
191 * any sort of functions
193 * @return string
195 function php_function($op)
197 $code = "function {$op['name']}(\$vars, \$return=FALSE, \$blocks=array())".$this->ident()."{";
198 $this->ident++;
199 return $code;
201 // }}}
203 // php_if(array $op) {{{
205 * Return the "if" declaration and increase $this->ident
207 * @return string
209 protected function php_if($op)
211 $code = "if (".$this->php_generate_expr($op['expr']).") {";
212 $this->ident++;
213 return $code;
215 // }}}
217 // php_expr($op) {{{
219 * Return a stand-alone statement
221 * @return string
223 protected function php_expr($op)
225 return $this->php_generate_expr($op[0]).";";
227 // }}}
229 // php_end_block() {{{
231 * Finish the current block (if, for, function, etc),
232 * return the final "}", and decrease $this->ident
234 * @return string
236 protected function php_end_block()
238 $this->ident--;
239 return $this->ident()."}";
241 // }}}
243 // php_end_function() {{{
245 * Return code to end a function
247 * @return string
249 protected function php_end_function()
251 return $this->php_end_block();
253 // }}}
255 // php_end_if() {{{
257 * Return code to end a if
259 * @return string
261 protected function php_end_if()
263 return $this->php_end_block();
265 // }}}
267 // php_end_foreach() {{{
269 * Return code to end a foreach
271 * @return string
273 protected function php_end_foreach()
275 return $this->php_end_block();
277 // }}}
279 // php_foreach(array $op) {{{
281 * Return the declaration of a "foreach" statement.
283 * @return string
285 protected function php_foreach($op)
287 $op['array'] = $this->php_get_varname($op['array']);
288 $op['value'] = $this->php_get_varname($op['value']);
289 $code = "foreach ({$op['array']} as ";
290 if (!isset($op['key'])) {
291 $code .= " {$op['value']}";
292 } else {
293 $op['key'] = $this->php_get_varname($op['key']);
294 $code .= " {$op['key']} => {$op['value']}";
297 $code .= ") {";
298 $this->ident++;
299 return $code;
301 // }}}
303 // php_append_var(array $op) {{{
305 * Return code to append something to a variable
307 * @return string
309 protected function php_append_var($op)
311 return $this->php_declare($op, '.=');
313 // }}}
315 // php_exec($op) {{{
317 * Return code for a function calling.
319 * @return string
321 protected function php_exec($op)
323 $code = "";
324 if (is_string($op['name'])) {
325 $code .= $op['name'];
326 } else {
327 $function = $this->php_get_varname($op['name']);
328 $code .= $function;
330 $code .= '(';
331 if (isset($op['args'])) {
332 $code .= $this->php_generate_list($op['args']);
334 $code .= ')';
335 return $code;
337 // }}}
339 // php_global($op) {{{
340 function php_global($op)
342 return "global \$".implode(", \$", $op['vars']).";";
344 // }}}
346 // php_generate_expr($op) {{{
348 * Return an expression
350 * @return string
352 protected function php_generate_expr($expr)
354 $code = '';
355 if (is_object($expr)) {
356 $expr = $expr->getArray();
358 if (is_array($expr) && isset($expr['op_expr'])) {
359 if ($expr['op_expr'] == 'expr') {
360 $code .= "(";
361 $code .= $this->php_generate_expr($expr[0]);
362 $code .= ")";
363 } else if ($expr['op_expr'] == 'not') {
364 $code .= "!".$this->php_generate_expr($expr[0]);
365 } else {
366 $code .= $this->php_generate_expr($expr[0]);
367 if (is_object($expr['op_expr'])) {
368 var_dump($expr);die('unexpected error');
370 $code .= " {$expr['op_expr']} ";
371 $code .= $this->php_generate_expr($expr[1]);
373 } else {
374 if (is_array($expr)) {
375 $code .= $this->php_generate_stmt(array($expr));
376 } else {
377 if ($expr === FALSE) {
378 $expr = 'FALSE';
379 } else if ($expr === TRUE) {
380 $expr = 'TRUE';
382 $code .= $expr;
385 return $code;
387 // }}}
389 // php_generate_list(array ($array) {{{
391 * Return a list of expressions for parameters
392 * of a function
394 * @return string
396 protected function php_generate_list($array)
398 $code = "";
399 foreach ($array as $value) {
400 $code .= $this->php_generate_stmt(array($value));
401 $code .= ", ";
403 return substr($code, 0, -2);
405 // }}}
407 // php_generate_stmt(Array $op) {{{
409 * Return the representation of a statement
411 * @return string
413 protected function php_generate_stmt($op)
415 $code = "";
417 for ($i=0; $i < count($op); $i++) {
418 if (!isset($op[$i])) {
419 continue;
421 if (!is_Array($op[$i])) {
422 throw new Haanga_Compiler_Exception("Malformed declaration ".print_r($op, TRUE));
424 $key = key($op[$i]);
425 $value = current($op[$i]);
426 switch ($key) {
427 case 'array':
428 $code .= "Array(";
429 $code .= $this->php_generate_list($value);
430 $code .= ")";
431 break;
432 case 'function':
433 case 'exec':
434 if (strlen($code) != 0 && $code[strlen($code) -1] != '.') {
435 $code .= '.';
438 $value = array('name' => $value, 'args' => $op[$i]['args']);
439 $code .= $this->php_exec($value, FALSE);
440 $code .= '.';
441 break;
442 case 'key':
443 $code .= $this->php_generate_stmt(array($value[0]))." => ".$this->php_generate_stmt(array($value[1]));
444 break;
445 case 'string':
446 if ($code != "" && $code[strlen($code)-1] == "'") {
447 $code = substr($code, 0, -1);
448 } else {
449 $code .= "'";
451 $html = addslashes_ex($value);
452 //$html = addslashes($value);
453 //$html = str_replace(array('$', "\r", "\t", "\n","\\'"), array('\\$', '\r', '\t', '\n',"'"), $html);
454 $code .= $html."'";
455 break;
456 case 'var':
457 if (strlen($code) != 0 && $code[strlen($code) -1] != '.') {
458 $code .= '.';
460 $code .= $this->php_get_varname($value).'.';
461 break;
462 case 'number':
463 if (!is_numeric($value)) {
464 throw new Exception("$value is not a valid number");
466 $code .= $value;
467 break;
468 case 'op_expr':
469 if (strlen($code) != 0) {
470 $code .= '.';
472 $code .= $this->php_generate_expr($op[$i]);
473 $code .= ".";
474 break;
475 case 'expr':
476 if (strlen($code) != 0) {
477 $code .= '.';
479 $code .= $this->php_generate_expr($value);
480 $code .= ".";
481 break;
482 case 'expr_cond':
483 if (strlen($code) != 0) {
484 $code .= '.';
486 $code .= "(";
487 $code .= $this->php_generate_expr($value);
488 $code .= " ? ";
489 $code .= $this->php_generate_stmt(array($op[$i]['true']));
490 $code .= " : ";
491 $code .= $this->php_generate_stmt(array($op[$i]['false']));
492 $code .= ").";
493 break;
494 case 'constant':
495 $code = $value;
496 break;
497 default:
498 throw new Exception("Don't know how to declare {$key} = {$value} (".print_r($op[$i], TRUE));
502 if ($code != "" && $code[strlen($code)-1] == '.') {
503 $code = substr($code, 0, -1);
506 return $code;
508 // }}}
510 // php_print(array $op) {{{
512 * Return an echo of an stmt
514 * @return string
516 protected function php_print($op)
518 $output = $this->php_generate_stmt($op);
519 if ($output == "' '" && Haanga_Compiler::getOption('strip_whitespace')) {
520 return; /* ignore this */
522 return 'echo '.$output.';';
524 // }}}
526 // php_inc(array $op) {{{
528 * Return increment a variable ($var++)
530 * @return string
532 protected function php_inc($op)
534 return $this->php_get_varname($op['name'])."++;";
536 // }}}
538 // php_declare(array $op, $assign='=') {{{
540 * Return a variable declaration
542 * @return string
544 protected function php_declare($op, $assign=' =')
546 $op['name'] = $this->php_get_varname($op['name']);
547 $code = "{$op['name']} {$assign} ".$this->php_generate_stmt($op).";";
548 return $code;
550 // }}}
552 // php_get_varname(mixed $var) {{{
554 * Return a variable
556 * @param mixed $var
558 * @return string
560 protected function php_get_varname($var)
562 if (is_array($var)) {
563 if (!is_string($var[0])) {
564 if (count($var) == 1) {
565 return $this->php_get_varname($var[0]);
566 } else {
567 throw new Exception("Invalid variable definition ".print_r($var, TRUE));
570 $var_str = $this->php_get_varname($var[0]);
571 for ($i=1; $i < count($var); $i++) {
572 if (is_string($var[$i])) {
573 $var_str .= "['".addslashes_ex($var[$i])."']";
574 } else if (is_array($var[$i])) {
575 if (isset($var[$i]['var'])) {
576 /* index is a variable */
577 $var_str .= '['.$this->php_get_varname($var[$i]['var']).']';
578 } else if (isset($var[$i]['string'])) {
579 /* index is a string */
580 $var_str .= "['".addslashes_ex($var[$i]['string'])."']";
581 } else if (isset($var[$i]['number'])) {
582 /* index is a number */
583 $var_str .= '['.$var[$i]['number'].']';
584 } else if (isset($var[$i]['object'])) {
585 /* Accessing a object's property */
586 $var_str .= '->'.$var[$i]['object'];
587 } else if ($var[$i] === array()) {
588 /* index is a NULL (do append) */
589 $var_str .= '[]';
590 } else {
591 throw new Haanga_Compiler_Exception('Unknown variable definition '.print_r($var, TRUE));
595 return $var_str;
596 } else {
597 return "\$".$var;
600 // }}}
602 // php_return($op) {{{
604 * Return "return"
606 * @return string
608 protected function php_return($op)
610 $code = "return ".$this->php_generate_stmt($op).";";
611 return $code;
613 // }}}
618 * Local variables:
619 * tab-width: 4
620 * c-basic-offset: 4
621 * End:
622 * vim600: sw=4 ts=4 fdm=marker
623 * vim<600: sw=4 ts=4