From 275b09a080ac9cd02011ff827554630b4fc6f544 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Mon, 26 Sep 2011 00:10:07 +0200 Subject: Better error messages. Also corrected some minor bugs and typos. * Error messages now include the name of the template and the line where the error occurred. * `instanceof Text` is no longer valid, must be `instanceof TextNode`. * The Precompiler is now a own function. * ste:if subcompiler fixed. --- README.markdown | 3 - stupid_template_engine.php | 195 ++++++++++++++++++++++++++++++++------------- 2 files changed, 138 insertions(+), 60 deletions(-) diff --git a/README.markdown b/README.markdown index bfaa4ad..37c10ae 100644 --- a/README.markdown +++ b/README.markdown @@ -24,9 +24,6 @@ Why should you use it? Annoying things. ---------------- -* The error messages the parser returns, when your template has errors, are not - very helpful, i.e. the position of the error is not returned. You have to seek - it yourself. * No caching. Could be slow on websites with many hits. WARNING diff --git a/stupid_template_engine.php b/stupid_template_engine.php index e84bf67..b88cb3f 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -15,19 +15,30 @@ */ namespace ste; -class TextNode +abstract class ASTNode +{ + public $tpl; + public $offset; + public function __construct($tpl, $off) + { + $this->tpl = $tpl; + $this->offset = $off; + } +} + +class TextNode extends ASTNode { public $text; } -class TagNode +class TagNode extends ASTNode { public $name; public $params; public $sub; } -class VariableNode +class VariableNode extends ASTNode { public $name; public $arrayfields; @@ -55,6 +66,28 @@ class VariableNode } } +class ParseCompileError extends \Exception +{ + public $msg; + public $tpl; + public $off; + + 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) + { + $line = substr_count(str_replace("\r\n", "\n", $code), "\n", 0, $this->off + 1) + 1; + $this->message = "{$this->msg} (Template $tpl, Line $line)"; + $this->is_rewritten = True; + } +} + /* $text must start after the first opening bracket */ function find_closing_bracket($text, $opening, $closing) { @@ -86,80 +119,98 @@ function unescape_text($text) return stripcslashes($text); } -function tokenize_text($text) +function tokenize_text($text, $tpl, $off) { $tokens = array(); /* Find next non-escaped $-char */ if(preg_match("/(?:(?text = preg_replace("/^\\n\\s*/s", "", unescape_text($text)); return (strlen($node->text) == 0) ? array() : array($node); } if($match[0][1] > 0) { - $node = new TextNode(); + $node = new TextNode($tpl, $off); $node->text = unescape_text(substr($text, 0, $match[0][1])); $tokens[] = $node; } if($text[$match[0][1] + 1] == "{") { - $varend = find_closing_bracket(substr($text, $match[0][1] + 2), "{", "}") + $match[0][1] + 2; + 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))), - tokenize_text(substr($text, $varend + 1)) + 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) ); } $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); - if($nexttokens[0] instanceof Text) + $nexttokens = tokenize_text($text, $tpl, $off); + if($nexttokens[0] instanceof TextNode) $nexttokens[0]->text = "\$" . $nexttokens[0]->text; else { - $node = new TextNode(); + $node = new TextNode($tpl, $off); $node->text = "\$"; $tokens[] = $node; } return array_merge($tokens, $nexttokens); } - $node = new VariableNode(); + $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); - $fieldend = find_closing_bracket($text, "[", "]"); - $node->arrayfields[] = tokenize_text(substr($text, 0, $fieldend)); + $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; } $tokens[] = $node; - return strlen($text) > 0 ? array_merge($tokens, tokenize_text($text)) : $tokens; + return strlen($text) > 0 ? array_merge($tokens, tokenize_text($text, $tpl, $off)) : $tokens; } -function mk_ast($code) +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); - - $ast = tokenize_text(substr($code, 0, $matches[0][1])); + return tokenize_text($code, $tpl, $err_off); - $tag = new TagNode(); + $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]); $tag->params = array(); @@ -168,14 +219,16 @@ function mk_ast($code) $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); + $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]); } if(preg_match("/^\\s*([\\/]?)\\s*\\>/s", $code, $matches) == 0) - throw new \Exception("Missing closing '>' in \"" . $tag->name . "\"-Tag. Stop. (:::CODE START:::$code:::CODE END:::)"); + throw new ParseCompileError("Parse Error: Missing closing '>' in \"" . $tag->name . "\"-Tag.", $tpl, $tag->offset); $code = substr($code, strlen($matches[0])); + $err_off += strlen($matches[0]); $tag->sub = array(); @@ -201,37 +254,39 @@ function mk_ast($code) } if(($tags_open != 0) or ($tag->name != $matches[2][0])) - throw new \Exception("Missing closing \"ste:" . $tag->name . "\"-Tag. Stop."); + throw new ParseCompileError("Parse Error: Missing closing \"ste:" . $tag->name . "\"-Tag.", $tpl, $tag->offset); if($tag->name == "rawtext") { - $tag = new TextNode(); + $tag = new TextNode($tpl, $err_off); $tag->text = substr($code, 0, $last_tag_start); } + else if($tag->name == "comment") + $tag = NULL; /* All this work to remove a comment ... */ else - $tag->sub = mk_ast(substr($code, 0, $last_tag_start)); + $tag->sub = mk_ast(substr($code, 0, $last_tag_start), $tpl, $err_off); $code = substr($code, $off); + $err_off += $off; } - $ast[] = $tag; - return array_merge($ast, strlen($code) > 0 ? mk_ast($code) : array()); + if($tag !== NULL) + $ast[] = $tag; + return array_merge($ast, strlen($code) > 0 ? mk_ast($code, $tpl, $err_off) : array()); } /* - * Function: parse - * Parsing a STE T/PL template. + * Function: precompile + * Precompiling STE T/PL templates. * You only need this function, if you want to manually transcompile a template. * * Parameters: - * $code - The STE T/PL code. + * $code - The input code * * Returns: - * An abstract syntax tree, whic can be used with . + * The precompiled code. */ -function parse($code) +function precompile($code) { - /* Precompiling... */ - $code = preg_replace("/\\<\\s*ste:comment\\s*\\>.*?\\<\\s*\\/\\s*ste:comment\\s*\\>/s", "", $code); /* Remove comments */ $code = preg_replace( /* Transform short form of comparison (~{a|op|b}) to long form */ "/(?:(?", @@ -244,13 +299,29 @@ function parse($code) ); /* Unescape \? \~ \{ \} \| */ $code = preg_replace("/(?:(?. + */ +function parse($code, $tpl) +{ + return mk_ast($code, $tpl, 0); } function indent_code($code) @@ -407,8 +478,8 @@ $ste_builtins = array( { $output = ""; $condition = array(); - $then = array(); - $else = array(); + $then = NULL; + $else = NULL; foreach($ast->sub as $node) { @@ -420,15 +491,15 @@ $ste_builtins = array( $condition[] = $node; } - if(empty($then)) - throw new \Exception("Transcompile error: Missing in . Stop."); + if($then === NULL) + throw new ParseCompileError("Transcompile error: Missing in .", $ast->tpl, $ast->offset); $output .= "\$outputstack[] = \"\";\n\$outputstack_i++;\n"; $output .= _transcompile($condition); $output .= "\$outputstack_i--;\nif(\$ste->evalbool(array_pop(\$outputstack)))\n{\n"; $output .= indent_code(_transcompile($then)); $output .= "\n}\n"; - if(!empty($else)) + if($else !== NULL) { $output .= "else\n{\n"; $output .= indent_code(_transcompile($else)); @@ -452,7 +523,7 @@ $ste_builtins = array( $b = 'array_pop($outputstack)'; } else - throw new \Exception("Transcompile error: neiter var_b nor text_b set in . Stop."); + throw new ParseCompileError("Transcompile error: neiter var_b nor text_b set in .", $ast->tpl, $ast->offset); $code .= "\$outputstack[] = '';\n\$outputstack_i++;\n"; if(isset($ast->params["var_a"])) @@ -466,13 +537,13 @@ $ste_builtins = array( $a = 'array_pop($outputstack)'; } else - throw new \Exception("Transcompile error: neiter var_a nor text_a set in . Stop."); + throw new ParseCompileError("Transcompile error: neiter var_a nor text_a set in .", $ast->tpl, $ast->offset); $code .= "\$outputstack[] = '';\n\$outputstack_i++;\n"; if(isset($ast->params["op"])) $code .= _transcompile($ast->params["op"]); else - throw new \Exception("Transcompile error: op not given in . Stop."); + throw new ParseCompileError("Transcompile error: op not given in .", $ast->tpl, $ast->offset); $code .= "\$outputstack_i -= 3;\nswitch(trim(array_pop(\$outputstack)))\n{\n\t"; $code .= implode("", array_map( @@ -512,13 +583,13 @@ $ste_builtins = array( $code = ""; $loopname = "forloop_" . str_replace(".", "_", uniqid("",True)); if(empty($ast->params["start"])) - throw new \Exception("Transcompile error: Missing 'start' parameter in . Stop."); + throw new ParseCompileError("Transcompile error: Missing 'start' parameter in .", $ast->tpl, $ast->offset); $code .= "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->params["start"]); $code .= "\$outputstack_i--;\n\$${loopname}_start = array_pop(\$outputstack);\n"; if(empty($ast->params["stop"])) - throw new \Exception("Transcompile error: Missing 'end' parameter in . Stop."); + throw new ParseCompileError("Transcompile error: Missing 'end' parameter in .", $ast->tpl, $ast->offset); $code .= "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->params["stop"]); $code .= "\$outputstack_i--;\n\$${loopname}_stop = array_pop(\$outputstack);\n"; @@ -560,13 +631,13 @@ $ste_builtins = array( $code = ""; if(empty($ast->params["array"])) - throw new \Exception("Transcompile Error: array not givein in . Stop."); + throw new ParseCompileError("Transcompile Error: array not given in .", $ast->tpl, $ast->offset); $code .= "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->params["array"]); $code .= "\$outputstack_i--;\n\$${loopname}_arrayvar = array_pop(\$outputstack);\n"; if(empty($ast->params["value"])) - throw new \Exception("Transcompile Error: value not givein in . Stop."); + throw new ParseCompileError("Transcompile Error: value not given in .", $ast->tpl, $ast->offset); $code .= "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->params["value"]); $code .= "\$outputstack_i--;\n\$${loopname}_valuevar = array_pop(\$outputstack);\n"; @@ -616,8 +687,8 @@ $ste_builtins = array( }, "block" => function($ast) { - if(empty($ast->name)) - throw new \Exception("Transcompile Error: name missing in . Stop."); + if(empty($ast->params["name"])) + throw new ParseCompileError("Transcompile Error: name missing in .", $ast->tpl, $ast->offset); $blknamevar = "blockname_" . str_replace(".", "_", uniqid("", True)); @@ -638,7 +709,7 @@ $ste_builtins = array( "load" => function($ast) { if(empty($ast->params["name"])) - throw new \Exception("Transcompile Error: name missing in . Stop."); + throw new ParseCompileError("Transcompile Error: name missing in .", $ast->tpl, $ast->offset); $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->params["name"]); @@ -649,7 +720,7 @@ $ste_builtins = array( "mktag" => function($ast) { if(empty($ast->params["name"])) - throw new \Exception("Transcompile Error: name missing in . Stop."); + throw new ParseCompileError("Transcompile Error: name missing in .", $ast->tpl, $ast->offset); $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->params["name"]); @@ -681,7 +752,7 @@ $ste_builtins = array( "set" => function($ast) { if(empty($ast->params["var"])) - throw new \Exception("Transcompile Error: var missing in . Stop."); + throw new ParseCompileError("Transcompile Error: var missing in .", $ast->tpl, $ast->offset); $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->params["var"]); @@ -749,6 +820,7 @@ $ste_transc_boilerplate = "\$outputstack = array('');\n\$outputstack_i = 0;\n"; /* * Function: transcompile * Transcompiles an abstract syntax tree to PHP. + * You only need this function, if you want to manually transcompile a template. * * Parameters: * $ast - The abstract syntax tree to transcompile. @@ -1112,8 +1184,17 @@ class STECore $content = $this->storage_access->load($tpl, $mode); if($mode == MODE_SOURCE) { - $ast = parse($content); - $transc = transcompile($ast); + $content = precompile($content); + try + { + $ast = parse($content, $tpl, 0); + $transc = transcompile($ast); + } + catch(ParseCompileError $e) + { + $e->rewrite($content); + throw $e; + } $this->storage_access->save($tpl, $transc, MODE_TRANSCOMPILED); eval("\$content = $transc;"); } -- cgit v1.2.3-54-g00ecf