diff options
author | Kevin Chabowski <kevin@kch42.de> | 2013-10-25 21:44:02 +0200 |
---|---|---|
committer | Kevin Chabowski <kevin@kch42.de> | 2013-10-25 21:44:02 +0200 |
commit | dd03af69265ad686ae8daff6ecbd9df763e6d19f (patch) | |
tree | a86dae876afae2dcea9a3d70e3b513e305b38415 | |
parent | 91b965181f907b169aeeff63a6c15f9b8df9d9a9 (diff) | |
parent | eebf5cb885f266104333ac145355ef6e2599e5f6 (diff) | |
download | ste-dd03af69265ad686ae8daff6ecbd9df763e6d19f.tar.gz ste-dd03af69265ad686ae8daff6ecbd9df763e6d19f.tar.bz2 ste-dd03af69265ad686ae8daff6ecbd9df763e6d19f.zip |
Merge branch 'parser2'
STE gets a new, more robust parser. The previous parser was very fragile
and relied on some ugly regular expressions.
Other advantages of the new parser:
* No precompile phase to implement short tags (?{}, ~{}), that means
short tags can now be nested in any order.
* The parses now uses the mb_* functions, so it should handle non-ascii
text correctly.
There were also some improvements to the compiler:
* Various small bugfixes.
* The compiler accepts and compiles tag parameters that have tags in
them. The parser still doesn't accept this (and probably never will),
this is mainly used to allow arbitrary code inside of short cmp tags
(~{..}).
There are now (finally!) tests for the language to maintain a certain
quality and consistency (which STE lacked in the past, to be honest).
And finally the code was reformatted. Now 1TBS is used instead of
Allman.
44 files changed, 1053 insertions, 1414 deletions
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 @@ <p><code><ste:if>condition<ste:then>then</ste:then><ste:else>else</ste:else></ste:if></code></p> <p><code>?</code>, <code>{</code>, <code>|</code> and <code>}</code> can be <a href="#escaping">escaped</a></p> <p>In this variant, the else part <strong>is not optional</strong>!</p> - <p><strong>WARNING:</strong> short if-clauses can not be nested!</p> <h3 id="builtin_cmp">ste:cmp</h3> <p>With the ste:cmp tag you can compare two values.</p> @@ -285,9 +284,7 @@ <p><code>~{a|operator|b}</code></p> <p>This is equivalent to:</p> <p><code><ste:cmp text_a="a" op="operator" text_b="b" /></code></p> - <p><code>~</code>, <code>{</code>, <code>|</code> and <code>}</code> can be <a href="#escaping">escaped</a></p> - <p>Because this is implemented as a simple substitution, you can only use <a href="#basic_elems_text">Text</a> and <a href="#basic_elems_variable">Variables</a>. And <code>"</code> must be escaped.</p> - <p><strong>WARNING:</strong> short comparisons can not be nested! They can be inside <a href="#builtin_if_short">short if-clauses</a>, but not the other way around!</p> + <p><code>~</code>, <code>{</code>, <code>|</code> and <code>}</code> can be <a href="#escaping">escaped</a>.</p> <h3 id="builtin_not">ste:not</h3> <p>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.</p> diff --git a/example/templates/transc/.gitignore b/example/templates/transc/.gitignore new file mode 100644 index 0000000..cde8069 --- /dev/null +++ b/example/templates/transc/.gitignore @@ -0,0 +1 @@ +*.php diff --git a/example/templates/transc/articles.html.php b/example/templates/transc/articles.html.php deleted file mode 100644 index d52658c..0000000 --- a/example/templates/transc/articles.html.php +++ /dev/null @@ -1,172 +0,0 @@ -<?php $transcompile_fx = function($ste) -{ - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= $ste->load("custom_tags.tpl"); - $outputstack[$outputstack_i] .= $ste->load("master.html"); - $blockname_4feb57c8eb53c0_39108239 = "content"; - $ste->blocks['4feb57c8eb54f8.57715029'] = array_pop($outputstack); - $ste->blockorder[] = '4feb57c8eb54f8.57715029'; - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= "<h2>Some Articles</h2>\n\t"; - $parameters_4feb57c8eb55d1_49937129 = array(); - $parameters_4feb57c8eb55d1_49937129['array'] = "articles"; - $parameters_4feb57c8eb55d1_49937129['value'] = "article"; - $parameters_4feb57c8eb55d1_49937129['max'] = "3"; - $parameters_4feb57c8eb55d1_49937129['counter'] = "i"; - $outputstack[$outputstack_i] .= $ste->call_tag('foreach_limit', $parameters_4feb57c8eb55d1_49937129, function($ste) - { - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= "<h3>"; - $parameters_4feb57c8eb59c3_59434363 = array(); - $outputstack[$outputstack_i] .= $ste->call_tag('uppercase', $parameters_4feb57c8eb59c3_59434363, function($ste) - { - $outputstack = array(''); - $outputstack_i = 0; - $parameters_4feb57c8eb5a98_71645476 = array(); - $outputstack[$outputstack_i] .= $ste->call_tag('escape', $parameters_4feb57c8eb5a98_71645476, function($ste) - { - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= @$ste->vars["article"]["title"]; - return array_pop($outputstack); - }); - return array_pop($outputstack); - }); - $outputstack[$outputstack_i] .= "</h3>\n\t\t<div class=\"author\">Author: "; - $parameters_4feb57c8eb5f92_71020272 = array(); - $outputstack[$outputstack_i] .= $ste->call_tag('escape', $parameters_4feb57c8eb5f92_71020272, function($ste) - { - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= @$ste->vars["article"]["author"]; - return array_pop($outputstack); - }); - $outputstack[$outputstack_i] .= "</div>\n\t\t<div class=\"date\">"; - $parameters_4feb57c8eb62b8_11002450 = array(); - $parameters_4feb57c8eb62b8_11002450['timestamp'] = @$ste->vars["article"]["timestamp"]; - $outputstack[$outputstack_i] .= $ste->call_tag('date', $parameters_4feb57c8eb62b8_11002450, function($ste) - { - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= "%d. %h. %Y, %H:%M:%S"; - return array_pop($outputstack); - }); - $outputstack[$outputstack_i] .= "</div>\n\t\t<div class=\"article_content\">\n\t\t\t"; - $outputstack[] = ""; - $outputstack_i++; - $outputstack[$outputstack_i] .= (($ste->get_var_by_name("i")) == ("0")) ? 'yes' : ''; - $outputstack_i--; - if($ste->evalbool(array_pop($outputstack))) - { - $outputstack[$outputstack_i] .= "\n\t\t\t\t\t" . @$ste->vars["article"]["full"]; - - } - else - { - $outputstack[$outputstack_i] .= "\n\t\t\t\t\t" . @$ste->vars["article"]["excerpt"]; - - } - $outputstack[$outputstack_i] .= "</div>\n\t\t<hr />\n\t"; - return array_pop($outputstack); - }); - $outputstack[] = ''; - $outputstack_i++; - $parameters_4feb57c8eb72f3_12728934 = array(); - $parameters_4feb57c8eb72f3_12728934['array'] = "articles"; - $outputstack[$outputstack_i] .= $ste->call_tag('arraylen', $parameters_4feb57c8eb72f3_12728934, function($ste) { return ''; }); - $outputstack_i--; - $ste->set_var_by_name("articles_n", array_pop($outputstack)); - $outputstack[] = ""; - $outputstack_i++; - $outputstack[$outputstack_i] .= ((@$ste->vars["articles_n"]) > ("3")) ? 'yes' : ''; - $outputstack_i--; - if($ste->evalbool(array_pop($outputstack))) - { - $outputstack[$outputstack_i] .= "<p>There are <a href=\"#\">more articles</a>.</p>\n\t\t"; - - } - $outputstack[$outputstack_i] .= "<h2>Some more useless demo stuff...</h2>\n\t\t<h3>Counting from 10 to 0...</h3>\n\t\t<p>but take only the even ones and multiply by 5...</p>\n\t\t"; - $forloop_4feb57c8eb7965_88913250_start = "10"; - $forloop_4feb57c8eb7965_88913250_stop = "0"; - $forloop_4feb57c8eb7965_88913250_countername = "i"; - for($forloop_4feb57c8eb7965_88913250_counter = $forloop_4feb57c8eb7965_88913250_start; $forloop_4feb57c8eb7965_88913250_counter >= $forloop_4feb57c8eb7965_88913250_stop; $forloop_4feb57c8eb7965_88913250_counter += -1) - { - try - { - $ste->set_var_by_name($forloop_4feb57c8eb7965_88913250_countername, $forloop_4feb57c8eb7965_88913250_counter); - $outputstack[] = ""; - $outputstack_i++; - $outputstack[] = ''; - $outputstack_i++; - $outputstack[$outputstack_i] .= @$ste->vars["i"]; - $outputstack_i--; - $tmp_even = array_pop($outputstack); - $outputstack[$outputstack_i] .= (is_numeric($tmp_even) and ($tmp_even % 2 == 0)) ? 'yes' : ''; - $outputstack_i--; - if($ste->evalbool(array_pop($outputstack))) - { - $outputstack[] = ''; - $outputstack_i++; - $outputstack[$outputstack_i] .= @$ste->vars["i"] . " * 5"; - $outputstack_i--; - $outputstack[$outputstack_i] .= $ste->calc(array_pop($outputstack)); - $outputstack[$outputstack_i] .= "<br />\n\t\t\t\t"; - - } - - } - catch(\ste\BreakException $e) { break; } - catch(\ste\ContinueException $e) { continue; } - - } - - $outputstack[$outputstack_i] .= "<h3>Repeat some text...</h3>\n\t\t"; - $parameters_4feb57c8eb86f4_27000372 = array(); - $parameters_4feb57c8eb86f4_27000372['n'] = "10"; - $outputstack[$outputstack_i] .= $ste->call_tag('repeat', $parameters_4feb57c8eb86f4_27000372, function($ste) - { - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= "<p>Bla</p>\n\t\t"; - return array_pop($outputstack); - }); - $outputstack[$outputstack_i] .= "<h3>Get a variable's content dynamically</h3>\n\t\t"; - $outputstack[$outputstack_i] .= $ste->get_var_by_name(@$ste->vars["foo"] . "[" . @$ste->vars["bar"] . "]");$outputstack[$outputstack_i] .= "<h3>We will call ste:repeat with a non-numerical value for n here to see the handling of a RuntimeError</h3>\n\t\t"; - $parameters_4feb57c8eb8be8_11772552 = array(); - $parameters_4feb57c8eb8be8_11772552['n'] = "lol"; - $outputstack[$outputstack_i] .= $ste->call_tag('repeat', $parameters_4feb57c8eb8be8_11772552, function($ste) - { - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= "<p>Bla</p>\n\t\t"; - return array_pop($outputstack); - }); - $parameters_4feb57c8eb8e64_48510077 = array(); - $parameters_4feb57c8eb8e64_48510077['array'] = "hai"; - $parameters_4feb57c8eb8e64_48510077['delim'] = ","; - $outputstack[$outputstack_i] .= $ste->call_tag('split', $parameters_4feb57c8eb8e64_48510077, function($ste) - { - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= "a,b,c,d,e"; - return array_pop($outputstack); - }); - $parameters_4feb57c8eb91f2_76021324 = array(); - $parameters_4feb57c8eb91f2_76021324['array'] = "hai"; - $outputstack[$outputstack_i] .= $ste->call_tag('join', $parameters_4feb57c8eb91f2_76021324, function($ste) - { - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= "<br />"; - return array_pop($outputstack); - }); - $ste->blocks[$blockname_4feb57c8eb53c0_39108239] = array_pop($outputstack); - if(array_search($blockname_4feb57c8eb53c0_39108239, $ste->blockorder) === FALSE) - $ste->blockorder[] = $blockname_4feb57c8eb53c0_39108239; - $outputstack = array(''); - $outputstack_i = 0; - return array_pop($outputstack); -}; ?>
\ No newline at end of file diff --git a/example/templates/transc/custom_tags.tpl.php b/example/templates/transc/custom_tags.tpl.php deleted file mode 100644 index b92f8a3..0000000 --- a/example/templates/transc/custom_tags.tpl.php +++ /dev/null @@ -1,65 +0,0 @@ -<?php $transcompile_fx = function($ste) -{ - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[] = ''; - $outputstack_i++; - $outputstack[$outputstack_i] .= "array|value|max"; - $outputstack_i--; - $mandatory_params = explode('|', array_pop($outputstack)); - $tag_fx = function($ste, $params, $sub) use ($mandatory_params) - { - $outputstack = array(); $outputstack_i = 0;$ste->vars['_tag_parameters'] = $params; - foreach($mandatory_params as $mp) - { - if(!isset($params[$mp])) - throw new \ste\RuntimeError("$mp missing in <ste:" . "foreach_limit" . ">."); - }$foreachloop_4f4e9064144651_57761022_arrayvar = @$ste->vars["_tag_parameters"]["array"]; - $foreachloop_4f4e9064144651_57761022_valuevar = @$ste->vars["_tag_parameters"]["value"]; - $foreachloop_4f4e9064144651_57761022_countervar = "i"; - $foreachloop_4f4e9064144651_57761022_array = $ste->get_var_by_name($foreachloop_4f4e9064144651_57761022_arrayvar); - if(!is_array($foreachloop_4f4e9064144651_57761022_array)) - $foreachloop_4f4e9064144651_57761022_array = array(); - $foreachloop_4f4e9064144651_57761022_counter = -1; - foreach($foreachloop_4f4e9064144651_57761022_array as $foreachloop_4f4e9064144651_57761022_key => $foreachloop_4f4e9064144651_57761022_value) - { - try - { - $foreachloop_4f4e9064144651_57761022_counter++; - $ste->set_var_by_name($foreachloop_4f4e9064144651_57761022_countervar, $foreachloop_4f4e9064144651_57761022_counter); - $ste->set_var_by_name($foreachloop_4f4e9064144651_57761022_valuevar, $foreachloop_4f4e9064144651_57761022_value); - - $outputstack[] = ""; - $outputstack_i++; - $outputstack[$outputstack_i] .= (($ste->get_var_by_name("i")) >= ($ste->get_var_by_name("_tag_parameters[max]"))) ? 'yes' : ''; - $outputstack_i--; - if($ste->evalbool(array_pop($outputstack))) - { - throw new \ste\BreakException(); - - } - $outputstack[] = ""; - $outputstack_i++; - $outputstack[$outputstack_i] .= "\n\t\t\t" . @$ste->vars["_tag_parameters"]["counter"]; - $outputstack_i--; - if($ste->evalbool(array_pop($outputstack))) - { - $outputstack[] = ''; - $outputstack_i++; - $outputstack[$outputstack_i] .= @$ste->vars["i"]; - $outputstack_i--; - $ste->set_var_by_name(@$ste->vars["_tag_parameters"]["counter"], array_pop($outputstack)); - - } - $outputstack[$outputstack_i] .= $sub($ste); - } - catch(\ste\BreakException $e) { break; } - catch(\ste\ContinueException $e) { continue; } - - } - - return array_pop($outputstack); - }; - $ste->register_tag("foreach_limit", $tag_fx); - return array_pop($outputstack); -}; ?>
\ No newline at end of file diff --git a/example/templates/transc/master.html.php b/example/templates/transc/master.html.php deleted file mode 100644 index 1bee843..0000000 --- a/example/templates/transc/master.html.php +++ /dev/null @@ -1,503 +0,0 @@ -<?php $transcompile_fx = function($ste) -{/*Array -( - [0] => ste\TextNode Object - ( - [text] => <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en"> -<head> - <title> - [tpl] => master.html - [offset] => 0 - ) - - [1] => ste\TagNode Object - ( - [name] => if - [params] => Array - ( - ) - - [sub] => Array - ( - [0] => ste\VariableNode Object - ( - [name] => title - [arrayfields] => Array - ( - ) - - [tpl] => master.html - [offset] => 206 - ) - - [1] => ste\TagNode Object - ( - [name] => then - [params] => Array - ( - ) - - [sub] => Array - ( - [0] => ste\VariableNode Object - ( - [name] => title - [arrayfields] => Array - ( - ) - - [tpl] => master.html - [offset] => 222 - ) - - [1] => ste\TextNode Object - ( - [text] => - example - [tpl] => master.html - [offset] => 227 - ) - - ) - - [tpl] => master.html - [offset] => 211 - ) - - [2] => ste\TagNode Object - ( - [name] => else - [params] => Array - ( - ) - - [sub] => Array - ( - [0] => ste\TextNode Object - ( - [text] => example - [tpl] => master.html - [offset] => 258 - ) - - ) - - [tpl] => master.html - [offset] => 248 - ) - - ) - - [tpl] => master.html - [offset] => 197 - ) - - [2] => ste\TextNode Object - ( - [text] => </title> - [tpl] => master.html - [offset] => 285 - ) - - [3] => ste\TextNode Object - ( - [text] => <style type="text/css"> - * { - font-family: sans-serif; - } - .online { - color: #0a0; - } - .offline { - color: #555; - font-style: italic; - - } - </style> - -</head> -<body> - <h1>example</h1> - - <div id="content"> - - [tpl] => master.html - [offset] => 384 - ) - - [4] => ste\TagNode Object - ( - [name] => block - [params] => Array - ( - [name] => Array - ( - [0] => ste\TextNode Object - ( - [text] => content - [tpl] => master.html - [offset] => 626 - ) - - ) - - ) - - [sub] => Array - ( - [0] => ste\TextNode Object - ( - [text] => Default content. - - [tpl] => master.html - [offset] => 635 - ) - - ) - - [tpl] => master.html - [offset] => 609 - ) - - [5] => ste\TextNode Object - ( - [text] => </div> - <div id="otherstuff"> - - [tpl] => master.html - [offset] => 670 - ) - - [6] => ste\TagNode Object - ( - [name] => block - [params] => Array - ( - [name] => Array - ( - [0] => ste\TextNode Object - ( - [text] => otherstuff - [tpl] => master.html - [offset] => 721 - ) - - ) - - ) - - [sub] => Array - ( - [0] => ste\TextNode Object - ( - [text] => <h2>List of users</h2> - - [tpl] => master.html - [offset] => 733 - ) - - [1] => ste\TextNode Object - ( - [text] => <ul> - - [tpl] => master.html - [offset] => 820 - ) - - [2] => ste\TagNode Object - ( - [name] => foreach - [params] => Array - ( - [array] => Array - ( - [0] => ste\TextNode Object - ( - [text] => users - [tpl] => master.html - [offset] => 853 - ) - - ) - - [value] => Array - ( - [0] => ste\TextNode Object - ( - [text] => user - [tpl] => master.html - [offset] => 867 - ) - - ) - - ) - - [sub] => Array - ( - [0] => ste\TextNode Object - ( - [text] => <li class=" - [tpl] => master.html - [offset] => 873 - ) - - [1] => ste\TagNode Object - ( - [name] => if - [params] => Array - ( - ) - - [sub] => Array - ( - [0] => ste\VariableNode Object - ( - [name] => user - [arrayfields] => Array - ( - [0] => Array - ( - [0] => ste\TextNode Object - ( - [text] => online - [tpl] => master.html - [offset] => 904 - ) - - ) - - ) - - [tpl] => master.html - [offset] => 899 - ) - - [1] => ste\TagNode Object - ( - [name] => then - [params] => Array - ( - ) - - [sub] => Array - ( - [0] => ste\TextNode Object - ( - [text] => online - [tpl] => master.html - [offset] => 921 - ) - - ) - - [tpl] => master.html - [offset] => 911 - ) - - [2] => ste\TagNode Object - ( - [name] => else - [params] => Array - ( - ) - - [sub] => Array - ( - [0] => ste\TextNode Object - ( - [text] => offline - [tpl] => master.html - [offset] => 948 - ) - - ) - - [tpl] => master.html - [offset] => 938 - ) - - ) - - [tpl] => master.html - [offset] => 890 - ) - - [2] => ste\TextNode Object - ( - [text] => "> - [tpl] => master.html - [offset] => 975 - ) - - [3] => ste\VariableNode Object - ( - [name] => user - [arrayfields] => Array - ( - [0] => Array - ( - [0] => ste\TextNode Object - ( - [text] => name - [tpl] => master.html - [offset] => 983 - ) - - ) - - ) - - [tpl] => master.html - [offset] => 978 - ) - - [4] => ste\TextNode Object - ( - [text] => ( - [tpl] => master.html - [offset] => 988 - ) - - [5] => ste\VariableNode Object - ( - [name] => user - [arrayfields] => Array - ( - [0] => Array - ( - [0] => ste\TextNode Object - ( - [text] => username - [tpl] => master.html - [offset] => 996 - ) - - ) - - ) - - [tpl] => master.html - [offset] => 991 - ) - - [6] => ste\TextNode Object - ( - [text] => )</li> - - [tpl] => master.html - [offset] => 1005 - ) - - ) - - [tpl] => master.html - [offset] => 833 - ) - - [3] => ste\TextNode Object - ( - [text] => </ul> - - [tpl] => master.html - [offset] => 1030 - ) - - ) - - [tpl] => master.html - [offset] => 704 - ) - - [7] => ste\TextNode Object - ( - [text] => </div> -</body> -</html> - - [tpl] => master.html - [offset] => 1054 - ) - -) -*/ - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n\t\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en-us\" lang=\"en\">\n<head>\n\t<title>"; - $outputstack[] = ""; - $outputstack_i++; - $outputstack[$outputstack_i] .= @$ste->vars["title"]; - $outputstack_i--; - if($ste->evalbool(array_pop($outputstack))) - { - $outputstack[$outputstack_i] .= @$ste->vars["title"] . " - example"; - - } - else - { - $outputstack[$outputstack_i] .= "example"; - - } - $outputstack[$outputstack_i] .= "</title>" . "<style type=\"text/css\">\n\t\t* {\n\t\t\tfont-family: sans-serif;\n\t\t}\n\t\t.online {\n\t\t\tcolor: #0a0;\n\t\t}\n\t\t.offline {\n\t\t\tcolor: #555;\n\t\t\tfont-style: italic;\n\t\t\t\n\t\t}\n\t</style>\n\t\n</head>\n<body>\n\t<h1>example</h1>\n\t\n\t<div id=\"content\">\n\t\t"; - $blockname_4f4e94ddd41342_39491438 = "content"; - $ste->blocks['4f4e94ddd41470.10353359'] = array_pop($outputstack); - $ste->blockorder[] = '4f4e94ddd41470.10353359'; - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= "Default content.\n\t\t"; - $ste->blocks[$blockname_4f4e94ddd41342_39491438] = array_pop($outputstack); - if(array_search($blockname_4f4e94ddd41342_39491438, $ste->blockorder) === FALSE) - $ste->blockorder[] = $blockname_4f4e94ddd41342_39491438; - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= "</div>\n\t<div id=\"otherstuff\">\n\t\t"; - $blockname_4f4e94ddd415e6_09352068 = "otherstuff"; - $ste->blocks['4f4e94ddd416b2.98760934'] = array_pop($outputstack); - $ste->blockorder[] = '4f4e94ddd416b2.98760934'; - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= "<h2>List of users</h2>\n\t\t\t" . "<ul>\n\t\t\t\t"; - $foreachloop_4f4e94ddd417b7_50245786_arrayvar = "users"; - $foreachloop_4f4e94ddd417b7_50245786_valuevar = "user"; - $foreachloop_4f4e94ddd417b7_50245786_array = $ste->get_var_by_name($foreachloop_4f4e94ddd417b7_50245786_arrayvar); - if(!is_array($foreachloop_4f4e94ddd417b7_50245786_array)) - $foreachloop_4f4e94ddd417b7_50245786_array = array(); - foreach($foreachloop_4f4e94ddd417b7_50245786_array as $foreachloop_4f4e94ddd417b7_50245786_key => $foreachloop_4f4e94ddd417b7_50245786_value) - { - try - { - $ste->set_var_by_name($foreachloop_4f4e94ddd417b7_50245786_valuevar, $foreachloop_4f4e94ddd417b7_50245786_value); - - $outputstack[$outputstack_i] .= "<li class=\""; - $outputstack[] = ""; - $outputstack_i++; - $outputstack[$outputstack_i] .= @$ste->vars["user"]["online"]; - $outputstack_i--; - if($ste->evalbool(array_pop($outputstack))) - { - $outputstack[$outputstack_i] .= "online"; - - } - else - { - $outputstack[$outputstack_i] .= "offline"; - - } - $outputstack[$outputstack_i] .= "\">" . @$ste->vars["user"]["name"] . " (" . @$ste->vars["user"]["username"] . ")</li>\n\t\t\t\t"; - - } - catch(\ste\BreakException $e) { break; } - catch(\ste\ContinueException $e) { continue; } - - } - - $outputstack[$outputstack_i] .= "</ul>\n\t\t"; - $ste->blocks[$blockname_4f4e94ddd415e6_09352068] = array_pop($outputstack); - if(array_search($blockname_4f4e94ddd415e6_09352068, $ste->blockorder) === FALSE) - $ste->blockorder[] = $blockname_4f4e94ddd415e6_09352068; - $outputstack = array(''); - $outputstack_i = 0; - $outputstack[$outputstack_i] .= "</div>\n</body>\n</html>\n"; - return array_pop($outputstack); -}; ?>
\ No newline at end of file diff --git a/stupid_template_engine.php b/stupid_template_engine.php index 7153a99..dd90a89 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -15,76 +15,71 @@ */ 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); + $this->text = $text; + } } -class TagNode 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 -{ +class VariableNode extends ASTNode { public $name; - public $arrayfields; - public function transcompile() - { + public $arrayfields = array(); + 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->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; } } @@ -102,288 +97,463 @@ 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; - } - - if($counter > 0) - throw new \Exception("Missing closing \"$closing\". Stop."); +/* + * 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; + private $off; + private $len; - 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("/(?:(?<!\\\\)\\$)/s", $text, $match, PREG_OFFSET_CAPTURE) == 0) - { - $node = new TextNode($tpl, $off); - $node->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; - } - return array_merge($tokens, $nexttokens); + private function next($n = 1) { + if($n <= 0) { + throw new \InvalidArgumentException("\$n must be > 0"); + } + $c = mb_substr($this->text, $this->off, $n); + $this->off = min($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, "[", "]"); - } - 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; + private function back($n = 1) { + if($n <= 0) { + throw new \InvalidArgumentException("\$n must be > 0"); + } + $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($needles 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((string) $needles[$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_]+)=((?:\"(?:.*?)(?<!\\\\)\")|(?:'(?:.*?)(?<!\\\\)'))/s", $code, $matches, PREG_OFFSET_CAPTURE) > 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)", $this->name, $off); + } + return $name; + } - $tag->sub = array(); + /* + * 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 <ASTNode> objects. + * + * Throws: + * <ParseCompileError> + */ + 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 self::tidyup_ast($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)); - } - - /* Handling ste:rawtext pseudotag */ - if($tag->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)); - } - - $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*)|(?:\\s*\\/\\s*))\\>/s", $code, $matches, PREG_OFFSET_CAPTURE, $off) > 0) /* RegEx from hell! Matches all <ste:> 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); + private static function tidyup_ast($ast) { + $out = array(); + + $prevtext = NULL; + $first = true; + + foreach($ast as $node) { + if($node instanceof TextNode) { + if($prevtext === NULL) { + $prevtext = $node; + } else { + $prevtext->text .= $node->text; + } + } else { + if($prevtext !== NULL) { + if($first) { + $prevtext->text = ltrim($prevtext->text); + } + if($prevtext->text != "") { + $out[] = $prevtext; + } } - else - $tagstack[] = array($matches[2][0], $matches[0][1]); + $prevtext = NULL; + $first = false; + + 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; } - $last_tag_start = $matches[0][1]; - $off = $last_tag_start + strlen($matches[0][0]); - if(empty($tagstack)) + } + + if($prevtext !== NULL) { + if($first) { + $prevtext->text = ltrim($prevtext->text); + } + if($prevtext->text != "") { + $out[] = $prevtext; + } + } + + return $out; + } + + private function parse_text($escapes, $flags, $breakon = NULL, $separator = NULL, $nullaction = NULL, $opentag = NULL, $openedat = -1) { + $elems = array(); + $astlist = array(); + + $needles = array( + "commentopen" => "<ste:comment>", + "rawopen" => "<ste:rawtext>", + "escape" => '\\', + "varcurlyopen" => '${', + "var" => '$', + ); + + if($flags & self::PARSE_TAG) { + $needles["tagopen"] = '<ste:'; + $needles["closetagopen"] = '</ste:'; + } + if($flags & self::PARSE_SHORT) { + $needles["shortifopen"] = '?{'; + $needles["shortcompopen"] = '~{'; + } + + if($separator !== NULL) { + $needles["sep"] = $separator; + } + if($breakon !== NULL) { + $needles["break"] = $breakon; + } + + 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("</ste:comment>"); + 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("</ste:rawtext>"); + 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(); + } + break; + case "shortifopen": + $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) = $shortelems; + $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": + $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) = $shortelems; + $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 */ - "/(?:(?<!\\\\)~)(?:(?<!\\\\)\\{)(.*?)(?:(?<!\\\\)\\|)(.*?)(?:(?<!\\\\)\\|)(.*?)(?:(?<!\\\\)\\})/s", - "<ste:cmp text_a=\"\$1\" op=\"\$2\" text_b=\"\$3\" />", - $code - ); - $code = preg_replace( /* Transform short form of if-clause (?{cond|then|else}) to long form */ - "/(?:(?<!\\\\)\\?)(?:(?<!\\\\)\\{)(.*?)(?:(?<!\\\\)\\|)(.*?)(?:(?<!\\\\)\\|)(.*?)(?:(?<!\\\\)\\})/s", - "<ste:if>\$1<ste:then>\$2</ste:then><ste:else>\$3</ste:else></ste:if>", - $code - ); - /* Unescape \? \~ \{ \} \| */ - $code = preg_replace("/(?:(?<!\\\\)\\\\\\?)/s", "?", $code); - $code = preg_replace("/(?:(?<!\\\\)\\\\~)/s", "~", $code); - $code = preg_replace("/(?:(?<!\\\\)\\\\\\{)/s", "{", $code); - $code = preg_replace("/(?:(?<!\\\\)\\\\\\})/s", "}", $code); - $code = preg_replace("/(?:(?<!\\\\)\\\\\\|)/s", "|", $code); + private function parse_var($openedat, $curly) { + $varnode = new VariableNode($this->name, $openedat); + $varnode->name = $this->get_name(); + $varnode->arrayfields = $this->parse_array(); + + if(($curly) && ($this->next() != "}")) { + throw new ParseCompileError("Unclosed '\${'", $this->name, $openedat); + } + return $varnode; + } - return $code; -} - -/* - * 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 <transcompile>. - */ -function parse($code, $tpl) -{ - return mk_ast($code, $tpl, 0); + 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 <ste: tag (expected >)", $this->name, $openedat); + } + + return $tag; + case '>': + $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 */ + ); + $tag->sub = $sub[0]; + 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; + $paramval = $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 */ + ); + $tag->params[$param] = $paramval[0]; + } + } + } } -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), @@ -396,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 <ste:then> in <ste:if>.", $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', '!='), @@ -566,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 <ste:cmp>.", $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 <ste:cmp>.", $ast->tpl, $ast->offset); + } - if(!isset($ast->params["op"])) + if(!isset($ast->params["op"])) { throw new ParseCompileError("Transcompile error: op not given in <ste:cmp>.", $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 <ste:cmp>", $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) { @@ -613,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 <ste:for>.", $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 <ste:for>.", $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 <ste:for>.');\n"; $code .= "if(\$${loopname}_step > 0)\n{\n"; $code .= "\tfor(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter <= \$${loopname}_stop; \$${loopname}_counter += \$${loopname}_step)\n"; @@ -664,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 <ste:for>.", $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 <ste:foreach>.", $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 <ste:foreach>.", $ast->tpl, $ast->offset); - $code .= "\$${loopname}_valuevar = " . _transcompile($ast->params["value"], True) . ";\n"; + } + list($val, $pre) = _transcompile($ast->params["value"], true); + $code .= $pre; + $code .= "\$${loopname}_valuevar = " . $val . ";\n"; - if(!empty($ast->params["key"])) - $code .= "\$${loopname}_keyvar = " . _transcompile($ast->params["key"], True) . ";\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"; @@ -713,55 +899,58 @@ $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 <ste:block>.", $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 <ste:load>.", $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) - { - if(empty($ast->params["name"])) + "mktag" => function($ast) { + $code = ""; + + if(empty($ast->params["name"])) { throw new ParseCompileError("Transcompile Error: name missing in <ste:mktag>.", $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"; + list($tagname, $tagname_pre) = _transcompile($ast->params["name"], true); - if(!empty($ast->params["mandatory"])) - { + $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"; @@ -772,37 +961,40 @@ $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 .= $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 <ste:set>.", $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 <ste:get>.", $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"; @@ -811,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); @@ -853,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; @@ -883,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 <STECore> 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}"; } @@ -918,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. @@ -957,8 +1144,7 @@ interface StorageAccess * Class: FilesystemStorageAccess * The default <StorageAccess> implementation for loading / saving templates into a directory structure. */ -class FilesystemStorageAccess implements StorageAccess -{ +class FilesystemStorageAccess implements StorageAccess { protected $sourcedir; protected $transcompileddir; @@ -969,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, "<?php \$transcompile_fx = $data; ?>") === False) + @mkdir(dirname($fn), 0777, true); + if(file_put_contents($fn, "<?php \$transcompile_fx = $data; ?>") === false) { throw new CantSaveTemplate("Unable to save template."); - + } } } @@ -1035,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; @@ -1047,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 <RuntimeError> 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 <FatalRuntimeError> if a tag was called that was not registered, otherwise (default) a regular <RuntimeError> will be thrown and automatically handled by STE (see <$mute_runtime_errors>). + * $mute_runtime_errors - If true (default) a <RuntimeError> 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 <FatalRuntimeError> if a tag was called that was not registered, otherwise (default) a regular <RuntimeError> 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 @@ -1062,8 +1236,7 @@ class STECore * Parameters: * $storage_access - An Instance of a <StorageAccess> implementation. */ - public function __construct($storage_access) - { + public function __construct($storage_access) { $this->storage_access = $storage_access; $this->cur_tpl_dir = "/"; STEStandardLibrary::_register_lib($this); @@ -1083,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; } @@ -1107,28 +1281,24 @@ class STECore * Returns: * The output of the tag or, if a <RuntimeError> 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)); } @@ -1148,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; } @@ -1175,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 \"]\"."); } } @@ -1233,9 +1395,8 @@ class STECore * Throws: * <RuntimeError> 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; } @@ -1253,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; } @@ -1276,44 +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) - { - $content = precompile($content); - try - { - $ast = parse($content, $tpl); + if($mode == MODE_SOURCE) { + try { + $ast = Parser::parse($content, $tpl); $transc = transcompile($ast); - } - catch(ParseCompileError $e) - { + } catch(ParseCompileError $e) { $e->rewrite($content); throw $e; } @@ -1325,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; + } } /* @@ -1344,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 <ste:arraylen>."); - $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 <ste:inc>."); - $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 <ste:dec>."); - $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 <ste:in_array>."); - $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 <ste:join>."); + } 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 <ste:split>."); - if(empty($params["delim"])) + } + if(empty($params["delim"])) { throw new RuntimeError("Missing delim parameter in <ste:split>."); + } $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 <ste:array_add>."); + } - $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 <ste:array_filter>."); + } $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/test.php b/test.php new file mode 100644 index 0000000..c9a72ed --- /dev/null +++ b/test.php @@ -0,0 +1,18 @@ +<?php + +require("stupid_template_engine.php"); + +$file = file_get_contents("example/templates/src/master.html"); + +try { + $ast = \ste\Parser::parse($file, "master.html"); +} catch(\ste\ParseCompileError $e) { + $e->rewrite($file); + echo "Could not parse:\n"; + echo $e->getMessage(); + echo "\n"; + exit(1); +} + +echo "~~~~~~~~~~~\n"; +var_dump($ast);
\ No newline at end of file diff --git a/tests/dump_ast.php b/tests/dump_ast.php new file mode 100644 index 0000000..418fbc8 --- /dev/null +++ b/tests/dump_ast.php @@ -0,0 +1,5 @@ +<?php + +require(dirname(__FILE__) . "/../stupid_template_engine.php"); + +var_dump(\ste\Parser::parse(file_get_contents("php://stdin"), "-")); diff --git a/tests/mktest.sh b/tests/mktest.sh new file mode 100755 index 0000000..f249a7f --- /dev/null +++ b/tests/mktest.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +mkdir "$1" +touch "$1/test.tpl" +touch "$1/want" + +echo '<?php + +function test_func($ste) { + +}' > "$1/code.php" + +echo 'have +*.ast +*.transc.php' > "$1/.gitignore" diff --git a/tests/run_all.sh b/tests/run_all.sh new file mode 100755 index 0000000..5727afb --- /dev/null +++ b/tests/run_all.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +r=0 +for t in test_*; do + cd $t + php ../test.php > have + echo -ne "\e[1m$t\e[0m: " + if sed 's/\s*//' < have | grep -v '^$' | cmp -s want; then + echo "OK" + rm *.transc.php + else + echo "FAILED" + for tpl in *.tpl; do + php ../dump_ast.php < $tpl > $tpl.ast + done + r=1 + fi + cd .. +done + +exit $r diff --git a/tests/test.php b/tests/test.php new file mode 100644 index 0000000..41713dc --- /dev/null +++ b/tests/test.php @@ -0,0 +1,23 @@ +<?php + +require(dirname(__FILE__) . "/../stupid_template_engine.php"); +require("code.php"); + +class TestStorage implements \ste\StorageAccess { + public function load($tpl, &$mode) { + $mode = \ste\MODE_SOURCE; + return file_get_contents($tpl); + } + + public function save($tpl, $data, $mode) { + if($mode != \ste\MODE_TRANSCOMPILED) { + return; + } + + file_put_contents("$tpl.transc.php", $data); + } +} + +$ste = new \ste\STECore(new TestStorage()); +test_func($ste); +echo $ste->exectemplate("test.tpl"); diff --git a/tests/test_array/.gitignore b/tests/test_array/.gitignore new file mode 100644 index 0000000..de2a41b --- /dev/null +++ b/tests/test_array/.gitignore @@ -0,0 +1,3 @@ +have +*.ast +*.transc.php diff --git a/tests/test_array/code.php b/tests/test_array/code.php new file mode 100644 index 0000000..58601e2 --- /dev/null +++ b/tests/test_array/code.php @@ -0,0 +1,13 @@ +<?php + +function test_func($ste) { + $ste->vars["foo"] = array( + "a" => array( + "blabla" => "OK" + ), + "b" => "bla" + ); + $ste->vars["bar"] = array( + "baz" => "a" + ); +} diff --git a/tests/test_array/test.tpl b/tests/test_array/test.tpl new file mode 100644 index 0000000..050045d --- /dev/null +++ b/tests/test_array/test.tpl @@ -0,0 +1 @@ +${foo[$bar[baz]][${foo[b]}bla]}
\ No newline at end of file diff --git a/tests/test_array/want b/tests/test_array/want new file mode 100644 index 0000000..d86bac9 --- /dev/null +++ b/tests/test_array/want @@ -0,0 +1 @@ +OK diff --git a/tests/test_blocks/.gitignore b/tests/test_blocks/.gitignore new file mode 100644 index 0000000..de2a41b --- /dev/null +++ b/tests/test_blocks/.gitignore @@ -0,0 +1,3 @@ +have +*.ast +*.transc.php diff --git a/tests/test_blocks/code.php b/tests/test_blocks/code.php new file mode 100644 index 0000000..de8c553 --- /dev/null +++ b/tests/test_blocks/code.php @@ -0,0 +1,5 @@ +<?php + +function test_func($ste) { + +} diff --git a/tests/test_blocks/master.tpl b/tests/test_blocks/master.tpl new file mode 100644 index 0000000..a9608bd --- /dev/null +++ b/tests/test_blocks/master.tpl @@ -0,0 +1,3 @@ +Foo +<ste:block name="blk1">Bar</ste:block> +<ste:block name="blk2">Baz</ste:block>
\ No newline at end of file diff --git a/tests/test_blocks/test.tpl b/tests/test_blocks/test.tpl new file mode 100644 index 0000000..44c6891 --- /dev/null +++ b/tests/test_blocks/test.tpl @@ -0,0 +1,2 @@ +<ste:load name="master.tpl" /> +<ste:block name="blk1">Replaced</ste:block>
\ No newline at end of file diff --git a/tests/test_blocks/want b/tests/test_blocks/want new file mode 100644 index 0000000..640cfef --- /dev/null +++ b/tests/test_blocks/want @@ -0,0 +1,3 @@ +Foo +Replaced +Baz 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 @@ +<?php + +function test_func($ste) { + $ste->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} +<ste:my_echo text="foo\\\"bar\$" /> +<ste:my_echo text='\'' /> +\'
\ 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$ +' +\' diff --git a/tests/test_loop/.gitignore b/tests/test_loop/.gitignore new file mode 100644 index 0000000..de2a41b --- /dev/null +++ b/tests/test_loop/.gitignore @@ -0,0 +1,3 @@ +have +*.ast +*.transc.php diff --git a/tests/test_loop/code.php b/tests/test_loop/code.php new file mode 100644 index 0000000..de8c553 --- /dev/null +++ b/tests/test_loop/code.php @@ -0,0 +1,5 @@ +<?php + +function test_func($ste) { + +} diff --git a/tests/test_loop/test.tpl b/tests/test_loop/test.tpl new file mode 100644 index 0000000..399f05a --- /dev/null +++ b/tests/test_loop/test.tpl @@ -0,0 +1,3 @@ +<ste:for start="10" stop="0" step="-1" counter="i"> +$i +</ste:for>
\ No newline at end of file diff --git a/tests/test_loop/want b/tests/test_loop/want new file mode 100644 index 0000000..30b148d --- /dev/null +++ b/tests/test_loop/want @@ -0,0 +1,11 @@ +10 +9 +8 +7 +6 +5 +4 +3 +2 +1 +0 diff --git a/tests/test_mktag/.gitignore b/tests/test_mktag/.gitignore new file mode 100644 index 0000000..de2a41b --- /dev/null +++ b/tests/test_mktag/.gitignore @@ -0,0 +1,3 @@ +have +*.ast +*.transc.php diff --git a/tests/test_mktag/code.php b/tests/test_mktag/code.php new file mode 100644 index 0000000..de8c553 --- /dev/null +++ b/tests/test_mktag/code.php @@ -0,0 +1,5 @@ +<?php + +function test_func($ste) { + +} diff --git a/tests/test_mktag/test.tpl b/tests/test_mktag/test.tpl new file mode 100644 index 0000000..c381ef4 --- /dev/null +++ b/tests/test_mktag/test.tpl @@ -0,0 +1,12 @@ +<ste:mktag name="double"><ste:calc>2 * <ste:tagcontent /></ste:calc></ste:mktag> +<ste:mktag name="foo" mandatory="a|b"> + <ste:set var="b">$_tag_parameters[b]</ste:set><ste:comment>Ugly hack, since STE does no scoping...</ste:comment> + <ste:for counter="i" start="0" stop="$_tag_parameters[a]"> + <ste:if> + <ste:even>$i</ste:even> + <ste:then><ste:double>$i</ste:double></ste:then> + <ste:else>$b</ste:else> + </ste:if> + </ste:for> +</ste:mktag> +<ste:foo a="10" b="-" />
\ No newline at end of file diff --git a/tests/test_mktag/want b/tests/test_mktag/want new file mode 100644 index 0000000..3e2351c --- /dev/null +++ b/tests/test_mktag/want @@ -0,0 +1,11 @@ +0 +- +4 +- +8 +- +12 +- +16 +- +20 diff --git a/tests/test_pseudotags/.gitignore b/tests/test_pseudotags/.gitignore new file mode 100644 index 0000000..de2a41b --- /dev/null +++ b/tests/test_pseudotags/.gitignore @@ -0,0 +1,3 @@ +have +*.ast +*.transc.php diff --git a/tests/test_pseudotags/code.php b/tests/test_pseudotags/code.php new file mode 100644 index 0000000..de8c553 --- /dev/null +++ b/tests/test_pseudotags/code.php @@ -0,0 +1,5 @@ +<?php + +function test_func($ste) { + +} diff --git a/tests/test_pseudotags/test.tpl b/tests/test_pseudotags/test.tpl new file mode 100644 index 0000000..756be8a --- /dev/null +++ b/tests/test_pseudotags/test.tpl @@ -0,0 +1,2 @@ +<ste:comment><ste:comment><ste:foo>Ignore $me<ste:rawtext>Foo</ste:rawtext></ste:comment> +<ste:rawtext><ste:rawtext>$foo bar <ste:bla a="$b" /></ste:rawtext>
\ No newline at end of file diff --git a/tests/test_pseudotags/want b/tests/test_pseudotags/want new file mode 100644 index 0000000..3aaffdc --- /dev/null +++ b/tests/test_pseudotags/want @@ -0,0 +1 @@ +<ste:rawtext>$foo bar <ste:bla a="$b" /> diff --git a/tests/test_short/.gitignore b/tests/test_short/.gitignore new file mode 100644 index 0000000..de2a41b --- /dev/null +++ b/tests/test_short/.gitignore @@ -0,0 +1,3 @@ +have +*.ast +*.transc.php diff --git a/tests/test_short/code.php b/tests/test_short/code.php new file mode 100644 index 0000000..de8c553 --- /dev/null +++ b/tests/test_short/code.php @@ -0,0 +1,5 @@ +<?php + +function test_func($ste) { + +} diff --git a/tests/test_short/test.tpl b/tests/test_short/test.tpl new file mode 100644 index 0000000..42d498b --- /dev/null +++ b/tests/test_short/test.tpl @@ -0,0 +1 @@ +?{~{foo|eq|bar}|FAIL|?{~{~{1|gt|2}|?{y|eq|neq}|~{3|lt|2}}|OK|FAIL}}
\ No newline at end of file diff --git a/tests/test_short/want b/tests/test_short/want new file mode 100644 index 0000000..d86bac9 --- /dev/null +++ b/tests/test_short/want @@ -0,0 +1 @@ +OK diff --git a/tests/test_simple/.gitignore b/tests/test_simple/.gitignore new file mode 100644 index 0000000..de2a41b --- /dev/null +++ b/tests/test_simple/.gitignore @@ -0,0 +1,3 @@ +have +*.ast +*.transc.php diff --git a/tests/test_simple/code.php b/tests/test_simple/code.php new file mode 100644 index 0000000..4c0babc --- /dev/null +++ b/tests/test_simple/code.php @@ -0,0 +1,5 @@ +<?php + +function test_func($ste) { + $ste->vars["foo"] = "World"; +} diff --git a/tests/test_simple/test.tpl b/tests/test_simple/test.tpl new file mode 100644 index 0000000..30a4f22 --- /dev/null +++ b/tests/test_simple/test.tpl @@ -0,0 +1 @@ +Hello $foo!
\ No newline at end of file diff --git a/tests/test_simple/want b/tests/test_simple/want new file mode 100644 index 0000000..980a0d5 --- /dev/null +++ b/tests/test_simple/want @@ -0,0 +1 @@ +Hello World! |