From 3ecf03caace8e6b05946f2f7983439bd9cb40530 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Sun, 20 Oct 2013 14:12:09 +0200 Subject: Trying to write a better parser. UNTESTED! The previous parser relied on some obscure regular expressions which probably could be tricked. The new parser will also allow a more sane definition of the short tags ?{..|..|..} and ~{..|..|..}. Before these were a terrible hack... All in all the goal of the rewrite is to produce a more robust parser. --- stupid_template_engine.php | 563 ++++++++++++++++++++++++++++----------------- 1 file changed, 346 insertions(+), 217 deletions(-) (limited to 'stupid_template_engine.php') diff --git a/stupid_template_engine.php b/stupid_template_engine.php index 7153a99..bef1fac 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -29,6 +29,10 @@ abstract class ASTNode class TextNode extends ASTNode { public $text; + public function __construct($tpl, $off, $text = "") { + parent::__construct($tpl, $off); + $this->text = $text; + } } class TagNode extends ASTNode @@ -102,255 +106,380 @@ class RuntimeError extends \Exception {} */ class FatalRuntimeError extends \Exception {} -/* $text must start after the first opening bracket */ -function find_closing_bracket($text, $opening, $closing) -{ - $counter = 1; - $len = strlen($text); - for($i = 0; $i < $len; ++$i) - { - switch($text[$i]) - { - case $opening: - ++$counter; - break; - case $closing: - --$counter; - break; - } - if($counter == 0) - break; - } +class Parser { + private $text; + private $name; + private $off; + private $len; - if($counter > 0) - throw new \Exception("Missing closing \"$closing\". Stop."); - - return $i; -} - -function instance_in_array($classname, $a) -{ - foreach($a as $v) - { - if($v instanceof $classname) - return True; - } - return False; -} - -function unescape_text($text) -{ - return stripcslashes($text); -} - -function tokenize_text($text, $tpl, $off) -{ - $tokens = array(); - /* Find next non-escaped $-char */ - if(preg_match("/(?:(?text = preg_replace("/^(?:\\n|\\r\\n|\\r)\\s*/s", "", unescape_text($text)); - return (strlen($node->text) == 0) ? array() : array($node); - } + const PARSE_SHORT = 1; + const PARSE_TAG = 2; - if($match[0][1] > 0) - { - $node = new TextNode($tpl, $off); - $node->text = unescape_text(substr($text, 0, $match[0][1])); - $tokens[] = $node; - } + const ESCAPES_DEFAULT = '$?~{}|\\'; - if($text[$match[0][1] + 1] == "{") - { - try - { - $varend = find_closing_bracket(substr($text, $match[0][1] + 2), "{", "}") + $match[0][1] + 2; - } - catch(\Exception $e) - { - throw new ParseCompileError("Parse Error: Missing closing '}'", $tpl, $off + $match[0][1] + 1); - } - return array_merge( - $tokens, - tokenize_text("\$" . substr($text, $match[0][1] + 2, ($varend - 1) - ($match[0][1] + 1)), $tpl, $off + $match[0][1] + 2), - tokenize_text(substr($text, $varend + 1), $tpl, $off + $varend + 1) - ); + private function __construct($text, $name) { + $this->text = $text; + $this->name = $name; + $this->off = 0; + $this->len = mb_strlen($text); } - $text = substr($text, $match[0][1] + 1); - $off += $match[0][1] + 1; - if(preg_match("/^[a-zA-Z0-9_]+/s", $text, $match, PREG_OFFSET_CAPTURE) == 0) - { - $nexttokens = tokenize_text($text, $tpl, $off); - if($nexttokens[0] instanceof TextNode) - $nexttokens[0]->text = "\$" . $nexttokens[0]->text; - else - { - $node = new TextNode($tpl, $off); - $node->text = "\$"; - $tokens[] = $node; + private function next($n = 1) { + if($n <= 0) { + throw new \InvalidArgumentException("\$n must be > 0"); } - return array_merge($tokens, $nexttokens); + $c = mb_substr($this->text, $this->off, $n); + $this->off = max($this->off + $n, $this->len); + return $c; } - $node = new VariableNode($tpl, $off + $match[0][1]); - $node->name = $match[0][0]; - $node->arrayfields = array(); - $text = substr($text, $match[0][1] + strlen($match[0][0])); - $off += $match[0][1] + strlen($match[0][0]); - while(@$text[0] == "[") - { - $text = substr($text, 1); - $off += 1; - try - { - $fieldend = find_closing_bracket($text, "[", "]"); + private function back($n = 1) { + if($n <= 0) { + throw new \InvalidArgumentException("\$n must be > 0"); } - catch(\Exception $e) - { - throw new ParseCompileError("Parse Error: Missing closing ']'", $tpl, $off - 1); - } - $node->arrayfields[] = tokenize_text(substr($text, 0, $fieldend), $tpl, $off); - $text = substr($text, $fieldend + 1); - $off += $fieldend + 1; + $this->off = max($this->off - $n, 0); } - $tokens[] = $node; + private function search_off($needle) { + return mb_strpos($this->text, $needle, $this->off); + } - return strlen($text) > 0 ? array_merge($tokens, tokenize_text($text, $tpl, $off)) : $tokens; -} + private function search_multi($needles) { + $oldoff = $this->off; + + $minoff = $this->len; + $which = NULL; + + foreach($needle as $key => $needle) { + if(($off = $this->search_off($needle)) === false) { + continue; + } -function mk_ast($code, $tpl, $err_off) -{ - $ast = array(); - - if(preg_match("/\\<\\s*ste:([a-zA-Z0-9_]*)/s", $code, $matches, PREG_OFFSET_CAPTURE) == 0) - return tokenize_text($code, $tpl, $err_off); - - $ast = tokenize_text(substr($code, 0, $matches[0][1]), $tpl, $err_off); - $tag = new TagNode($tpl, $err_off + $matches[0][1]); - $tag->name = $matches[1][0]; - - $code = substr($code, $matches[0][1] + strlen($matches[0][0])); - $err_off += $matches[0][1] + strlen($matches[0][0]); + if($off < $minoff) { + $minoff = $off; + $which = $key; + } + } + + $this->off = $minoff + (($which === NULL) ? 0 : mb_strlen($which)); + + return array($which, $minoff, mb_substr($this->text, $oldoff, $minoff - $oldoff), $oldoff); + } - $tag->params = array(); + private function search($needle) { + $oldoff = $this->off; + + $off = $this->search_off($needle); + if($off === false) { + $this->off = $this->len; + return array(false, mb_substr($this->text, $oldoff), $oldoff); + } + + $this->off = $off + mb_strlen($needle); + return array($off, mb_substr($this->text, $oldoff, $off - $oldoff), $oldoff); + } - while(preg_match("/^\\s+([a-zA-Z0-9_]+)=((?:\"(?:.*?)(? 0) - { - $paramval = substr($code, $matches[2][1] + 1, strlen($matches[2][0]) - 2); - $paramval = str_replace("\\\"", "\"", $paramval); - $paramval = str_replace("\\'", "'", $paramval); - $tag->params[$matches[1][0]] = tokenize_text($paramval, $tpl, $err_off + $matches[2][1] + 1); - $code = substr($code, strlen($matches[0][0])); - $err_off += strlen($matches[0][0]); + private function take_while($cb) { + $s = ""; + while($c = $this->next()) { + if(!call_user_func($cb, $c)) { + $this->back(); + return $s; + } + $s .= $c; + } + return $s; } - if(preg_match("/^\\s*([\\/]?)\\s*\\>/s", $code, $matches) == 0) - throw new ParseCompileError("Parse Error: Missing closing '>' in \"" . $tag->name . "\"-Tag.", $tpl, $tag->offset); + private function skip_ws() { + $this->take_while("ctype_space"); + } - $code = substr($code, strlen($matches[0])); - $err_off += strlen($matches[0]); + private function get_name() { + $off = $this->off; + $name = $this->take_while(function($c) { return ctype_alnum($c) || ($c == "_"); }); + if(mb_strlen($name) == 0) { + throw new ParseCompileError("Expected a name (alphanumeric chars + '_', at least one char)"); + } + return $name; + } - $tag->sub = array(); + public static function parse($text, $name) { + $obj = new self($text, $name); + $res = $obj->parse_text( + self::ESCAPES_DEFAULT, /* Escapes */ + self::PARSE_SHORT | self::PARSE_TAG /* Flags */ + ); + return $res[0]; + } - if($matches[1][0] != "/") - { - /* Handling ste:comment pseudotag */ - if($tag->name == "comment") - { - if(preg_match("/\\<\\s*\\/\\s*ste:comment\\s*\\>/s", $code, $matches, PREG_OFFSET_CAPTURE) == 0) - return $ast; /* Treat the whole code as comment */ - $comment_end = $matches[0][1] + strlen($matches[0][0]); - return array_merge($ast, mk_ast(substr($code, $comment_end), $tpl, $err_off + $comment_end)); + private function parse_text($escapes, $flags, $breakon = NULL, $separator = NULL, $nullaction = NULL, $opentag = NULL, $openedat = -1) { + $elems = array(); + $astlist = array(); + + $needles = array( + "commentopen" => "", + "rawopen" => "", + "escape" => '\\', + "varcurlyopen" => '${', + "var" => '$', + ); + + if($flags & self::PARSE_TAG) { + $needles["tagopen"] = 'name == "rawtext") - { - $tag = new TextNode($tpl, $tag->offset); - if(preg_match("/\\<\\s*\\/\\s*ste:rawtext\\s*\\>/s", $code, $matches, PREG_OFFSET_CAPTURE) == 0) - { - /* Treat the rest of the code as rawtext */ - $tag->text = $code; - $ast[] = $tag; - return $ast; - } - $tag->text = strpos($code, 0, $matches[0][1]); - $ast[] = $tag; - $rawtext_end = $matches[0][1] + strlen($matches[0][0]); - return array_merge($ast, mk_ast(substr($code, $rawtext_end), $tpl, $err_off + $rawtext_end)); + if($separator !== NULL) { + $needles["sep"] = $separator; + } + if($breakon !== NULL) { + $needles["break"] = $breakon; } - $off = 0; - $last_tag_start = 0; - $tagstack = array(array($tag->name, $tag->offset - $err_off)); - while(preg_match("/\\<((?:\\s*)|(?:\\s*\\/\\s*))ste:([a-zA-Z0-9_]*)(?:\\s+(?:[a-zA-Z0-9_]+)=(?:(?:\"(?:.*?)(?/s", $code, $matches, PREG_OFFSET_CAPTURE, $off) > 0) /* RegEx from hell! Matches all Tags. Opening, closing and self-closing ones. */ - { - if(trim($matches[3][0]) != "/") - { - $closingtag = trim($matches[1][0]); - if($closingtag[0] == "/") - { - list($matching_opentag, $mo_off) = array_pop($tagstack); - if($matching_opentag != $matches[2][0]) - throw new ParseCompileError("Parse Error: Missing closing \"ste:$matching_opentag\"-Tag.", $tpl, $mo_off + $err_off); + for(;;) { + list($which, $off, $before, $offbefore) = $this->search_multi($needles); + + $astlist[] = new TextNode($this->name, $offbefore, $before); + + switch($which) { + case NULL: + if($nullaction === NULL) { + $elems[] = $astlist; + return $elems; + } else { + call_user_func($nullaction); + } + break; + case "commentopen": + list($off, $before, $offbefore) = $this->search(""); + if($off === false) { + throw new ParseCompileError("ste:comment was not closed", $this->name, $offbefore); + } + break; + case "rawopen": + $off_start = $off; + list($off, $before, $offbefore) = $this->search(""); + if($off === false) { + throw new ParseCompileError("ste:rawtext was not closed", $this->name, $off_start); + } + $astlist[] = new TextNode($this->name, $off_start, $before); + break; + case "tagopen": + $astlist[] = $this->parse_tag($off); + break; + case "closetagopen": + $off_start = $off; + $name = $this->get_name(); + $this->skip_ws(); + $off = $this->off; + if($this->next() != ">") { + throw new ParseCompileError("Expected '>' in closing ste-Tag", $this->name, $off); + } + + if($opentag === NULL) { + throw new ParseCompileError("Found closing ste:$name tag, but no tag was opened", $this->name, $off_start); + } + if($opentag != $name) { + throw new ParseCompileError("Open ste:$opentag was not closed", $this->name, $openedat); + } + + $elems[] = $astlist; + return $elems; + case "escape": + $c = $this->next(); + if(mb_strpos($escapes, $c) !== false) { + $astlist[] = new TextNode($this->name, $off, $c); + } else { + $astlist[] = new TextNode($this->name, $off, '\\'); + $this->back(); } - else - $tagstack[] = array($matches[2][0], $matches[0][1]); - } - $last_tag_start = $matches[0][1]; - $off = $last_tag_start + strlen($matches[0][0]); - if(empty($tagstack)) break; + case "shortifopen": + $elems = $this->parse_short("?{", $off); + if(count($elems) != 3) { + throw new ParseCompileError("A short if tag must have the form ?{..|..|..}", $this->name, $off); + } + + list($cond, $then, $else) = $elems; + $thentag = new TagNode($this->name, $off); + $thentag->name = "then"; + $thentag->sub = $then; + + $elsetag = new TagNode($this->name, $off); + $elsetag->name = "else"; + $elsetag->sub = $else; + + $iftag = new TagNode($this->name, $off); + $iftag->name = "if"; + $iftag->sub = $cond; + $iftag->sub[] = $thentag; + $iftag->sub[] = $elsetag; + + $astlist[] = $iftag; + break; + case "shortcompopen": + $elems = $this->parse_short("~{", $off); + if(count($elems) != 3) { + throw new ParseCompileError("A short comparasion tag must have the form ~{..|..|..}", $this->name, $off); + } + + // TODO: What will happen, if a tag was in one of the elements? + list($a, $op, $b) = $elems; + $cmptag = new TagNode($this->name, $off); + $cmptag->name = "cmp"; + $cmptag->params["text_a"] = $a; + $cmptag->params["op"] = $op; + $cmptag->params["text_b"] = $b; + + $astlist[] = $cmptag; + break; + case "sep": + $elems[] = $astlist; + $astlist = array(); + break; + case "varcurlyopen": + $astlist[] = $this->parse_var($off, true); + break; + case "var": + $astlist[] = $this->parse_var($off, false); + break; + case "break": + $elems[] = $astlist; + return $elems; + } } - if((!empty($tagstack)) or ($tag->name != $matches[2][0])) - throw new ParseCompileError("Parse Error: Missing closing \"ste:" . $tag->name . "\"-Tag.", $tpl, $tag->offset); + $elems[] = $astlist; + return $elems; + } + + private function parse_short($shortname, $openedat) { + $tplname = $this->name; - $tag->sub = mk_ast(substr($code, 0, $last_tag_start), $tpl, $err_off); - $code = substr($code, $off); - $err_off += $off; + return $this->parse_text( + self::ESCAPES_DEFAULT, /* Escapes */ + self::PARSE_SHORT | self::PARSE_TAG, /* Flags */ + '}', /* Break on */ + '|', /* Separator */ + function() use ($shortname, $tplname, $openedat) { /* NULL action */ + throw new ParseCompileError("Unclosed $shortname", $tplname, $openedat); + }, + NULL, /* Open tag */ + $openedat /* Opened at */ + ); } - if($tag !== NULL) - $ast[] = $tag; - return array_merge($ast, strlen($code) > 0 ? mk_ast($code, $tpl, $err_off) : array()); -} - -/* - * Function: precompile - * Precompiling STE T/PL templates. - * You only need this function, if you want to manually transcompile a template. - * - * Parameters: - * $code - The input code - * - * Returns: - * The precompiled code. - */ -function precompile($code) -{ - $code = preg_replace( /* Transform short form of comparison (~{a|op|b}) to long form */ - "/(?:(?", - $code - ); - $code = preg_replace( /* Transform short form of if-clause (?{cond|then|else}) to long form */ - "/(?:(?\$1\$2\$3", - $code - ); - /* Unescape \? \~ \{ \} \| */ - $code = preg_replace("/(?:(?name, $openedat); + $varnode->name = $this->get_name(); + $varnode->arrayfields = $this->parse_array(); + + if(!$curly) { + return $varnode; + } + + if($this->next() != "}") { + throw new ParseCompileError("Unclosed '\${'", $this->name, $openedat); + } + $varnode->arrayfields = array_merge($varnode->arrayfields, $this->parse_array()); + return $varnode; + } - return $code; + private function parse_array() { + $tplname = $this->name; + + $arrayfields = array(); + + while($this->next() == "[") { + $openedat = $this->off - 1; + $res = $this->parse_text( + self::ESCAPES_DEFAULT, /* Escapes */ + 0, /* Flags */ + ']', /* Break on */ + NULL, /* Separator */ + function() use ($tplname, $openedat) { /* NULL action */ + throw new ParseCompileError("Unclosed array access '[...]'", $tplname, $openedat); + }, + NULL, /* Open tag */ + $openedat /* Opened at */ + ); + $arrayfields[] = $res[0]; + } + + $this->back(); + return $arrayfields; + } + + private function parse_tag($openedat) { + $tplname = $this->name; + + $this->skip_ws(); + $tag = new TagNode($this->name, $openedat); + $name = $tag->name = $this->get_name(); + $tag->params = array(); + $tag->sub = array(); + + for(;;) { + $this->skip_ws(); + + switch($this->next()) { + case '/': /* Self-closing tag */ + $this->skip_ws(); + if($this->next() != '>') { + throw new ParseCompileError("Unclosed opening )", $this->name, $openedat); + } + + return $tag; + case '>': + $tag->sub = $this->parse_text( + self::ESCAPES_DEFAULT, /* Escapes */ + self::PARSE_SHORT | self::PARSE_TAG, /* Flags */ + NULL, /* Break on */ + NULL, /* Separator */ + function() use ($name, $tplname, $openedat) { /* NULL action */ + throw new ParseCompileError("Open ste:$name tag was not closed", $tplname, $openedat); + }, + $tag->name, /* Open tag */ + $openedat /* Opened at */ + ); + return $tag; + default: + $this->back(); + + $param = $this->get_name(); + + $this->skip_ws(); + if($this->next() != '=') { + throw new ParseCompileError("Expected '=' after tag parameter name", $this->name, $this->off - 1); + } + $this->skip_ws(); + + $quot = $this->next(); + if(($quot != '"') && ($quot != "'")) { + throw new ParseCompileError("Expected ' or \" after '=' of tag parameter", $this->name, $this->off - 1); + } + + $off = $this->off - 1; + $tag->params[$name] = $this->parse_text( + self::ESCAPES_DEFAULT . $quot, /* Escapes */ + 0, /* Flags */ + $quot, /* Break on */ + NULL, /* Separator */ + function() use ($quot, $tplname, $off) { /* NULL action */ + throw new ParseCompileError("Open tag parameter value ($quot) was not closed", $tplname, $off); + }, + NULL, /* Open tag */ + $off /* Opened at */ + ); + } + } + } } /* @@ -1309,7 +1438,7 @@ class STECore $content = precompile($content); try { - $ast = parse($content, $tpl); + $ast = Parser::parse($content, $tpl); $transc = transcompile($ast); } catch(ParseCompileError $e) -- cgit v1.2.3-54-g00ecf From a68a8f7ccf60fa17fc5aa379ac191f555668fe25 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Sun, 20 Oct 2013 14:15:53 +0200 Subject: Some smaller fixes. Parser seems to work now. * Parser::next fixed * Parser::search_multi fixes (wrong variable, wrong offset added) * fixed shortifopen and shortcompopen overwriting parser result. --- stupid_template_engine.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'stupid_template_engine.php') diff --git a/stupid_template_engine.php b/stupid_template_engine.php index bef1fac..2c8a5ac 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -129,7 +129,7 @@ class Parser { throw new \InvalidArgumentException("\$n must be > 0"); } $c = mb_substr($this->text, $this->off, $n); - $this->off = max($this->off + $n, $this->len); + $this->off = min($this->off + $n, $this->len); return $c; } @@ -151,7 +151,7 @@ class Parser { $minoff = $this->len; $which = NULL; - foreach($needle as $key => $needle) { + foreach($needles as $key => $needle) { if(($off = $this->search_off($needle)) === false) { continue; } @@ -162,7 +162,7 @@ class Parser { } } - $this->off = $minoff + (($which === NULL) ? 0 : mb_strlen($which)); + $this->off = $minoff + (($which === NULL) ? 0 : mb_strlen((string) $needles[$which])); return array($which, $minoff, mb_substr($this->text, $oldoff, $minoff - $oldoff), $oldoff); } @@ -301,12 +301,12 @@ class Parser { } break; case "shortifopen": - $elems = $this->parse_short("?{", $off); - if(count($elems) != 3) { + $shortelems = $this->parse_short("?{", $off); + if(count($shortelems) != 3) { throw new ParseCompileError("A short if tag must have the form ?{..|..|..}", $this->name, $off); } - list($cond, $then, $else) = $elems; + list($cond, $then, $else) = $shortelems; $thentag = new TagNode($this->name, $off); $thentag->name = "then"; $thentag->sub = $then; @@ -324,13 +324,13 @@ class Parser { $astlist[] = $iftag; break; case "shortcompopen": - $elems = $this->parse_short("~{", $off); - if(count($elems) != 3) { + $shortelems = $this->parse_short("~{", $off); + if(count($shortelems) != 3) { throw new ParseCompileError("A short comparasion tag must have the form ~{..|..|..}", $this->name, $off); } // TODO: What will happen, if a tag was in one of the elements? - list($a, $op, $b) = $elems; + list($a, $op, $b) = $shortelems; $cmptag = new TagNode($this->name, $off); $cmptag->name = "cmp"; $cmptag->params["text_a"] = $a; -- cgit v1.2.3-54-g00ecf From 6ca501f5992f4ce11fb0e6dc61769d2e9614232c Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Sun, 20 Oct 2013 16:20:43 +0200 Subject: Fixed tag parameter parsing --- stupid_template_engine.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'stupid_template_engine.php') diff --git a/stupid_template_engine.php b/stupid_template_engine.php index 2c8a5ac..c67b1fd 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -466,7 +466,7 @@ class Parser { } $off = $this->off - 1; - $tag->params[$name] = $this->parse_text( + $tag->params[$param] = $this->parse_text( self::ESCAPES_DEFAULT . $quot, /* Escapes */ 0, /* Flags */ $quot, /* Break on */ @@ -1435,7 +1435,6 @@ class STECore $content = $this->storage_access->load($tpl, $mode); if($mode == MODE_SOURCE) { - $content = precompile($content); try { $ast = Parser::parse($content, $tpl); -- cgit v1.2.3-54-g00ecf From f18070853c4c82247ec8e153dbf2a4a1b43e730d Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Sun, 20 Oct 2013 16:21:03 +0200 Subject: Added function to tidy up the AST --- stupid_template_engine.php | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) (limited to 'stupid_template_engine.php') diff --git a/stupid_template_engine.php b/stupid_template_engine.php index c67b1fd..840e0ce 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -211,7 +211,50 @@ class Parser { self::ESCAPES_DEFAULT, /* Escapes */ self::PARSE_SHORT | self::PARSE_TAG /* Flags */ ); - return $res[0]; + return self::tidyup_ast($res[0]); + } + + private static function tidyup_ast($ast) { + $out = array(); + + $prevtext = NULL; + $first = true; + foreach($ast as $node) { + if($node instanceof TagNode) { + $node->sub = self::tidyup_ast($node->sub); + } + + if(!($node instanceof TextNode)) { + if($prevtext !== NULL) { + if($prevtext->text != "") { + $out[] = $prevtext; + } + $prevtext = NULL; + } + + $out[] = $node; + + $first = false; + continue; + } + + if($first) { + $node->text = ltrim($node->text); + } + + if($prevtext !== NULL) { + $prevtext->text .= $node->text; + } else { + $prevtext = $node; + } + + $first = false; + } + + if(($prevtext !== NULL) && ($prevtext->text != "")) { + $out[] = $prevtext; + } + return $out; } private function parse_text($escapes, $flags, $breakon = NULL, $separator = NULL, $nullaction = NULL, $opentag = NULL, $openedat = -1) { -- cgit v1.2.3-54-g00ecf From 5401ff08a26e11649d282c5c8fe11d9d8c199c53 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Sun, 20 Oct 2013 17:08:06 +0200 Subject: Fixed parse_tag and parse_var Now everything seems to work fine. The output code very noisy, though... --- stupid_template_engine.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'stupid_template_engine.php') diff --git a/stupid_template_engine.php b/stupid_template_engine.php index 840e0ce..1cd21fc 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -423,14 +423,9 @@ class Parser { $varnode->name = $this->get_name(); $varnode->arrayfields = $this->parse_array(); - if(!$curly) { - return $varnode; - } - - if($this->next() != "}") { + if(($curly) && ($this->next() != "}")) { throw new ParseCompileError("Unclosed '\${'", $this->name, $openedat); } - $varnode->arrayfields = array_merge($varnode->arrayfields, $this->parse_array()); return $varnode; } @@ -480,7 +475,7 @@ class Parser { return $tag; case '>': - $tag->sub = $this->parse_text( + $sub = $this->parse_text( self::ESCAPES_DEFAULT, /* Escapes */ self::PARSE_SHORT | self::PARSE_TAG, /* Flags */ NULL, /* Break on */ @@ -491,6 +486,7 @@ class Parser { $tag->name, /* Open tag */ $openedat /* Opened at */ ); + $tag->sub = $sub[0]; return $tag; default: $this->back(); @@ -509,7 +505,7 @@ class Parser { } $off = $this->off - 1; - $tag->params[$param] = $this->parse_text( + $paramval = $this->parse_text( self::ESCAPES_DEFAULT . $quot, /* Escapes */ 0, /* Flags */ $quot, /* Break on */ @@ -520,6 +516,7 @@ class Parser { NULL, /* Open tag */ $off /* Opened at */ ); + $tag->params[$param] = $paramval[0]; } } } -- cgit v1.2.3-54-g00ecf From b2c464d6e3b4fa91eca3a80985150ee100713b9f Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Sun, 20 Oct 2013 22:14:18 +0200 Subject: tidyup_ast improved --- stupid_template_engine.php | 53 ++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 25 deletions(-) (limited to 'stupid_template_engine.php') diff --git a/stupid_template_engine.php b/stupid_template_engine.php index 1cd21fc..386f760 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -218,37 +218,40 @@ class Parser { $out = array(); $prevtext = NULL; - $first = true; + $wastag = true; foreach($ast as $node) { - if($node instanceof TagNode) { - $node->sub = self::tidyup_ast($node->sub); - } - - if(!($node instanceof TextNode)) { - if($prevtext !== NULL) { - if($prevtext->text != "") { - $out[] = $prevtext; - } - $prevtext = NULL; + if($node instanceof TextNode) { + if($wastag) { + $node->text = ltrim($node->text); } - $out[] = $node; - - $first = false; - continue; - } - - if($first) { - $node->text = ltrim($node->text); - } - - if($prevtext !== NULL) { - $prevtext->text .= $node->text; + if($prevtext === NULL) { + $prevtext = $node; + } else { + $prevtext->text .= $node->text; + } } else { - $prevtext = $node; + if(($prevtext !== NULL) && ($prevtext->text != "")) { + $out[] = $prevtext; + } + $prevtext = NULL; + + if($node instanceof TagNode) { + $node->sub = self::tidyup_ast($node->sub); + foreach($node->params as $k => &$v) { + $v = self::tidyup_ast($v); + } + unset($v); + } else { /* VariableNode */ + foreach($node->arrayfields as &$v) { + $v = self::tidyup_ast($v); + } + unset($v); + } + $out[] = $node; } - $first = false; + $wastag = $node instanceof TagNode; } if(($prevtext !== NULL) && ($prevtext->text != "")) { -- cgit v1.2.3-54-g00ecf From 04ee002e30b5451d505ee5abd9c6b97c40da2df0 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Mon, 21 Oct 2013 12:47:41 +0200 Subject: Removed old parse function and added some documentation. --- stupid_template_engine.php | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) (limited to 'stupid_template_engine.php') diff --git a/stupid_template_engine.php b/stupid_template_engine.php index 386f760..78a3eec 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -106,6 +106,11 @@ class RuntimeError extends \Exception {} */ class FatalRuntimeError extends \Exception {} +/* + * Class: Parser + * The class, where the parser lives in. Can not be constructed manually. + * Use the static method parse. + */ class Parser { private $text; private $name; @@ -205,6 +210,22 @@ class Parser { return $name; } + /* + * Function: parse + * Parses the input into an AST. + * + * You only need this function, if you want to manually trnascompile a template. + * + * Parameters: + * $text - The input code. + * $name - The name of the template. + * + * Returns: + * An array of objects. + * + * Throws: + * + */ public static function parse($text, $name) { $obj = new self($text, $name); $res = $obj->parse_text( @@ -525,23 +546,6 @@ class Parser { } } -/* - * Function: parse - * Parsing a STE T/PL template. - * You only need this function, if you want to manually transcompile a template. - * - * Parameters: - * $code - The STE T/PL code. - * $tpl - The name of the template. - * - * Returns: - * An abstract syntax tree, which can be used with . - */ -function parse($code, $tpl) -{ - return mk_ast($code, $tpl, 0); -} - function indent_code($code) { return implode( -- cgit v1.2.3-54-g00ecf From fce36cfcd183a6ca6d86d51b72cf41e43c349432 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Wed, 23 Oct 2013 15:24:15 +0200 Subject: Adding default values to some properties. To reduce the ridiculous amount of notices PHP throws when parsing and executing templates (should really be fixed in the future, this is quite embarassing...). --- stupid_template_engine.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'stupid_template_engine.php') diff --git a/stupid_template_engine.php b/stupid_template_engine.php index 78a3eec..58b327d 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -38,14 +38,18 @@ class TextNode extends ASTNode class TagNode extends ASTNode { public $name; - public $params; - public $sub; + public $params = array(); + public $sub = array(); + public function __construct($tpl, $off, $name = "") { + parent::__construct($tpl, $off); + $this->name = $name; + } } class VariableNode extends ASTNode { public $name; - public $arrayfields; + public $arrayfields = array(); public function transcompile() { $varaccess = '@$ste->vars[' . (is_numeric($this->name) ? $this->name : '"' . escape_text($this->name) . '"'). ']'; -- cgit v1.2.3-54-g00ecf From 80c76caffa50ce7c9a520ff2773d1c98b9100875 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Fri, 25 Oct 2013 00:17:53 +0200 Subject: Better handling of newslines and whitespaces --- stupid_template_engine.php | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to 'stupid_template_engine.php') diff --git a/stupid_template_engine.php b/stupid_template_engine.php index 58b327d..0eef489 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -243,23 +243,26 @@ class Parser { $out = array(); $prevtext = NULL; - $wastag = true; + $first = true; + foreach($ast as $node) { if($node instanceof TextNode) { - if($wastag) { - $node->text = ltrim($node->text); - } - if($prevtext === NULL) { $prevtext = $node; } else { $prevtext->text .= $node->text; } } else { - if(($prevtext !== NULL) && ($prevtext->text != "")) { - $out[] = $prevtext; + if($prevtext !== NULL) { + if($first) { + $prevtext->text = ltrim($prevtext->text); + } + if($prevtext->text != "") { + $out[] = $prevtext; + } } $prevtext = NULL; + $first = false; if($node instanceof TagNode) { $node->sub = self::tidyup_ast($node->sub); @@ -273,15 +276,20 @@ class Parser { } unset($v); } + $out[] = $node; } - - $wastag = $node instanceof TagNode; } - if(($prevtext !== NULL) && ($prevtext->text != "")) { - $out[] = $prevtext; + if($prevtext !== NULL) { + if($first) { + $prevtext->text = ltrim($prevtext->text); + } + if($prevtext->text != "") { + $out[] = $prevtext; + } } + return $out; } -- cgit v1.2.3-54-g00ecf From 2ffa4ae4253276af4c1266b73df3ddaf7ddf0051 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Fri, 25 Oct 2013 01:37:29 +0200 Subject: Fixed potential bug in VariableNode::transcompile Also now PHP doesn't throw notices here. --- stupid_template_engine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'stupid_template_engine.php') diff --git a/stupid_template_engine.php b/stupid_template_engine.php index 0eef489..db9028a 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -55,7 +55,7 @@ class VariableNode extends ASTNode $varaccess = '@$ste->vars[' . (is_numeric($this->name) ? $this->name : '"' . escape_text($this->name) . '"'). ']'; foreach($this->arrayfields as $af) { - if((count($af) == 1) and ($af[0] instanceof TextNode) and is_numeric($af->text)) + if((count($af) == 1) and ($af[0] instanceof TextNode) and is_numeric($af[0]->text)) $varaccess .= '[' . $af->text . ']'; else $varaccess .= '[' . implode(".", -- cgit v1.2.3-54-g00ecf From c3a72422e2b9754b60467387fd3cb660e0979e18 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Fri, 25 Oct 2013 01:40:07 +0200 Subject: Fixed some mistakes in mktag subcompiler * Undeclared $code variable fixed. * "use ($mandatory_params)" only included, if the $mandatory_params variable will be generated. * Fixed $outputstack not being initialized correctly. --- stupid_template_engine.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'stupid_template_engine.php') diff --git a/stupid_template_engine.php b/stupid_template_engine.php index db9028a..2cfb9d0 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -941,15 +941,19 @@ $ste_builtins = array( }, "mktag" => function($ast) { + $code = ""; + if(empty($ast->params["name"])) throw new ParseCompileError("Transcompile Error: name missing in .", $ast->tpl, $ast->offset); $tagname = _transcompile($ast->params["name"], True); - $fxbody = "\$outputstack = array(); \$outputstack_i = 0;\$ste->vars['_tag_parameters'] = \$params;\n"; + $fxbody = "\$outputstack = array(''); \$outputstack_i = 0;\$ste->vars['_tag_parameters'] = \$params;\n"; + $usemandatory = ""; if(!empty($ast->params["mandatory"])) { + $usemandatory = " use (\$mandatory_params)"; $code .= "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->params["mandatory"]); $code .= "\$outputstack_i--;\n\$mandatory_params = explode('|', array_pop(\$outputstack));\n"; @@ -960,7 +964,7 @@ $ste_builtins = array( $fxbody .= _transcompile($ast->sub); $fxbody .= "return array_pop(\$outputstack);"; - $code .= "\$tag_fx = function(\$ste, \$params, \$sub) use (\$mandatory_params)\n{\n" . indent_code($fxbody) . "\n};\n"; + $code .= "\$tag_fx = function(\$ste, \$params, \$sub)" . $usemandatory . "\n{\n" . indent_code($fxbody) . "\n};\n"; $code .= "\$ste->register_tag($tagname, \$tag_fx);\n"; return $code; -- cgit v1.2.3-54-g00ecf From 6dc747c097e096075b4726eb4aebf1ce0ed71276 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Fri, 25 Oct 2013 21:10:32 +0200 Subject: short cmp tags can now contain tags. To accomplish this, tag parameters can now be compiled, even if they contain tags (the parser still doesn't allow this, but the compiler can now handle this situation). Also reformatted the code. --- docu/language_definition.html | 5 +- stupid_template_engine.php | 820 ++++++++++++++++++++---------------------- tests/test_short/comment | 1 - tests/test_short/want | 2 +- 4 files changed, 395 insertions(+), 433 deletions(-) delete mode 100644 tests/test_short/comment (limited to 'stupid_template_engine.php') diff --git a/docu/language_definition.html b/docu/language_definition.html index 77cb680..934042d 100644 --- a/docu/language_definition.html +++ b/docu/language_definition.html @@ -223,7 +223,6 @@

<ste:if>condition<ste:then>then</ste:then><ste:else>else</ste:else></ste:if>

?, {, | and } can be escaped

In this variant, the else part is not optional!

-

WARNING: short if-clauses can not be nested!

ste:cmp

With the ste:cmp tag you can compare two values.

@@ -285,9 +284,7 @@

~{a|operator|b}

This is equivalent to:

<ste:cmp text_a="a" op="operator" text_b="b" />

-

~, {, | and } can be escaped

-

Because this is implemented as a simple substitution, you can only use Text and Variables. And " must be escaped.

-

WARNING: short comparisons can not be nested! They can be inside short if-clauses, but not the other way around!

+

~, {, | and } can be escaped.

ste:not

The ste:not Tag will logically invert its content. If it is an empty text (i.e. false), it will return a non-empty text (i.e. true) and vice versa.

diff --git a/stupid_template_engine.php b/stupid_template_engine.php index 2cfb9d0..cfb5882 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -15,19 +15,16 @@ */ namespace ste; -abstract class ASTNode -{ +abstract class ASTNode { public $tpl; public $offset; - public function __construct($tpl, $off) - { + public function __construct($tpl, $off) { $this->tpl = $tpl; $this->offset = $off; } } -class TextNode extends ASTNode -{ +class TextNode extends ASTNode { public $text; public function __construct($tpl, $off, $text = "") { parent::__construct($tpl, $off); @@ -35,8 +32,7 @@ class TextNode extends ASTNode } } -class TagNode extends ASTNode -{ +class TagNode extends ASTNode { public $name; public $params = array(); public $sub = array(); @@ -46,53 +42,44 @@ class TagNode extends ASTNode } } -class VariableNode extends ASTNode -{ +class VariableNode extends ASTNode { public $name; public $arrayfields = array(); - public function transcompile() - { + public function transcompile() { $varaccess = '@$ste->vars[' . (is_numeric($this->name) ? $this->name : '"' . escape_text($this->name) . '"'). ']'; - foreach($this->arrayfields as $af) - { - if((count($af) == 1) and ($af[0] instanceof TextNode) and is_numeric($af[0]->text)) + foreach($this->arrayfields as $af) { + if((count($af) == 1) and ($af[0] instanceof TextNode) and is_numeric($af[0]->text)) { $varaccess .= '[' . $af->text . ']'; - else - $varaccess .= '[' . implode(".", - array_map( - function($node) - { - if($node instanceof TextNode) - return "\"" . escape_text($node->text) . "\""; - else if($node instanceof VariableNode) - return $node->transcompile(); - }, $af - ) - ). ']'; + } else { + $varaccess .= '[' . implode(".", array_map(function($node) { + if($node instanceof TextNode) { + return "\"" . escape_text($node->text) . "\""; + } else if($node instanceof VariableNode) { + return $node->transcompile(); + } + }, $af)). ']'; + } } return $varaccess; } } -class ParseCompileError extends \Exception -{ +class ParseCompileError extends \Exception { public $msg; public $tpl; public $off; - public function __construct($msg, $tpl, $offset, $code = 0, $previous = NULL) - { + public function __construct($msg, $tpl, $offset, $code = 0, $previous = NULL) { $this->msg = $msg; $this->tpl = $tpl; $this->off = $offset; $this->message = "$msg (Template $tpl, Offset $offset)"; } - public function rewrite($code) - { + public function rewrite($code) { $line = substr_count(str_replace("\r\n", "\n", substr($code, 0, $this->off)), "\n") + 1; $this->message = "{$this->msg} (Template {$this->tpl}, Line $line)"; - $this->is_rewritten = True; + $this->is_rewritten = true; } } @@ -558,20 +545,15 @@ class Parser { } } -function indent_code($code) -{ +function indent_code($code) { return implode( "\n", - array_map( - function($line) { return "\t$line"; }, - explode("\n", $code) - ) + array_map(function($line) { return "\t$line"; }, explode("\n", $code)) ); } /* We could also just eval() the $infix_math code, but this is much cooler :-D (Parser inception) */ -function shunting_yard($infix_math) -{ +function shunting_yard($infix_math) { $operators = array( "+" => array("l", 2), "-" => array("l", 2), @@ -584,165 +566,156 @@ function shunting_yard($infix_math) ); preg_match_all("/\s*(?:(?:[+\\-\\*\\/\\^\\(\\)])|(\\d*[\\.]?\\d*))\\s*/s", $infix_math, $tokens, PREG_PATTERN_ORDER); - $tokens_raw = array_filter(array_map('trim', $tokens[0]), function($x) { return ($x === "0") or (!empty($x)); }); + $tokens_raw = array_filter(array_map('trim', $tokens[0]), function($x) { return ($x === "0") || (!empty($x)); }); $output_queue = array(); $op_stack = array(); $lastpriority = NULL; /* Make - unary, if neccessary */ $tokens = array(); - foreach($tokens_raw as $token) - { + foreach($tokens_raw as $token) { $priority = isset($operators[$token]) ? $operators[$token][1] : -1; - if(($token == "-") and (($lastpriority === NULL) or ($lastpriority >= 0))) - { + if(($token == "-") && (($lastpriority === NULL) || ($lastpriority >= 0))) { $priority = $operators["_"][1]; $tokens[] = "_"; - } - else + } else { $tokens[] = $token; + } $lastpriority = $priority; } - while(!empty($tokens)) - { + while(!empty($tokens)) { $token = array_shift($tokens); - if(is_numeric($token)) + if(is_numeric($token)) { $output_queue[] = $token; - else if($token == "(") + } else if($token == "(") { $op_stack[] = $token; - else if($token == ")") - { - $lbr_found = False; - while(!empty($op_stack)) - { + } else if($token == ")") { + $lbr_found = false; + while(!empty($op_stack)) { $op = array_pop($op_stack); - if($op == "(") - { - $lbr_found = True; + if($op == "(") { + $lbr_found = true; break; } $output_queue[] = $op; } - if(!$lbr_found) + if(!$lbr_found) { throw new RuntimeError("Bracket mismatch."); - } - else if(!isset($operators[$token])) + } + } else if(!isset($operators[$token])) { throw new RuntimeError("Invalid token ($token): Not a number, bracket or operator. Stop."); - else - { + } else { $priority = $operators[$token][1]; - if($operators[$token][0] == "l") - while((!empty($op_stack)) and ($priority <= $operators[$op_stack[count($op_stack)-1]][1])) + if($operators[$token][0] == "l") { + while((!empty($op_stack)) and ($priority <= $operators[$op_stack[count($op_stack)-1]][1])) { $output_queue[] = array_pop($op_stack); - else - while((!empty($op_stack)) and ($priority < $operators[$op_stack[count($op_stack)-1]][1])) + } + } else { + while((!empty($op_stack)) and ($priority < $operators[$op_stack[count($op_stack)-1]][1])) { $output_queue[] = array_pop($op_stack); + } + } $op_stack[] = $token; } } - while(!empty($op_stack)) - { + while(!empty($op_stack)) { $op = array_pop($op_stack); - if($op == "(") + if($op == "(") { throw new RuntimeError("Bracket mismatch..."); + } $output_queue[] = $op; } return $output_queue; } -function pop2(&$array) -{ +function pop2(&$array) { $rv = array(array_pop($array), array_pop($array)); - if(array_search(NULL, $rv, True) !== False) + if(array_search(NULL, $rv, true) !== false) { throw new RuntimeError("Not enough numbers on stack. Invalid formula."); + } return $rv; } -function calc_rpn($rpn) -{ +function calc_rpn($rpn) { $stack = array(); - foreach($rpn as $token) - { - switch($token) - { - case "+": - list($b, $a) = pop2($stack); - $stack[] = $a + $b; - break; - case "-": - list($b, $a) = pop2($stack); - $stack[] = $a - $b; - break; - case "*": - list($b, $a) = pop2($stack); - $stack[] = $a * $b; - break; - case "/": - list($b, $a) = pop2($stack); - $stack[] = $a / $b; - break; - case "^": - list($b, $a) = pop2($stack); - $stack[] = pow($a, $b); - break; - case "_": - $a = array_pop($stack); - if($a === NULL) - throw new RuntimeError("Not enough numbers on stack. Invalid formula."); - $stack[] = -$a; - break; - default: - $stack[] = $token; - break; + foreach($rpn as $token) { + switch($token) { + case "+": + list($b, $a) = pop2($stack); + $stack[] = $a + $b; + break; + case "-": + list($b, $a) = pop2($stack); + $stack[] = $a - $b; + break; + case "*": + list($b, $a) = pop2($stack); + $stack[] = $a * $b; + break; + case "/": + list($b, $a) = pop2($stack); + $stack[] = $a / $b; + break; + case "^": + list($b, $a) = pop2($stack); + $stack[] = pow($a, $b); + break; + case "_": + $a = array_pop($stack); + if($a === NULL) { + throw new RuntimeError("Not enough numbers on stack. Invalid formula."); + } + $stack[] = -$a; + break; + default: + $stack[] = $token; + break; } } return array_pop($stack); } -function loopbody($code) -{ +function loopbody($code) { return "try\n{\n" . indent_code($code) . "\n}\ncatch(\ste\BreakException \$e) { break; }\ncatch(\ste\ContinueException \$e) { continue; }\n"; } $ste_builtins = array( - "if" => function($ast) - { + "if" => function($ast) { $output = ""; $condition = array(); $then = NULL; $else = NULL; - foreach($ast->sub as $node) - { - if(($node instanceof TagNode) and ($node->name == "then")) + foreach($ast->sub as $node) { + if(($node instanceof TagNode) and ($node->name == "then")) { $then = $node->sub; - else if(($node instanceof TagNode) and ($node->name == "else")) + } else if(($node instanceof TagNode) and ($node->name == "else")) { $else = $node->sub; - else + } else { $condition[] = $node; + } } - if($then === NULL) + if($then === NULL) { throw new ParseCompileError("Transcompile error: Missing in .", $ast->tpl, $ast->offset); + } $output .= "\$outputstack[] = \"\";\n\$outputstack_i++;\n"; $output .= _transcompile($condition); $output .= "\$outputstack_i--;\nif(\$ste->evalbool(array_pop(\$outputstack)))\n{\n"; $output .= indent_code(_transcompile($then)); $output .= "\n}\n"; - if($else !== NULL) - { + if($else !== NULL) { $output .= "else\n{\n"; $output .= indent_code(_transcompile($else)); $output .= "\n}\n"; } return $output; }, - "cmp" => function($ast) - { + "cmp" => function($ast) { $operators = array( array('eq', '=='), array('neq', '!='), @@ -754,42 +727,48 @@ $ste_builtins = array( $code = ""; - if(isset($ast->params["var_b"])) - $b = '$ste->get_var_by_name(' . _transcompile($ast->params["var_b"], True) . ')'; - else if(isset($ast->params["text_b"])) - $b = _transcompile($ast->params["text_b"], True); - else + if(isset($ast->params["var_b"])) { + list($val, $pre) = _transcompile($ast->params["var_b"], true); + $code .= $pre; + $b = '$ste->get_var_by_name(' . $val . ')'; + } else if(isset($ast->params["text_b"])) { + list($b, $pre) = _transcompile($ast->params["text_b"], true); + $code .= $pre; + } else { throw new ParseCompileError("Transcompile error: neiter var_b nor text_b set in .", $ast->tpl, $ast->offset); + } - if(isset($ast->params["var_a"])) - $a = '$ste->get_var_by_name(' . _transcompile($ast->params["var_a"], True) . ')'; - else if(isset($ast->params["text_a"])) - $a = _transcompile($ast->params["text_a"], True); - else + if(isset($ast->params["var_a"])) { + list($val, $pre) = _transcompile($ast->params["var_a"], true); + $code .= $pre; + $a = '$ste->get_var_by_name(' . $val . ')'; + } else if(isset($ast->params["text_a"])) { + list($a, $pre) = _transcompile($ast->params["text_a"], true); + $code .= $pre; + } else { throw new ParseCompileError("Transcompile error: neiter var_a nor text_a set in .", $ast->tpl, $ast->offset); + } - if(!isset($ast->params["op"])) + if(!isset($ast->params["op"])) { throw new ParseCompileError("Transcompile error: op not given in .", $ast->tpl, $ast->offset); - if((count($ast->params["op"]) == 1) and ($ast->params["op"][0] instanceof TextNode)) - { + } + if((count($ast->params["op"]) == 1) and ($ast->params["op"][0] instanceof TextNode)) { /* Operator is known at compile time, this saves *a lot* of output code! */ $op = trim($ast->params["op"][0]->text); $op_php = NULL; - foreach($operators as $v) - { - if($v[0] == $op) - { + foreach($operators as $v) { + if($v[0] == $op) { $op_php = $v[1]; break; } } - if($op_php === NULL) + if($op_php === NULL) { throw new ParseCompileError("Transcompile Error: Unknown operator in ", $ast->tpl, $ast->offset); + } $code .= "\$outputstack[\$outputstack_i] .= (($a) $op_php ($b)) ? 'yes' : '';\n"; - } - else - { - $code .= "switch(trim(" . _transcompile($ast->params["op"], True) . "))\n{\n\t"; + } else { + list($val, $pre) = _transcompile($ast->params["op"], true); + $code .= $pre . "switch(trim(" . $val . "))\n{\n\t"; $code .= implode("", array_map( function($op) use ($a,$b) { @@ -801,49 +780,57 @@ $ste_builtins = array( } return $code; }, - "not" => function($ast) - { + "not" => function($ast) { $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->sub); $code .= "\$outputstack_i--;\n\$outputstack[\$outputstack_i] .= (!\$ste->evalbool(array_pop(\$outputstack))) ? 'yes' : '';\n"; return $code; }, - "even" => function($ast) - { + "even" => function($ast) { $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->sub); $code .= "\$outputstack_i--;\n\$tmp_even = array_pop(\$outputstack);\n\$outputstack[\$outputstack_i] .= (is_numeric(\$tmp_even) and (\$tmp_even % 2 == 0)) ? 'yes' : '';\n"; return $code; }, - "for" => function($ast) - { + "for" => function($ast) { $code = ""; - $loopname = "forloop_" . str_replace(".", "_", uniqid("",True)); - if(empty($ast->params["start"])) + $loopname = "forloop_" . str_replace(".", "_", uniqid("",true)); + if(empty($ast->params["start"])) { throw new ParseCompileError("Transcompile error: Missing 'start' parameter in .", $ast->tpl, $ast->offset); - $code .= "\$${loopname}_start = " . _transcompile($ast->params["start"], True) . ";\n"; + } + list($val, $pre) = _transcompile($ast->params["start"], true); + $code .= $pre; + $code .= "\$${loopname}_start = " . $val . ";\n"; - if(empty($ast->params["stop"])) + if(empty($ast->params["stop"])) { throw new ParseCompileError("Transcompile error: Missing 'end' parameter in .", $ast->tpl, $ast->offset); - $code .= "\$${loopname}_stop = " . _transcompile($ast->params["stop"], True) . ";\n"; + } + list($val, $pre) = _transcompile($ast->params["stop"], true); + $code .= $pre; + $code .= "\$${loopname}_stop = " . $val . ";\n"; $step = NULL; /* i.e. not known at compilation time */ - if(empty($ast->params["step"])) + if(empty($ast->params["step"])) { $step = 1; - else if((count($ast->params["step"]) == 1) and ($ast->params["step"][0] instanceof TextNode)) + } else if((count($ast->params["step"]) == 1) and ($ast->params["step"][0] instanceof TextNode)) { $step = $ast->params["step"][0]->text + 0; - else - $code .= "\$${loopname}_step = " . _transcompile($ast->params["step"], True) . ";\n"; + } else { + list($val, $pre) = _transcompile($ast->params["step"], true); + $code .= $pre; + $code .= "\$${loopname}_step = " . $val . ";\n"; + } - if(!empty($ast->params["counter"])) - $code .= "\$${loopname}_countername = " . _transcompile($ast->params["counter"], True) . ";\n"; + if(!empty($ast->params["counter"])) { + list($val, $pre) = _transcompile($ast->params["counter"], true); + $code .= $pre; + $code .= "\$${loopname}_countername = " . $val . ";\n"; + } $loopbody = empty($ast->params["counter"]) ? "" : "\$ste->set_var_by_name(\$${loopname}_countername, \$${loopname}_counter);\n"; $loopbody .= _transcompile($ast->sub); $loopbody = indent_code("{\n" . loopbody(indent_code($loopbody)) . "\n}\n"); - if($step === NULL) - { + if($step === NULL) { $code .= "if(\$${loopname}_step == 0)\n\tthrow new \\ste\\RuntimeError('step can not be 0 in .');\n"; $code .= "if(\$${loopname}_step > 0)\n{\n"; $code .= "\tfor(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter <= \$${loopname}_stop; \$${loopname}_counter += \$${loopname}_step)\n"; @@ -852,47 +839,58 @@ $ste_builtins = array( $code .= "\tfor(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter >= \$${loopname}_stop; \$${loopname}_counter += \$${loopname}_step)\n"; $code .= $loopbody; $code .= "\n}\n"; - } - else if($step == 0) + } else if($step == 0) { throw new ParseCompileError("Transcompile Error: step can not be 0 in .", $ast->tpl, $ast->offset); - else if($step > 0) + } else if($step > 0) { $code .= "for(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter <= \$${loopname}_stop; \$${loopname}_counter += $step)\n$loopbody\n"; - else + } else { $code .= "for(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter >= \$${loopname}_stop; \$${loopname}_counter += $step)\n$loopbody\n"; + } return $code; }, - "foreach" => function($ast) - { - $loopname = "foreachloop_" . str_replace(".", "_", uniqid("",True)); + "foreach" => function($ast) { + $loopname = "foreachloop_" . str_replace(".", "_", uniqid("",true)); $code = ""; - if(empty($ast->params["array"])) + if(empty($ast->params["array"])) { throw new ParseCompileError("Transcompile Error: array not given in .", $ast->tpl, $ast->offset); - $code .= "\$${loopname}_arrayvar = " . _transcompile($ast->params["array"], True) . ";\n"; + } + list($val, $pre) = _transcompile($ast->params["array"], true); + $code .= $pre; + $code .= "\$${loopname}_arrayvar = " . $val . ";\n"; - if(empty($ast->params["value"])) + if(empty($ast->params["value"])) { throw new ParseCompileError("Transcompile Error: value not given in .", $ast->tpl, $ast->offset); - $code .= "\$${loopname}_valuevar = " . _transcompile($ast->params["value"], True) . ";\n"; - - if(!empty($ast->params["key"])) - $code .= "\$${loopname}_keyvar = " . _transcompile($ast->params["key"], True) . ";\n"; + } + list($val, $pre) = _transcompile($ast->params["value"], true); + $code .= $pre; + $code .= "\$${loopname}_valuevar = " . $val . ";\n"; + + if(!empty($ast->params["key"])) { + list($val, $pre) = _transcompile($ast->params["key"], true); + $code .= $pre; + $code .= "\$${loopname}_keyvar = " . $val . ";\n"; + } - if(!empty($ast->params["counter"])) - $code .= "\$${loopname}_countervar = " . _transcompile($ast->params["counter"], True) . ";\n"; + if(!empty($ast->params["counter"])) { + list($val, $pre) = _transcompile($ast->params["counter"], true); + $code .= $pre; + $code .= "\$${loopname}_countervar = " . $val . ";\n"; + } $loopbody = ""; $code .= "\$${loopname}_array = \$ste->get_var_by_name(\$${loopname}_arrayvar);\n"; $code .= "if(!is_array(\$${loopname}_array))\n\t\$${loopname}_array = array();\n"; - if(!empty($ast->params["counter"])) - { + if(!empty($ast->params["counter"])) { $code .= "\$${loopname}_counter = -1;\n"; $loopbody .= "\$${loopname}_counter++;\n\$ste->set_var_by_name(\$${loopname}_countervar, \$${loopname}_counter);\n"; } $loopbody .= "\$ste->set_var_by_name(\$${loopname}_valuevar, \$${loopname}_value);\n"; - if(!empty($ast->params["key"])) + if(!empty($ast->params["key"])) { $loopbody .= "\$ste->set_var_by_name(\$${loopname}_keyvar, \$${loopname}_key);\n"; + } $loopbody .= "\n"; $loopbody .= _transcompile($ast->sub); $loopbody = "{\n" . loopbody(indent_code($loopbody)) . "\n}\n"; @@ -901,58 +899,57 @@ $ste_builtins = array( return $code; }, - "infloop" => function($ast) - { - return "while(True)\n{\n" . indent_code(loopbody(indent_code(_transcompile($ast->sub)))) . "\n}\n"; + "infloop" => function($ast) { + return "while(true)\n{\n" . indent_code(loopbody(indent_code(_transcompile($ast->sub)))) . "\n}\n"; }, - "break" => function($ast) - { + "break" => function($ast) { return "throw new \\ste\\BreakException();\n"; }, - "continue" => function($ast) - { + "continue" => function($ast) { return "throw new \\ste\\ContinueException();\n"; }, - "block" => function($ast) - { - if(empty($ast->params["name"])) + "block" => function($ast) { + if(empty($ast->params["name"])) { throw new ParseCompileError("Transcompile Error: name missing in .", $ast->tpl, $ast->offset); + } - $blknamevar = "blockname_" . str_replace(".", "_", uniqid("", True)); + $blknamevar = "blockname_" . str_replace(".", "_", uniqid("", true)); - $code = "\$${blknamevar} = " . _transcompile($ast->params["name"], True) . ";\n"; + list($val, $code) = _transcompile($ast->params["name"], true); + $code .= "\$${blknamevar} = " . $val . ";\n"; - $tmpblk = uniqid("", True); + $tmpblk = uniqid("", true); $code .= "\$ste->blocks['$tmpblk'] = array_pop(\$outputstack);\n\$ste->blockorder[] = '$tmpblk';\n\$outputstack = array('');\n\$outputstack_i = 0;\n"; $code .= _transcompile($ast->sub); $code .= "\$ste->blocks[\$${blknamevar}] = array_pop(\$outputstack);\n"; - $code .= "if(array_search(\$${blknamevar}, \$ste->blockorder) === FALSE)\n\t\$ste->blockorder[] = \$${blknamevar};\n\$outputstack = array('');\n\$outputstack_i = 0;\n"; + $code .= "if(array_search(\$${blknamevar}, \$ste->blockorder) === false)\n\t\$ste->blockorder[] = \$${blknamevar};\n\$outputstack = array('');\n\$outputstack_i = 0;\n"; return $code; }, - "load" => function($ast) - { - if(empty($ast->params["name"])) + "load" => function($ast) { + if(empty($ast->params["name"])) { throw new ParseCompileError("Transcompile Error: name missing in .", $ast->tpl, $ast->offset); + } - return "\$outputstack[\$outputstack_i] .= \$ste->load(" . _transcompile($ast->params["name"], True) . ");\n"; + list($val, $code) = _transcompile($ast->params["name"], true); + $code .= "\$outputstack[\$outputstack_i] .= \$ste->load(" . $val . ");\n"; + return $code; }, - "mktag" => function($ast) - { + "mktag" => function($ast) { $code = ""; - if(empty($ast->params["name"])) + if(empty($ast->params["name"])) { throw new ParseCompileError("Transcompile Error: name missing in .", $ast->tpl, $ast->offset); - - $tagname = _transcompile($ast->params["name"], True); + } $fxbody = "\$outputstack = array(''); \$outputstack_i = 0;\$ste->vars['_tag_parameters'] = \$params;\n"; + list($tagname, $tagname_pre) = _transcompile($ast->params["name"], true); + $usemandatory = ""; - if(!empty($ast->params["mandatory"])) - { + if(!empty($ast->params["mandatory"])) { $usemandatory = " use (\$mandatory_params)"; $code .= "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->params["mandatory"]); @@ -965,36 +962,39 @@ $ste_builtins = array( $fxbody .= "return array_pop(\$outputstack);"; $code .= "\$tag_fx = function(\$ste, \$params, \$sub)" . $usemandatory . "\n{\n" . indent_code($fxbody) . "\n};\n"; + $code .= $tagname_pre; $code .= "\$ste->register_tag($tagname, \$tag_fx);\n"; return $code; }, - "tagcontent" => function($ast) - { + "tagcontent" => function($ast) { return "\$outputstack[\$outputstack_i] .= \$sub(\$ste);"; }, - "set" => function($ast) - { - if(empty($ast->params["var"])) + "set" => function($ast) { + if(empty($ast->params["var"])) { throw new ParseCompileError("Transcompile Error: var missing in .", $ast->tpl, $ast->offset); + } $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->sub); $code .= "\$outputstack_i--;\n"; - $code .= "\$ste->set_var_by_name(" . _transcompile($ast->params["var"], True) . ", array_pop(\$outputstack));\n"; + list($val, $pre) = _transcompile($ast->params["var"], true); + $code .= $pre; + $code .= "\$ste->set_var_by_name(" . $val . ", array_pop(\$outputstack));\n"; return $code; }, - "get" => function($ast) - { - if(empty($ast->params["var"])) + "get" => function($ast) { + if(empty($ast->params["var"])) { throw new ParseCompileError("Transcompile Error: var missing in .", $ast->tpl, $ast->offset); + } - return "\$outputstack[\$outputstack_i] .= \$ste->get_var_by_name(" . _transcompile($ast->params["var"], True) . ");"; + list($val, $pre) = _transcompile($ast->params["var"], true); + $code .= $pre; + return "\$outputstack[\$outputstack_i] .= \$ste->get_var_by_name(" . $val . ");"; }, - "calc" => function($ast) - { + "calc" => function($ast) { $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->sub); $code .= "\$outputstack_i--;\n\$outputstack[\$outputstack_i] .= \$ste->calc(array_pop(\$outputstack));\n"; @@ -1003,40 +1003,36 @@ $ste_builtins = array( } ); -function escape_text($text) -{ +function escape_text($text) { return addcslashes($text, "\r\n\t\$\0..\x1f\\\"\x7f..\xff"); } -function _transcompile($ast, $no_outputstack = False) /* The real transcompile function, does not add boilerplate code. */ -{ +function _transcompile($ast, $avoid_outputstack = false) { /* The real transcompile function, does not add boilerplate code. */ $code = ""; global $ste_builtins; $text_and_var_buffer = array(); - foreach($ast as $node) - { - if($node instanceof TextNode) + foreach($ast as $node) { + if($node instanceof TextNode) { $text_and_var_buffer[] = '"' . escape_text($node->text) . '"'; - else if($node instanceof VariableNode) + } else if($node instanceof VariableNode) { $text_and_var_buffer[] = $node->transcompile(); - else if($node instanceof TagNode) - { - if(!empty($text_and_var_buffer)) - { + } else if($node instanceof TagNode) { + if(!empty($text_and_var_buffer)) { $code .= "\$outputstack[\$outputstack_i] .= " . implode (" . ", $text_and_var_buffer) . ";\n"; $text_and_var_buffer = array(); } - if(isset($ste_builtins[$node->name])) + if(isset($ste_builtins[$node->name])) { $code .= $ste_builtins[$node->name]($node); - else - { - $paramarray = "parameters_" . str_replace(".", "_", uniqid("", True)); + } else { + $paramarray = "parameters_" . str_replace(".", "_", uniqid("", true)); $code .= "\$$paramarray = array();\n"; - foreach($node->params as $pname => $pcontent) - $code .= "\$${paramarray}['" . escape_text($pname) . "'] = " . _transcompile($pcontent, True) . ";\n"; + foreach($node->params as $pname => $pcontent) { + list($pval, $pre) = _transcompile($pcontent, true); + $code .= $pre . "\$${paramarray}['" . escape_text($pname) . "'] = " . $pval . ";\n"; + } $code .= "\$outputstack[\$outputstack_i] .= \$ste->call_tag('" . escape_text($node->name) . "', \$${paramarray}, "; $code .= empty($node->sub) ? "function(\$ste) { return ''; }" : transcompile($node->sub); @@ -1045,18 +1041,19 @@ function _transcompile($ast, $no_outputstack = False) /* The real transcompile f } } - if(!empty($text_and_var_buffer)) - { - if(!$no_outputstack) - $code .= "\$outputstack[\$outputstack_i] .= "; - $code .= implode (" . ", $text_and_var_buffer); - if(!$no_outputstack) - $code .= ";\n"; - $text_and_var_buffer = array(); + if($avoid_outputstack && ($code == "")) { + return array(implode (" . ", $text_and_var_buffer), ""); } - else if($no_outputstack) - { - $code = "\"\""; + + if(!empty($text_and_var_buffer)) { + $code .= "\$outputstack[\$outputstack_i] .= ". implode (" . ", $text_and_var_buffer) . ";\n"; + } + + if($avoid_outputstack) { + $tmpvar = "tmp_" . str_replace(".", "_", uniqid("",true)); + $code = "\$outputstack[] = '';\n\$outputstack_i++;" . $code; + $code .= "\$$tmpvar = array_pop(\$outputstack);\n\$outputstack_i--;\n"; + return array("\$$tmpvar", $code); } return $code; @@ -1075,8 +1072,7 @@ $ste_transc_boilerplate = "\$outputstack = array('');\n\$outputstack_i = 0;\n"; * Returns: * PHP code. The PHP code is an anonymous function expecting a instance as its parameter and returns a string (everything that was not pached into a section). */ -function transcompile($ast) /* Transcompile and add some boilerplate code. */ -{ +function transcompile($ast) { /* Transcompile and add some boilerplate code. */ global $ste_transc_boilerplate; return "function(\$ste)\n{\n" . indent_code($ste_transc_boilerplate . _transcompile($ast) . "return array_pop(\$outputstack);") . "\n}"; } @@ -1110,8 +1106,7 @@ class CantSaveTemplate extends StorageAccessFailure { } * A StorageAccess implementation is used to access the templates from any storage. * This means, that you are not limited to store the Templates inside directories, you can also use a database or something else. */ -interface StorageAccess -{ +interface StorageAccess { /* * Function: load * Loading a template. @@ -1149,8 +1144,7 @@ interface StorageAccess * Class: FilesystemStorageAccess * The default implementation for loading / saving templates into a directory structure. */ -class FilesystemStorageAccess implements StorageAccess -{ +class FilesystemStorageAccess implements StorageAccess { protected $sourcedir; protected $transcompileddir; @@ -1161,62 +1155,51 @@ class FilesystemStorageAccess implements StorageAccess * $src - The directory with the sources (Writing permissions are not mandatory, because STE does not save template sources). * $transc - The directory with the transcompiled templates (the PHP instance / the HTTP Server needs writing permissions to this directory). */ - public function __construct($src, $transc) - { + public function __construct($src, $transc) { $this->sourcedir = $src; $this->transcompileddir = $transc; } - public function load($tpl, &$mode) - { + public function load($tpl, &$mode) { $src_fn = $this->sourcedir . "/" . $tpl; $transc_fn = $this->transcompileddir . "/" . $tpl . ".php"; - if($mode == MODE_SOURCE) - { + if($mode == MODE_SOURCE) { $content = @file_get_contents($src_fn); - if($content === False) + if($content === false) { throw new CantLoadTemplate("Template not found."); + } return $content; } $src_stat = @stat($src_fn); $transc_stat = @stat($transc_fn); - if(($src_stat === False) and ($transc_stat === False)) + if(($src_stat === false) and ($transc_stat === false)) { throw new CantLoadTemplate("Template not found."); - else if($transc_stat === False) - { + } else if($transc_stat === false) { $mode = MODE_SOURCE; return file_get_contents($src_fn); - } - else if($src_stat === False) - { + } else if($src_stat === false) { include($transc_fn); return $transcompile_fx; - } - else - { - if($src_stat["mtime"] > $transc_stat["mtime"]) - { + } else { + if($src_stat["mtime"] > $transc_stat["mtime"]) { $mode = MODE_SOURCE; return file_get_contents($src_fn); - } - else - { + } else { include($transc_fn); return $transcompile_fx; } } } - public function save($tpl, $data, $mode) - { + public function save($tpl, $data, $mode) { $fn = (($mode == MODE_SOURCE) ? $this->sourcedir : $this->transcompileddir) . "/" . $tpl . (($mode == MODE_TRANSCOMPILED) ? ".php" : ""); - @mkdir(dirname($fn), 0777, True); - if(file_put_contents($fn, "") === False) + @mkdir(dirname($fn), 0777, true); + if(file_put_contents($fn, "") === false) { throw new CantSaveTemplate("Unable to save template."); - + } } } @@ -1227,8 +1210,7 @@ class ContinueException extends \Exception { } * Class: STECore * The Core of STE */ -class STECore -{ +class STECore { private $tags; private $storage_access; private $cur_tpl_dir; @@ -1239,14 +1221,14 @@ class STECore * $blocks - Associative array of blocks (see the language definition). * $blockorder - The order of the blocks (an array) * $vars - Associative array of all template variables. Use this to pass data to your templates. - * $mute_runtime_errors - If True (default) a exception will result in no output from the tag, if False a error message will be written to output. - * $fatal_error_on_missing_tag - If True, STE will throw a if a tag was called that was not registered, otherwise (default) a regular will be thrown and automatically handled by STE (see <$mute_runtime_errors>). + * $mute_runtime_errors - If true (default) a exception will result in no output from the tag, if false a error message will be written to output. + * $fatal_error_on_missing_tag - If true, STE will throw a if a tag was called that was not registered, otherwise (default) a regular will be thrown and automatically handled by STE (see <$mute_runtime_errors>). */ public $blocks; public $blockorder; public $vars; - public $mute_runtime_errors = True; - public $fatal_error_on_missing_tag = False; + public $mute_runtime_errors = true; + public $fatal_error_on_missing_tag = false; /* * Constructor: __construct @@ -1254,8 +1236,7 @@ class STECore * Parameters: * $storage_access - An Instance of a implementation. */ - public function __construct($storage_access) - { + public function __construct($storage_access) { $this->storage_access = $storage_access; $this->cur_tpl_dir = "/"; STEStandardLibrary::_register_lib($this); @@ -1275,12 +1256,13 @@ class STECore * Throws: * An Exception if the tag could not be registered (if $callback is not callable or if $name is empty) */ - public function register_tag($name, $callback) - { - if(!is_callable($callback)) + public function register_tag($name, $callback) { + if(!is_callable($callback)) { throw new \Exception("Can not register tag \"$name\", not callable."); - if(empty($name)) + } + if(empty($name)) { throw new \Exception("Can not register tag, empty name."); + } $this->tags[$name] = $callback; } @@ -1299,28 +1281,24 @@ class STECore * Returns: * The output of the tag or, if a was thrown, the appropiate result (see <$mute_runtime_errors>). */ - public function call_tag($name, $params, $sub) - { - try - { - if(!isset($this->tags[$name])) - { - if($this->fatal_error_on_missing_tag) + public function call_tag($name, $params, $sub) { + try { + if(!isset($this->tags[$name])) { + if($this->fatal_error_on_missing_tag) { throw new FatalRuntimeError("Can not call tag \"$name\": Does not exist."); - else + } else { throw new RuntimeError("Can not call tag \"$name\": Does not exist."); + } } return call_user_func($this->tags[$name], $this, $params, $sub); - } - catch(RuntimeError $e) - { - if(!$this->mute_runtime_errors) + } catch(RuntimeError $e) { + if(!$this->mute_runtime_errors) { return "RuntimeError occurred on tag '$name': " . $e->getMessage(); + } } } - public function calc($expression) - { + public function calc($expression) { return calc_rpn(shunting_yard($expression)); } @@ -1340,13 +1318,13 @@ class STECore * Returns: * The output of the template. */ - public function exectemplate($tpl) - { + public function exectemplate($tpl) { $output = ""; $lastblock = $this->load($tpl); - foreach($this->blockorder as $blockname) + foreach($this->blockorder as $blockname) { $output .= $this->blocks[$blockname]; + } return $output . $lastblock; } @@ -1367,47 +1345,39 @@ class STECore * A Reference to the variable. */ - public function &get_var_reference($name, $create_if_not_exist) - { + public function &get_var_reference($name, $create_if_not_exist) { $ref = &$this->_get_var_reference($this->vars, $name, $create_if_not_exist); return $ref; } - private function &_get_var_reference(&$from, $name, $create_if_not_exist) - { + private function &_get_var_reference(&$from, $name, $create_if_not_exist) { $bracket_open = strpos($name, "["); - if($bracket_open === False) - { - if(isset($from[$name]) or $create_if_not_exist) - { + if($bracket_open === false) { + if(isset($from[$name]) or $create_if_not_exist) { $ref = &$from[$name]; return $ref; - } - else + } else { return NULL; - } - else - { + } + } else { $old_varname = $varname; $bracket_close = strpos($name, "]", $bracket_open); - if($bracket_close === FALSE) + if($bracket_close === false) { throw new RuntimeError("Invalid varname \"$varname\". Missing closing \"]\"."); + } $varname = substr($name, 0, $bracket_open); $name = substr($name, $bracket_open + 1, $bracket_close - $bracket_open - 1) . substr($name, $bracket_close + 1); - if(!is_array($from[$varname])) - { - if($create_if_not_exist) + if(!is_array($from[$varname])) { + if($create_if_not_exist) { $from[$varname] = array(); - else + } else { return NULL; + } } - try - { + try { $ref = &$this->_get_var_reference($from[$varname], $name, $create_if_not_exist); return $ref; - } - catch(Exception $e) - { + } catch(Exception $e) { throw new RuntimeError("Invalid varname \"$old_varname\". Missing closing \"]\"."); } } @@ -1425,9 +1395,8 @@ class STECore * Throws: * if the variable name can not be parsed (e.g. unbalanced brackets). */ - public function set_var_by_name($name, $val) - { - $ref = &$this->_get_var_reference($this->vars, $name, True); + public function set_var_by_name($name, $val) { + $ref = &$this->_get_var_reference($this->vars, $name, true); $ref = $val; } @@ -1445,9 +1414,8 @@ class STECore * Returns: * The variables value. */ - public function get_var_by_name($name) - { - $ref = $this->_get_var_reference($this->vars, $name, False); + public function get_var_by_name($name) { + $ref = $this->_get_var_reference($this->vars, $name, false); return $ref === NULL ? "" : $ref; } @@ -1468,43 +1436,38 @@ class STECore * Returns: * The result of the template (if $quiet == false). */ - public function load($tpl, $quiet=False) - { + public function load($tpl, $quiet = false) { $tpldir_b4 = $this->cur_tpl_dir; /* Resolve ".", ".." and protect from possible LFI */ $tpl = str_replace("\\", "/", $tpl); - if($tpl[0] != "/") + if($tpl[0] != "/") { $tpl = $this->cur_tpl_dir . "/" . $tpl; + } $pathex = array_filter(explode("/", $tpl), function($s) { return ($s != ".") and (!empty($s)); }); $pathex = array_merge($pathex); - while(($i = array_search("..", $pathex)) !== False) - { - if($i == 0) + while(($i = array_search("..", $pathex)) !== false) { + if($i == 0) { $pathex = array_slice($pathex, 1); - else + } else { $pathex = array_merge(array_slice($pathex, 0, $i), array_slice($pathex, $i + 2)); + } } $tpl = implode("/", $pathex); $this->cur_tpl_dir = dirname($tpl); - if($quiet) - { + if($quiet) { $blocks_back = clone $this->blocks; $blockorder_back = clone $this->blockorder; } $mode = MODE_TRANSCOMPILED; $content = $this->storage_access->load($tpl, $mode); - if($mode == MODE_SOURCE) - { - try - { + if($mode == MODE_SOURCE) { + try { $ast = Parser::parse($content, $tpl); $transc = transcompile($ast); - } - catch(ParseCompileError $e) - { + } catch(ParseCompileError $e) { $e->rewrite($content); throw $e; } @@ -1516,13 +1479,12 @@ class STECore $this->cur_tpl_dir = $tpldir_b4; - if($quiet) - { + if($quiet) { $this->blocks = $blocks_back; $this->blockorder = $blockorder_back; - } - else + } else { return $output; + } } /* @@ -1535,145 +1497,149 @@ class STECore * Returns: * true/false. */ - public function evalbool($txt) - { + public function evalbool($txt) { return trim($txt . "") != ""; } } -class STEStandardLibrary -{ - static public function _register_lib($ste) - { - foreach(get_class_methods(__CLASS__) as $method) - if($method[0] != "_") +class STEStandardLibrary { + static public function _register_lib($ste) { + foreach(get_class_methods(__CLASS__) as $method) { + if($method[0] != "_") { $ste->register_tag($method, array(__CLASS__, $method)); + } + } } - static public function escape($ste, $params, $sub) - { - if($ste->evalbool($params["lines"])) + static public function escape($ste, $params, $sub) { + if($ste->evalbool($params["lines"])) { return nl2br(htmlspecialchars(str_replace("\r\n", "\n", $sub($ste)))); - else + } else { return htmlspecialchars($sub($ste)); + } } - static public function strlen($ste, $params, $sub) - { + static public function strlen($ste, $params, $sub) { return strlen($sub($ste)); } - static public function arraylen($ste, $params, $sub) - { - if(empty($params["array"])) + static public function arraylen($ste, $params, $sub) { + if(empty($params["array"])) { throw new RuntimeError("Missing array parameter in ."); - $a = $ste->get_var_by_name($params["array"], False); + } + $a = $ste->get_var_by_name($params["array"], false); return (is_array($a)) ? count($a) : ""; } - static public function inc($ste, $params, $sub) - { - if(empty($params["var"])) + static public function inc($ste, $params, $sub) { + if(empty($params["var"])) { throw new RuntimeError("Missing var parameter in ."); - $ref = &$ste->get_var_reference($params["var"], True); + } + $ref = &$ste->get_var_reference($params["var"], true); $ref++; } - static public function dec($ste, $params, $sub) - { - if(empty($params["var"])) + static public function dec($ste, $params, $sub) { + if(empty($params["var"])) { throw new RuntimeError("Missing var parameter in ."); - $ref = &$ste->get_var_reference($params["var"], True); + } + $ref = &$ste->get_var_reference($params["var"], true); $ref--; } - static public function date($ste, $params, $sub) - { + static public function date($ste, $params, $sub) { return @strftime($sub($ste), empty($params["timestamp"]) ? @time() : (int) $params["timestamp"]); } - static public function in_array($ste, $params, $sub) - { - if(empty($params["array"])) + static public function in_array($ste, $params, $sub) { + if(empty($params["array"])) { throw new RuntimeError("Missing array parameter in ."); - $ar = &$ste->get_var_reference($params["array"], False); - if(!is_array($ar)) + } + $ar = &$ste->get_var_reference($params["array"], false); + if(!is_array($ar)) { return ""; + } return in_array($sub($ste), $ar) ? "y" : ""; } - static public function join($ste, $params, $sub) - { - if(empty($params["array"])) + static public function join($ste, $params, $sub) { + if(empty($params["array"])) { throw new RuntimeError("Missing array parameter in ."); + } return implode($sub($ste), $ste->get_var_by_name($params["array"])); } - static public function split($ste, $params, $sub) - { - if(empty($params["array"])) + static public function split($ste, $params, $sub) { + if(empty($params["array"])) { throw new RuntimeError("Missing array parameter in ."); - if(empty($params["delim"])) + } + if(empty($params["delim"])) { throw new RuntimeError("Missing delim parameter in ."); + } $ste->set_var_by_name($params["array"], explode($params["delim"], $sub($ste))); } - static public function array_add($ste, $params, $sub) - { - if(empty($params["array"])) + static public function array_add($ste, $params, $sub) { + if(empty($params["array"])) { throw new RuntimeError("Missing array parameter in ."); + } - $ar = &$ste->get_var_reference($params["array"], True); - if(empty($params["key"])) + $ar = &$ste->get_var_reference($params["array"], true); + if(empty($params["key"])) { $ar[] = $sub($ste); - else + } else { $ar[$params["key"]] = $sub($ste); + } } static public function array_filter($ste, $params, $sub) { - if(empty($params["array"])) + if(empty($params["array"])) { throw new RuntimeError("Missing array parameter in ."); + } $ar = $ste->get_var_by_name($params["array"]); - if(!is_array($ar)) + if(!is_array($ar)) { throw new RuntimeError("Variable at 'array' is not an array."); + } $keys = array_keys($ar); - if(!empty($params["keep_by_keys"])) - { - $keep_by_keys = &$ste->get_var_reference($params["keep_by_keys"], False); - if(!is_array($keep_by_keys)) + if(!empty($params["keep_by_keys"])) { + $keep_by_keys = &$ste->get_var_reference($params["keep_by_keys"], false); + if(!is_array($keep_by_keys)) { throw new RuntimeError("Variable at 'keep_by_keys' is not an array."); + } $delkeys = array_filter($keys, function($k) use ($keep_by_keys) { return !in_array($k, $keep_by_keys); }); - foreach($delkeys as $dk) + foreach($delkeys as $dk) { unset($ar[$dk]); + } $keys = array_keys($ar); } - if(!empty($params["keep_by_values"])) - { - $keep_by_values = &$ste->get_var_reference($params["keep_by_values"], False); - if(!is_array($keep_by_values)) + if(!empty($params["keep_by_values"])) { + $keep_by_values = &$ste->get_var_reference($params["keep_by_values"], false); + if(!is_array($keep_by_values)) { throw new RuntimeError("Variable at 'keep_by_values' is not an array."); + } $ar = array_filter($ar, function($v) use ($keep_by_values) { return in_array($v, $keep_by_values); }); $keys = array_keys($ar); } - if(!empty($params["delete_by_keys"])) - { - $delete_by_keys = &$ste->get_var_reference($params["delete_by_keys"], False); - if(!is_array($delete_by_keys)) + if(!empty($params["delete_by_keys"])) { + $delete_by_keys = &$ste->get_var_reference($params["delete_by_keys"], false); + if(!is_array($delete_by_keys)) { throw new RuntimeError("Variable at 'delete_by_keys' is not an array."); + } $delkeys = array_filter($keys, function($k) use ($delete_by_keys) { return in_array($k, $delete_by_keys); }); - foreach($delkeys as $dk) + foreach($delkeys as $dk) { unset($ar[$dk]); + } $keys = array_keys($ar); } - if(!empty($params["delete_by_values"])) - { - $delete_by_values = &$ste->get_var_reference($params["delete_by_values"], False); - if(!is_array($delete_by_values)) + if(!empty($params["delete_by_values"])) { + $delete_by_values = &$ste->get_var_reference($params["delete_by_values"], false); + if(!is_array($delete_by_values)) { throw new RuntimeError("Variable at 'delete_by_values' is not an array."); + } $ar = array_filter($ar, function($v) use ($delete_by_values) { return !in_array($v, $delete_by_values); }); $keys = array_keys($ar); } diff --git a/tests/test_short/comment b/tests/test_short/comment deleted file mode 100644 index 12d7494..0000000 --- a/tests/test_short/comment +++ /dev/null @@ -1 +0,0 @@ -This currently does not work, since the compiler does not allow tags in tag parameters. diff --git a/tests/test_short/want b/tests/test_short/want index a0aba93..d86bac9 100644 --- a/tests/test_short/want +++ b/tests/test_short/want @@ -1 +1 @@ -OK \ No newline at end of file +OK -- cgit v1.2.3-54-g00ecf From eebf5cb885f266104333ac145355ef6e2599e5f6 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Fri, 25 Oct 2013 21:43:37 +0200 Subject: New Test --- stupid_template_engine.php | 2 +- tests/test_escapes/.gitignore | 3 +++ tests/test_escapes/code.php | 7 +++++++ tests/test_escapes/test.tpl | 7 +++++++ tests/test_escapes/want | 7 +++++++ 5 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 tests/test_escapes/.gitignore create mode 100644 tests/test_escapes/code.php create mode 100644 tests/test_escapes/test.tpl create mode 100644 tests/test_escapes/want (limited to 'stupid_template_engine.php') diff --git a/stupid_template_engine.php b/stupid_template_engine.php index cfb5882..dd90a89 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -196,7 +196,7 @@ class Parser { $off = $this->off; $name = $this->take_while(function($c) { return ctype_alnum($c) || ($c == "_"); }); if(mb_strlen($name) == 0) { - throw new ParseCompileError("Expected a name (alphanumeric chars + '_', at least one char)"); + throw new ParseCompileError("Expected a name (alphanumeric chars + '_', at least one char)", $this->name, $off); } return $name; } diff --git a/tests/test_escapes/.gitignore b/tests/test_escapes/.gitignore new file mode 100644 index 0000000..de2a41b --- /dev/null +++ b/tests/test_escapes/.gitignore @@ -0,0 +1,3 @@ +have +*.ast +*.transc.php diff --git a/tests/test_escapes/code.php b/tests/test_escapes/code.php new file mode 100644 index 0000000..05984d1 --- /dev/null +++ b/tests/test_escapes/code.php @@ -0,0 +1,7 @@ +register_tag("my_echo", function($ste, $params, $sub) { + return $params["text"]; + }); +} diff --git a/tests/test_escapes/test.tpl b/tests/test_escapes/test.tpl new file mode 100644 index 0000000..5458fde --- /dev/null +++ b/tests/test_escapes/test.tpl @@ -0,0 +1,7 @@ +\\\\\\\$foo +\${bar} +\?{foo|bar|baz} +?{|Foo|Bar\|Baz} + + +\' \ No newline at end of file diff --git a/tests/test_escapes/want b/tests/test_escapes/want new file mode 100644 index 0000000..3278c4d --- /dev/null +++ b/tests/test_escapes/want @@ -0,0 +1,7 @@ +\\\$foo +${bar} +?{foo|bar|baz} +Bar|Baz +foo\"bar$ +' +\' -- cgit v1.2.3-54-g00ecf