From bfd4763b4f831ed9eccbdd4717c98b7d95999d2e Mon Sep 17 00:00:00 2001 From: Laria Carolin Chabowski Date: Sun, 26 Apr 2020 16:48:01 +0200 Subject: Some simple code formatting - Expand tabs into spaces - Remove trailing whitespace - Get rid of closing `?>` tags --- example/index.php | 56 ++- src/ste/ASTNode.php | 12 +- src/ste/Calc.php | 262 +++++------ src/ste/FilesystemStorageAccess.php | 112 ++--- src/ste/Misc.php | 6 +- src/ste/ParseCompileError.php | 32 +- src/ste/Parser.php | 890 +++++++++++++++++------------------ src/ste/STECore.php | 586 +++++++++++------------ src/ste/STEStandardLibrary.php | 286 +++++------ src/ste/Scope.php | 280 +++++------ src/ste/StorageAccess.php | 80 ++-- src/ste/TagNode.php | 14 +- src/ste/TextNode.php | 10 +- src/ste/Transcompiler.php | 914 ++++++++++++++++++------------------ src/ste/VariableNode.php | 38 +- steloader.php | 8 +- tests/test.php | 24 +- tests/test_array/code.php | 18 +- tests/test_blocks/code.php | 2 +- tests/test_closure/code.php | 2 +- tests/test_escapes/code.php | 6 +- tests/test_foreach/code.php | 10 +- tests/test_getset/code.php | 2 +- tests/test_loop/code.php | 2 +- tests/test_mktag/code.php | 2 +- tests/test_pseudotags/code.php | 2 +- tests/test_recursive/code.php | 2 +- tests/test_scoping/code.php | 2 +- tests/test_short/code.php | 2 +- tests/test_simple/code.php | 2 +- 30 files changed, 1831 insertions(+), 1833 deletions(-) diff --git a/example/index.php b/example/index.php index a0aa03c..bd03a43 100644 --- a/example/index.php +++ b/example/index.php @@ -7,10 +7,10 @@ use \kch42\ste; # Initialize an STECore instance $ste = new ste\STECore( - new ste\FilesystemStorageAccess( # The STECore needs a StorageAccess implementation, we are using the FilesystemStorageAccess, which comes with STE. - dirname(__FILE__) . "/templates/src", # FilesystemStorageAccess needs a directory, where the Templates are... - dirname(__FILE__) . "/templates/transc" # ...and a directory for the transcompiled templates (write permissions needed). - ) + new ste\FilesystemStorageAccess( # The STECore needs a StorageAccess implementation, we are using the FilesystemStorageAccess, which comes with STE. + dirname(__FILE__) . "/templates/src", # FilesystemStorageAccess needs a directory, where the Templates are... + dirname(__FILE__) . "/templates/transc" # ...and a directory for the transcompiled templates (write permissions needed). + ) ); # Set STE to a more verbose behavior: @@ -20,40 +20,40 @@ $ste->mute_runtime_errors = False; # will exchange all letters with their uppercase complement $ste->register_tag("uppercase", - function($ste, $params, $sub) - { - $text = $sub($ste); # Get the tags content - return strtoupper($text); # Return the new text. - } + function($ste, $params, $sub) + { + $text = $sub($ste); # Get the tags content + return strtoupper($text); # Return the new text. + } ); # will repeat its content n times ( could be used too, but i needed more examples :-P ) $ste->register_tag("repeat", - function($ste, $params, $sub) - { - $output = ""; - if(!is_numeric($params["n"])) - throw new ste\RuntimeError("Sorry, but parameter n must be a number..."); - - for($i = 0; $i < $params["n"]; ++$i) - $output .= $sub($ste); - - return $output; - } + function($ste, $params, $sub) + { + $output = ""; + if(!is_numeric($params["n"])) + throw new ste\RuntimeError("Sorry, but parameter n must be a number..."); + + for($i = 0; $i < $params["n"]; ++$i) + $output .= $sub($ste); + + return $output; + } ); # assign some data $ste->vars["users"] = array( - array("name" => "Foo", "username" => "foo", "online" => true), - array("name" => "Bar", "username" => "bar", "online" => false), - array("name" => "Baz", "username" => "baz", "online" => true) + array("name" => "Foo", "username" => "foo", "online" => true), + array("name" => "Bar", "username" => "bar", "online" => false), + array("name" => "Baz", "username" => "baz", "online" => true) ); $ste->vars["title"] = "cool"; $ste->vars["articles"] = array( - array("author" => "foo", "title" => "cool article", "timestamp" => 1316553353, "excerpt" => "bla", "full" => "blablabla"), - array("author" => "bar", "title" => "awesome", "timestamp" => 1316552000, "excerpt" => "...", "full" => ".........."), - array("author" => "baz", "title" => " 1316551000, "excerpt" => "...", "full" => ".........."), - array("author" => "baz", "title" => "whatever...", "timestamp" => 1316550000, "excerpt" => "...", "full" => "..........") + array("author" => "foo", "title" => "cool article", "timestamp" => 1316553353, "excerpt" => "bla", "full" => "blablabla"), + array("author" => "bar", "title" => "awesome", "timestamp" => 1316552000, "excerpt" => "...", "full" => ".........."), + array("author" => "baz", "title" => " 1316551000, "excerpt" => "...", "full" => ".........."), + array("author" => "baz", "title" => "whatever...", "timestamp" => 1316550000, "excerpt" => "...", "full" => "..........") ); $ste->vars["foo"] = "baz"; @@ -62,5 +62,3 @@ $ste->vars["baz"] = array("lol" => "cool"); # Execute the template and output the result echo $ste->exectemplate("articles.html"); - -?> diff --git a/src/ste/ASTNode.php b/src/ste/ASTNode.php index fc5d9cc..ea40bac 100644 --- a/src/ste/ASTNode.php +++ b/src/ste/ASTNode.php @@ -3,10 +3,10 @@ namespace kch42\ste; abstract class ASTNode { - public $tpl; - public $offset; - public function __construct($tpl, $off) { - $this->tpl = $tpl; - $this->offset = $off; - } + public $tpl; + public $offset; + public function __construct($tpl, $off) { + $this->tpl = $tpl; + $this->offset = $off; + } } diff --git a/src/ste/Calc.php b/src/ste/Calc.php index e8dba8d..e700c4f 100644 --- a/src/ste/Calc.php +++ b/src/ste/Calc.php @@ -4,135 +4,135 @@ namespace kch42\ste; /* Class Calc contains static methods needed by */ class Calc { - private function __construct() {} - - /* We could also just eval() the $infix_math code, but this is much cooler :-D (Parser inception) */ - public static function shunting_yard($infix_math) { - $operators = array( - "+" => array("l", 2), - "-" => array("l", 2), - "*" => array("l", 3), - "/" => array("l", 3), - "^" => array("r", 4), - "_" => array("r", 5), - "(" => array("", 0), - ")" => array("", 0) - ); - - 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") || (!empty($x)); }); - $output_queue = array(); - $op_stack = array(); - - $lastpriority = NULL; - /* Make - unary, if neccessary */ - $tokens = array(); - foreach($tokens_raw as $token) { - $priority = isset($operators[$token]) ? $operators[$token][1] : -1; - if(($token == "-") && (($lastpriority === NULL) || ($lastpriority >= 0))) { - $priority = $operators["_"][1]; - $tokens[] = "_"; - } else { - $tokens[] = $token; - } - $lastpriority = $priority; - } - - while(!empty($tokens)) { - $token = array_shift($tokens); - if(is_numeric($token)) { - $output_queue[] = $token; - } else if($token == "(") { - $op_stack[] = $token; - } else if($token == ")") { - $lbr_found = false; - while(!empty($op_stack)) { - $op = array_pop($op_stack); - if($op == "(") { - $lbr_found = true; - break; - } - $output_queue[] = $op; - } - if(!$lbr_found) { - throw new RuntimeError("Bracket mismatch."); - } - } else if(!isset($operators[$token])) { - throw new RuntimeError("Invalid token ($token): Not a number, bracket or operator. Stop."); - } else { - $priority = $operators[$token][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])) { - $output_queue[] = array_pop($op_stack); - } - } - $op_stack[] = $token; - } - } - - while(!empty($op_stack)) { - $op = array_pop($op_stack); - if($op == "(") { - throw new RuntimeError("Bracket mismatch..."); - } - $output_queue[] = $op; - } - - return $output_queue; - } - - public static function pop2(&$array) { - $rv = array(array_pop($array), array_pop($array)); - if(array_search(NULL, $rv, true) !== false) { - throw new RuntimeError("Not enough numbers on stack. Invalid formula."); - } - return $rv; - } - - public static function calc_rpn($rpn) { - $stack = array(); - foreach($rpn as $token) { - switch($token) { - case "+": - list($b, $a) = self::pop2($stack); - $stack[] = $a + $b; - break; - case "-": - list($b, $a) = self::pop2($stack); - $stack[] = $a - $b; - break; - case "*": - list($b, $a) = self::pop2($stack); - $stack[] = $a * $b; - break; - case "/": - list($b, $a) = self::pop2($stack); - $stack[] = $a / $b; - break; - case "^": - list($b, $a) = self::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); - } - - public static function calc($expr) { - return self::calc_rpn(self::shunting_yard($expr)); - } + private function __construct() {} + + /* We could also just eval() the $infix_math code, but this is much cooler :-D (Parser inception) */ + public static function shunting_yard($infix_math) { + $operators = array( + "+" => array("l", 2), + "-" => array("l", 2), + "*" => array("l", 3), + "/" => array("l", 3), + "^" => array("r", 4), + "_" => array("r", 5), + "(" => array("", 0), + ")" => array("", 0) + ); + + 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") || (!empty($x)); }); + $output_queue = array(); + $op_stack = array(); + + $lastpriority = NULL; + /* Make - unary, if neccessary */ + $tokens = array(); + foreach($tokens_raw as $token) { + $priority = isset($operators[$token]) ? $operators[$token][1] : -1; + if(($token == "-") && (($lastpriority === NULL) || ($lastpriority >= 0))) { + $priority = $operators["_"][1]; + $tokens[] = "_"; + } else { + $tokens[] = $token; + } + $lastpriority = $priority; + } + + while(!empty($tokens)) { + $token = array_shift($tokens); + if(is_numeric($token)) { + $output_queue[] = $token; + } else if($token == "(") { + $op_stack[] = $token; + } else if($token == ")") { + $lbr_found = false; + while(!empty($op_stack)) { + $op = array_pop($op_stack); + if($op == "(") { + $lbr_found = true; + break; + } + $output_queue[] = $op; + } + if(!$lbr_found) { + throw new RuntimeError("Bracket mismatch."); + } + } else if(!isset($operators[$token])) { + throw new RuntimeError("Invalid token ($token): Not a number, bracket or operator. Stop."); + } else { + $priority = $operators[$token][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])) { + $output_queue[] = array_pop($op_stack); + } + } + $op_stack[] = $token; + } + } + + while(!empty($op_stack)) { + $op = array_pop($op_stack); + if($op == "(") { + throw new RuntimeError("Bracket mismatch..."); + } + $output_queue[] = $op; + } + + return $output_queue; + } + + public static function pop2(&$array) { + $rv = array(array_pop($array), array_pop($array)); + if(array_search(NULL, $rv, true) !== false) { + throw new RuntimeError("Not enough numbers on stack. Invalid formula."); + } + return $rv; + } + + public static function calc_rpn($rpn) { + $stack = array(); + foreach($rpn as $token) { + switch($token) { + case "+": + list($b, $a) = self::pop2($stack); + $stack[] = $a + $b; + break; + case "-": + list($b, $a) = self::pop2($stack); + $stack[] = $a - $b; + break; + case "*": + list($b, $a) = self::pop2($stack); + $stack[] = $a * $b; + break; + case "/": + list($b, $a) = self::pop2($stack); + $stack[] = $a / $b; + break; + case "^": + list($b, $a) = self::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); + } + + public static function calc($expr) { + return self::calc_rpn(self::shunting_yard($expr)); + } } \ No newline at end of file diff --git a/src/ste/FilesystemStorageAccess.php b/src/ste/FilesystemStorageAccess.php index cdbce8b..d2e5e7f 100644 --- a/src/ste/FilesystemStorageAccess.php +++ b/src/ste/FilesystemStorageAccess.php @@ -10,60 +10,60 @@ namespace kch42\ste; * The default implementation for loading / saving templates into a directory structure. */ class FilesystemStorageAccess implements StorageAccess { - protected $sourcedir; - protected $transcompileddir; - - /* - * Constructor: __construct - * - * Parameters: - * $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) { - $this->sourcedir = $src; - $this->transcompileddir = $transc; - } - - public function load($tpl, &$mode) { - $src_fn = $this->sourcedir . "/" . $tpl; - $transc_fn = $this->transcompileddir . "/" . $tpl . ".php"; - - if($mode == StorageAccess::MODE_SOURCE) { - $content = @file_get_contents($src_fn); - 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)) { - throw new CantLoadTemplate("Template not found."); - } else if($transc_stat === false) { - $mode = StorageAccess::MODE_SOURCE; - return file_get_contents($src_fn); - } else if($src_stat === false) { - include($transc_fn); - return $transcompile_fx; - } else { - if($src_stat["mtime"] > $transc_stat["mtime"]) { - $mode = StorageAccess::MODE_SOURCE; - return file_get_contents($src_fn); - } else { - include($transc_fn); - return $transcompile_fx; - } - } - } - - public function save($tpl, $data, $mode) { - $fn = (($mode == StorageAccess::MODE_SOURCE) ? $this->sourcedir : $this->transcompileddir) . "/" . $tpl . (($mode == StorageAccess::MODE_TRANSCOMPILED) ? ".php" : ""); - @mkdir(dirname($fn), 0777, true); - if(file_put_contents($fn, "") === false) { - throw new CantSaveTemplate("Unable to save template."); - } - } + protected $sourcedir; + protected $transcompileddir; + + /* + * Constructor: __construct + * + * Parameters: + * $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) { + $this->sourcedir = $src; + $this->transcompileddir = $transc; + } + + public function load($tpl, &$mode) { + $src_fn = $this->sourcedir . "/" . $tpl; + $transc_fn = $this->transcompileddir . "/" . $tpl . ".php"; + + if($mode == StorageAccess::MODE_SOURCE) { + $content = @file_get_contents($src_fn); + 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)) { + throw new CantLoadTemplate("Template not found."); + } else if($transc_stat === false) { + $mode = StorageAccess::MODE_SOURCE; + return file_get_contents($src_fn); + } else if($src_stat === false) { + include($transc_fn); + return $transcompile_fx; + } else { + if($src_stat["mtime"] > $transc_stat["mtime"]) { + $mode = StorageAccess::MODE_SOURCE; + return file_get_contents($src_fn); + } else { + include($transc_fn); + return $transcompile_fx; + } + } + } + + public function save($tpl, $data, $mode) { + $fn = (($mode == StorageAccess::MODE_SOURCE) ? $this->sourcedir : $this->transcompileddir) . "/" . $tpl . (($mode == StorageAccess::MODE_TRANSCOMPILED) ? ".php" : ""); + @mkdir(dirname($fn), 0777, true); + if(file_put_contents($fn, "") === false) { + throw new CantSaveTemplate("Unable to save template."); + } + } } diff --git a/src/ste/Misc.php b/src/ste/Misc.php index 56ae759..39ad30c 100644 --- a/src/ste/Misc.php +++ b/src/ste/Misc.php @@ -3,7 +3,7 @@ namespace kch42\ste; class Misc { - public static function escape_text($text) { - return addcslashes($text, "\r\n\t\$\0..\x1f\\\"\x7f..\xff"); - } + public static function escape_text($text) { + return addcslashes($text, "\r\n\t\$\0..\x1f\\\"\x7f..\xff"); + } } diff --git a/src/ste/ParseCompileError.php b/src/ste/ParseCompileError.php index 5ee37c7..33fd612 100644 --- a/src/ste/ParseCompileError.php +++ b/src/ste/ParseCompileError.php @@ -3,20 +3,20 @@ namespace kch42\ste; 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", substr($code, 0, $this->off)), "\n") + 1; - $this->message = "{$this->msg} (Template {$this->tpl}, Line $line)"; - $this->is_rewritten = true; - } + 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", substr($code, 0, $this->off)), "\n") + 1; + $this->message = "{$this->msg} (Template {$this->tpl}, Line $line)"; + $this->is_rewritten = true; + } } diff --git a/src/ste/Parser.php b/src/ste/Parser.php index 7e93e47..5e5ef0a 100644 --- a/src/ste/Parser.php +++ b/src/ste/Parser.php @@ -11,449 +11,449 @@ namespace kch42\ste; * Use the static method parse. */ class Parser { - private $text; - private $name; - private $off; - private $len; - - const PARSE_SHORT = 1; - const PARSE_TAG = 2; - - const ESCAPES_DEFAULT = '$?~{}|\\'; - - private function __construct($text, $name) { - $this->text = $text; - $this->name = $name; - $this->off = 0; - $this->len = mb_strlen($text); - } - - 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; - } - - private function eof() { - return ($this->off == $this->len); - } - - private function back($n = 1) { - if($n <= 0) { - throw new \InvalidArgumentException("\$n must be > 0"); - } - $this->off = max($this->off - $n, 0); - } - - private function search_off($needle) { - return mb_strpos($this->text, $needle, $this->off); - } - - 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; - } - - 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); - } - - 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); - } - - private function take_while($cb) { - $s = ""; - while($c = $this->next()) { - if(!call_user_func($cb, $c)) { - $this->back(); - return $s; - } - $s .= $c; - } - return $s; - } - - private function skip_ws() { - $this->take_while("ctype_space"); - } - - 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; - } - - /* - * Function: parse - * Parses the input into an AST. - * - * You only need this function, if you want to manually trnascompile a template. - * - * Parameters: - * $text - The input code. - * $name - The name of the template. - * - * Returns: - * An array of objects. - * - * Throws: - * - */ - public static function parse($text, $name) { - $obj = new self($text, $name); - $res = $obj->parse_text( - self::ESCAPES_DEFAULT, /* Escapes */ - self::PARSE_SHORT | self::PARSE_TAG /* Flags */ - ); - return self::tidyup_ast($res[0]); - } - - private static function tidyup_ast($ast) { - $out = array(); - - $prevtext = NULL; - $first = true; - - foreach($ast as $node) { - if($node instanceof 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; - } - } - $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; - } - } - - 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" => "", - "rawopen" => "", - "escape" => '\\', - "varcurlyopen" => '${', - "var" => '$', - ); - - if($flags & self::PARSE_TAG) { - $needles["tagopen"] = 'search_multi($needles); - - $astlist[] = new TextNode($this->name, $offbefore, $before); - - switch($which) { - case NULL: - if($nullaction === NULL) { - $elems[] = $astlist; - return $elems; - } else { - call_user_func($nullaction); - } - break; - case "commentopen": - list($off, $before, $offbefore) = $this->search(""); - if($off === false) { - throw new ParseCompileError("ste:comment was not closed", $this->name, $offbefore); - } - break; - case "rawopen": - $off_start = $off; - list($off, $before, $offbefore) = $this->search(""); - if($off === false) { - throw new ParseCompileError("ste:rawtext was not closed", $this->name, $off_start); - } - $astlist[] = new TextNode($this->name, $off_start, $before); - break; - case "tagopen": - $astlist[] = $this->parse_tag($off); - break; - case "closetagopen": - $off_start = $off; - $name = $this->get_name(); - $this->skip_ws(); - $off = $this->off; - if($this->next() != ">") { - throw new ParseCompileError("Expected '>' in closing ste-Tag", $this->name, $off); - } - - if($opentag === NULL) { - throw new ParseCompileError("Found closing ste:$name tag, but no tag was opened", $this->name, $off_start); - } - if($opentag != $name) { - throw new ParseCompileError("Open ste:$opentag was not closed", $this->name, $openedat); - } - - $elems[] = $astlist; - return $elems; - case "escape": - $c = $this->next(); - if(mb_strpos($escapes, $c) !== false) { - $astlist[] = new TextNode($this->name, $off, $c); - } else { - $astlist[] = new TextNode($this->name, $off, '\\'); - $this->back(); - } - 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; - } - } - - $elems[] = $astlist; - return $elems; - } - - private function parse_short($shortname, $openedat) { - $tplname = $this->name; - - 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 */ - ); - } - - private function parse_var($openedat, $curly) { - $varnode = new VariableNode($this->name, $openedat); - $varnode->name = $this->get_name(); - if(!$this->eof()) { - $varnode->arrayfields = $this->parse_array(); - } - - if(($curly) && ($this->next() != "}")) { - throw new ParseCompileError("Unclosed '\${'", $this->name, $openedat); - } - return $varnode; - } - - private function parse_array() { - $tplname = $this->name; - - $arrayfields = array(); - - while($this->next() == "[") { - $openedat = $this->off - 1; - $res = $this->parse_text( - self::ESCAPES_DEFAULT, /* Escapes */ - 0, /* Flags */ - ']', /* Break on */ - NULL, /* Separator */ - function() use ($tplname, $openedat) { /* NULL action */ - throw new ParseCompileError("Unclosed array access '[...]'", $tplname, $openedat); - }, - NULL, /* Open tag */ - $openedat /* Opened at */ - ); - $arrayfields[] = $res[0]; - } - - $this->back(); - return $arrayfields; - } - - private function parse_tag($openedat) { - $tplname = $this->name; - - $this->skip_ws(); - $tag = new TagNode($this->name, $openedat); - $name = $tag->name = $this->get_name(); - $tag->params = array(); - $tag->sub = array(); - - for(;;) { - $this->skip_ws(); - - switch($this->next()) { - case '/': /* Self-closing tag */ - $this->skip_ws(); - if($this->next() != '>') { - throw new ParseCompileError("Unclosed opening )", $this->name, $openedat); - } - - return $tag; - case '>': - $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]; - } - } - } + private $text; + private $name; + private $off; + private $len; + + const PARSE_SHORT = 1; + const PARSE_TAG = 2; + + const ESCAPES_DEFAULT = '$?~{}|\\'; + + private function __construct($text, $name) { + $this->text = $text; + $this->name = $name; + $this->off = 0; + $this->len = mb_strlen($text); + } + + 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; + } + + private function eof() { + return ($this->off == $this->len); + } + + private function back($n = 1) { + if($n <= 0) { + throw new \InvalidArgumentException("\$n must be > 0"); + } + $this->off = max($this->off - $n, 0); + } + + private function search_off($needle) { + return mb_strpos($this->text, $needle, $this->off); + } + + 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; + } + + 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); + } + + 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); + } + + private function take_while($cb) { + $s = ""; + while($c = $this->next()) { + if(!call_user_func($cb, $c)) { + $this->back(); + return $s; + } + $s .= $c; + } + return $s; + } + + private function skip_ws() { + $this->take_while("ctype_space"); + } + + 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; + } + + /* + * Function: parse + * Parses the input into an AST. + * + * You only need this function, if you want to manually trnascompile a template. + * + * Parameters: + * $text - The input code. + * $name - The name of the template. + * + * Returns: + * An array of objects. + * + * Throws: + * + */ + public static function parse($text, $name) { + $obj = new self($text, $name); + $res = $obj->parse_text( + self::ESCAPES_DEFAULT, /* Escapes */ + self::PARSE_SHORT | self::PARSE_TAG /* Flags */ + ); + return self::tidyup_ast($res[0]); + } + + private static function tidyup_ast($ast) { + $out = array(); + + $prevtext = NULL; + $first = true; + + foreach($ast as $node) { + if($node instanceof 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; + } + } + $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; + } + } + + 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" => "", + "rawopen" => "", + "escape" => '\\', + "varcurlyopen" => '${', + "var" => '$', + ); + + if($flags & self::PARSE_TAG) { + $needles["tagopen"] = 'search_multi($needles); + + $astlist[] = new TextNode($this->name, $offbefore, $before); + + switch($which) { + case NULL: + if($nullaction === NULL) { + $elems[] = $astlist; + return $elems; + } else { + call_user_func($nullaction); + } + break; + case "commentopen": + list($off, $before, $offbefore) = $this->search(""); + if($off === false) { + throw new ParseCompileError("ste:comment was not closed", $this->name, $offbefore); + } + break; + case "rawopen": + $off_start = $off; + list($off, $before, $offbefore) = $this->search(""); + if($off === false) { + throw new ParseCompileError("ste:rawtext was not closed", $this->name, $off_start); + } + $astlist[] = new TextNode($this->name, $off_start, $before); + break; + case "tagopen": + $astlist[] = $this->parse_tag($off); + break; + case "closetagopen": + $off_start = $off; + $name = $this->get_name(); + $this->skip_ws(); + $off = $this->off; + if($this->next() != ">") { + throw new ParseCompileError("Expected '>' in closing ste-Tag", $this->name, $off); + } + + if($opentag === NULL) { + throw new ParseCompileError("Found closing ste:$name tag, but no tag was opened", $this->name, $off_start); + } + if($opentag != $name) { + throw new ParseCompileError("Open ste:$opentag was not closed", $this->name, $openedat); + } + + $elems[] = $astlist; + return $elems; + case "escape": + $c = $this->next(); + if(mb_strpos($escapes, $c) !== false) { + $astlist[] = new TextNode($this->name, $off, $c); + } else { + $astlist[] = new TextNode($this->name, $off, '\\'); + $this->back(); + } + 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; + } + } + + $elems[] = $astlist; + return $elems; + } + + private function parse_short($shortname, $openedat) { + $tplname = $this->name; + + 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 */ + ); + } + + private function parse_var($openedat, $curly) { + $varnode = new VariableNode($this->name, $openedat); + $varnode->name = $this->get_name(); + if(!$this->eof()) { + $varnode->arrayfields = $this->parse_array(); + } + + if(($curly) && ($this->next() != "}")) { + throw new ParseCompileError("Unclosed '\${'", $this->name, $openedat); + } + return $varnode; + } + + private function parse_array() { + $tplname = $this->name; + + $arrayfields = array(); + + while($this->next() == "[") { + $openedat = $this->off - 1; + $res = $this->parse_text( + self::ESCAPES_DEFAULT, /* Escapes */ + 0, /* Flags */ + ']', /* Break on */ + NULL, /* Separator */ + function() use ($tplname, $openedat) { /* NULL action */ + throw new ParseCompileError("Unclosed array access '[...]'", $tplname, $openedat); + }, + NULL, /* Open tag */ + $openedat /* Opened at */ + ); + $arrayfields[] = $res[0]; + } + + $this->back(); + return $arrayfields; + } + + private function parse_tag($openedat) { + $tplname = $this->name; + + $this->skip_ws(); + $tag = new TagNode($this->name, $openedat); + $name = $tag->name = $this->get_name(); + $tag->params = array(); + $tag->sub = array(); + + for(;;) { + $this->skip_ws(); + + switch($this->next()) { + case '/': /* Self-closing tag */ + $this->skip_ws(); + if($this->next() != '>') { + throw new ParseCompileError("Unclosed opening )", $this->name, $openedat); + } + + return $tag; + case '>': + $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]; + } + } + } } diff --git a/src/ste/STECore.php b/src/ste/STECore.php index 35a92d5..297263a 100644 --- a/src/ste/STECore.php +++ b/src/ste/STECore.php @@ -10,297 +10,297 @@ namespace kch42\ste; * The Core of STE */ class STECore { - private $tags; - private $storage_access; - private $cur_tpl_dir; - public $scope; - - /* - * Variables: Public variables - * - * $blocks - Associative array of blocks (see the language definition). - * $blockorder - The order of the blocks (an array) - * $mute_runtime_errors - If true (default) a exception will result in no output from the tag, if false a error message will be written to output. - * $fatal_error_on_missing_tag - If true, STE will throw a if a tag was called that was not registered, otherwise (default) a regular will be thrown and automatically handled by STE (see <$mute_runtime_errors>). - * $vars - Variables in the top scope of the template. - */ - public $blocks; - public $blockorder; - public $mute_runtime_errors = true; - public $fatal_error_on_missing_tag = false; - public $vars; - - /* - * Constructor: __construct - * - * Parameters: - * $storage_access - An Instance of a implementation. - */ - public function __construct($storage_access) { - $this->storage_access = $storage_access; - $this->cur_tpl_dir = "/"; - STEStandardLibrary::_register_lib($this); - $this->blockorder = array(); - $this->blocks = array(); - - $this->vars = array(); - $this->scope = new Scope(); - $this->scope->vars =& $this->vars; - } - - /* - * Function: register_tag - * Register a custom tag. - * - * Parameters: - * $name - The name of the tag. - * $callback - A callable function (This must take three parameters: The instance, an associative array of parameters, and a function representing the tags content(This expects the instance as its only parameter and returns its text result, i.e to get the text, you neeed to call this function with the instance as a parameter)). - * - * 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)) { - throw new \Exception("Can not register tag \"$name\", not callable."); - } - if(empty($name)) { - throw new \Exception("Can not register tag, empty name."); - } - $this->tags[$name] = $callback; - } - - /* - * Function: call_tag - * Calling a custom tag (builtin ones can not be called) - * - * Parameters: - * $name - The Tag's name - * $params - Associative array of parameters - * $sub - A callable function (expecting an instance as it's parameter) that represents the tag's content. - * - * Throws: - * Might throw a (see <$fatal_error_on_missing_tag>. - * - * Returns: - * The output of the tag or, if a was thrown, the appropiate result (see <$mute_runtime_errors>). - */ - public function call_tag($name, $params, $sub) { - try { - if(!isset($this->tags[$name])) { - if($this->fatal_error_on_missing_tag) { - throw new FatalRuntimeError("Can not call tag \"$name\": Does not exist."); - } 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) { - return "RuntimeError occurred on tag '$name': " . $e->getMessage(); - } - } - } - - public function calc($expression) { - return Calc::calc($expression); - } - - /* - * Function: exectemplate - * Executes a template and returns the result. The huge difference to is that this function will also output all blocks. - * - * Parameters: - * $tpl - The name of the template to execute. - * - * Throws: - * * A exception if the template could not be loaded. - * * A if the template could not be parsed or transcompiled. - * * A if a tag threw it or if a tag was not found and <$fatal_error_on_missing_tag> is true. - * * Might also throw different exceptions, if a external tag threw it (but they should use or to make it possible for STE to handle them correctly). - * - * Returns: - * The output of the template. - */ - public function exectemplate($tpl) { - $output = ""; - $lastblock = $this->load($tpl); - - foreach($this->blockorder as $blockname) { - $output .= $this->blocks[$blockname]; - } - - return $output . $lastblock; - } - - /* - * Function: get_var_reference - * Get a reference to a template variable using a variable name. - * This can be used,if your custom tag takes a variable name as a parameter. - * - * Parameters: - * $name - The variables name. - * $create_if_not_exist - Should the variable be created, if it does not exist? Otherwise NULL will be returned, if the variable does not exist. - * - * Throws: - * if the variable name can not be parsed (e.g. unbalanced brackets). - * - * Returns: - * A Reference to the variable. - */ - public function &get_var_reference($name, $create_if_not_exist) { - $ref = &$this->scope->get_var_reference($name, $create_if_not_exist); - return $ref; - } - - /* - * Function: set_var_by_name - * Set a template variable by its name. - * This can be used,if your custom tag takes a variable name as a parameter. - * - * Parameters: - * $name - The variables name. - * $val - The new value. - * - * Throws: - * if the variable name can not be parsed (e.g. unbalanced brackets). - */ - public function set_var_by_name($name, $val) { - $this->scope->set_var_by_name($name, $val); - } - - /* - * Function: set_local_var - * Like , but only sets the variable in the global scope ( will overwrite the variable in the parent scope, if it's defined there) . - * - * Parameters: - * $name - The variables name. - * $val - The new value. - * - * Throws: - * if the variable name can not be parsed (e.g. unbalanced brackets). - */ - public function set_local_var($name, $val) { - $this->scope->set_local_var($name, $val); - } - - /* - * Function: get_var_by_name - * Get a template variable by its name. - * This can be used,if your custom tag takes a variable name as a parameter. - * - * Parameters: - * $name - The variables name. - * - * Throws: - * if the variable name can not be parsed (e.g. unbalanced brackets). - * - * Returns: - * The variables value. - */ - public function get_var_by_name($name) { - return $this->scope->get_var_by_name($name); - } - - /* - * Function: load - * Load a template and return its result (blocks not included, use for this). - * - * Parameters: - * $tpl - The name of the template to be loaded. - * $quiet - If true, do not output anything and do not modify the blocks. This can be useful to load custom tags that are programmed in the STE Template Language. Default: false. - * - * Throws: - * * A exception if the template could not be loaded. - * * A if the template could not be parsed or transcompiled. - * * A if a tag threw it or if a tag was not found and <$fatal_error_on_missing_tag> is true. - * * Might also throw different exceptions, if a external tag threw it (but they should use or to make it possible for STE to handle them correctly). - * - * Returns: - * The result of the template (if $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] != "/") { - $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) { - $pathex = array_slice($pathex, 1); - } 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) { - $blocks_back = clone $this->blocks; - $blockorder_back = clone $this->blockorder; - } - - $mode = StorageAccess::MODE_TRANSCOMPILED; - $content = $this->storage_access->load($tpl, $mode); - if($mode == StorageAccess::MODE_SOURCE) { - try { - $ast = Parser::parse($content, $tpl); - $transc = Transcompiler::transcompile($ast); - } catch(ParseCompileError $e) { - $e->rewrite($content); - throw $e; - } - $this->storage_access->save($tpl, $transc, StorageAccess::MODE_TRANSCOMPILED); - eval("\$content = $transc;"); - } - - $output = $content($this); - - $this->cur_tpl_dir = $tpldir_b4; - - if($quiet) { - $this->blocks = $blocks_back; - $this->blockorder = $blockorder_back; - } else { - return $output; - } - } - - /* - * Function: evalbool - * Test, if a text represents false (an empty / only whitespace text) or true (everything else). - * - * Parameters: - * $txt - The text to test. - * - * Returns: - * true/false. - */ - public function evalbool($txt) { - return trim(@(string)$txt) != ""; - } - - public function make_closure($fx) { - $bound_scope = $this->scope; - return function() use($bound_scope, $fx) { - $args = func_get_args(); - $ste = $args[0]; - - $prev = $ste->scope; - $scope = $bound_scope->new_subscope(); - $ste->scope = $scope; - - try { - $result = call_user_func_array($fx, $args); - $ste->scope = $prev; - return $result; - } catch(\Exception $e) { - $ste->scope = $prev; - throw $e; - } - }; - } + private $tags; + private $storage_access; + private $cur_tpl_dir; + public $scope; + + /* + * Variables: Public variables + * + * $blocks - Associative array of blocks (see the language definition). + * $blockorder - The order of the blocks (an array) + * $mute_runtime_errors - If true (default) a exception will result in no output from the tag, if false a error message will be written to output. + * $fatal_error_on_missing_tag - If true, STE will throw a if a tag was called that was not registered, otherwise (default) a regular will be thrown and automatically handled by STE (see <$mute_runtime_errors>). + * $vars - Variables in the top scope of the template. + */ + public $blocks; + public $blockorder; + public $mute_runtime_errors = true; + public $fatal_error_on_missing_tag = false; + public $vars; + + /* + * Constructor: __construct + * + * Parameters: + * $storage_access - An Instance of a implementation. + */ + public function __construct($storage_access) { + $this->storage_access = $storage_access; + $this->cur_tpl_dir = "/"; + STEStandardLibrary::_register_lib($this); + $this->blockorder = array(); + $this->blocks = array(); + + $this->vars = array(); + $this->scope = new Scope(); + $this->scope->vars =& $this->vars; + } + + /* + * Function: register_tag + * Register a custom tag. + * + * Parameters: + * $name - The name of the tag. + * $callback - A callable function (This must take three parameters: The instance, an associative array of parameters, and a function representing the tags content(This expects the instance as its only parameter and returns its text result, i.e to get the text, you neeed to call this function with the instance as a parameter)). + * + * 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)) { + throw new \Exception("Can not register tag \"$name\", not callable."); + } + if(empty($name)) { + throw new \Exception("Can not register tag, empty name."); + } + $this->tags[$name] = $callback; + } + + /* + * Function: call_tag + * Calling a custom tag (builtin ones can not be called) + * + * Parameters: + * $name - The Tag's name + * $params - Associative array of parameters + * $sub - A callable function (expecting an instance as it's parameter) that represents the tag's content. + * + * Throws: + * Might throw a (see <$fatal_error_on_missing_tag>. + * + * Returns: + * The output of the tag or, if a was thrown, the appropiate result (see <$mute_runtime_errors>). + */ + public function call_tag($name, $params, $sub) { + try { + if(!isset($this->tags[$name])) { + if($this->fatal_error_on_missing_tag) { + throw new FatalRuntimeError("Can not call tag \"$name\": Does not exist."); + } 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) { + return "RuntimeError occurred on tag '$name': " . $e->getMessage(); + } + } + } + + public function calc($expression) { + return Calc::calc($expression); + } + + /* + * Function: exectemplate + * Executes a template and returns the result. The huge difference to is that this function will also output all blocks. + * + * Parameters: + * $tpl - The name of the template to execute. + * + * Throws: + * * A exception if the template could not be loaded. + * * A if the template could not be parsed or transcompiled. + * * A if a tag threw it or if a tag was not found and <$fatal_error_on_missing_tag> is true. + * * Might also throw different exceptions, if a external tag threw it (but they should use or to make it possible for STE to handle them correctly). + * + * Returns: + * The output of the template. + */ + public function exectemplate($tpl) { + $output = ""; + $lastblock = $this->load($tpl); + + foreach($this->blockorder as $blockname) { + $output .= $this->blocks[$blockname]; + } + + return $output . $lastblock; + } + + /* + * Function: get_var_reference + * Get a reference to a template variable using a variable name. + * This can be used,if your custom tag takes a variable name as a parameter. + * + * Parameters: + * $name - The variables name. + * $create_if_not_exist - Should the variable be created, if it does not exist? Otherwise NULL will be returned, if the variable does not exist. + * + * Throws: + * if the variable name can not be parsed (e.g. unbalanced brackets). + * + * Returns: + * A Reference to the variable. + */ + public function &get_var_reference($name, $create_if_not_exist) { + $ref = &$this->scope->get_var_reference($name, $create_if_not_exist); + return $ref; + } + + /* + * Function: set_var_by_name + * Set a template variable by its name. + * This can be used,if your custom tag takes a variable name as a parameter. + * + * Parameters: + * $name - The variables name. + * $val - The new value. + * + * Throws: + * if the variable name can not be parsed (e.g. unbalanced brackets). + */ + public function set_var_by_name($name, $val) { + $this->scope->set_var_by_name($name, $val); + } + + /* + * Function: set_local_var + * Like , but only sets the variable in the global scope ( will overwrite the variable in the parent scope, if it's defined there) . + * + * Parameters: + * $name - The variables name. + * $val - The new value. + * + * Throws: + * if the variable name can not be parsed (e.g. unbalanced brackets). + */ + public function set_local_var($name, $val) { + $this->scope->set_local_var($name, $val); + } + + /* + * Function: get_var_by_name + * Get a template variable by its name. + * This can be used,if your custom tag takes a variable name as a parameter. + * + * Parameters: + * $name - The variables name. + * + * Throws: + * if the variable name can not be parsed (e.g. unbalanced brackets). + * + * Returns: + * The variables value. + */ + public function get_var_by_name($name) { + return $this->scope->get_var_by_name($name); + } + + /* + * Function: load + * Load a template and return its result (blocks not included, use for this). + * + * Parameters: + * $tpl - The name of the template to be loaded. + * $quiet - If true, do not output anything and do not modify the blocks. This can be useful to load custom tags that are programmed in the STE Template Language. Default: false. + * + * Throws: + * * A exception if the template could not be loaded. + * * A if the template could not be parsed or transcompiled. + * * A if a tag threw it or if a tag was not found and <$fatal_error_on_missing_tag> is true. + * * Might also throw different exceptions, if a external tag threw it (but they should use or to make it possible for STE to handle them correctly). + * + * Returns: + * The result of the template (if $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] != "/") { + $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) { + $pathex = array_slice($pathex, 1); + } 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) { + $blocks_back = clone $this->blocks; + $blockorder_back = clone $this->blockorder; + } + + $mode = StorageAccess::MODE_TRANSCOMPILED; + $content = $this->storage_access->load($tpl, $mode); + if($mode == StorageAccess::MODE_SOURCE) { + try { + $ast = Parser::parse($content, $tpl); + $transc = Transcompiler::transcompile($ast); + } catch(ParseCompileError $e) { + $e->rewrite($content); + throw $e; + } + $this->storage_access->save($tpl, $transc, StorageAccess::MODE_TRANSCOMPILED); + eval("\$content = $transc;"); + } + + $output = $content($this); + + $this->cur_tpl_dir = $tpldir_b4; + + if($quiet) { + $this->blocks = $blocks_back; + $this->blockorder = $blockorder_back; + } else { + return $output; + } + } + + /* + * Function: evalbool + * Test, if a text represents false (an empty / only whitespace text) or true (everything else). + * + * Parameters: + * $txt - The text to test. + * + * Returns: + * true/false. + */ + public function evalbool($txt) { + return trim(@(string)$txt) != ""; + } + + public function make_closure($fx) { + $bound_scope = $this->scope; + return function() use($bound_scope, $fx) { + $args = func_get_args(); + $ste = $args[0]; + + $prev = $ste->scope; + $scope = $bound_scope->new_subscope(); + $ste->scope = $scope; + + try { + $result = call_user_func_array($fx, $args); + $ste->scope = $prev; + return $result; + } catch(\Exception $e) { + $ste->scope = $prev; + throw $e; + } + }; + } } diff --git a/src/ste/STEStandardLibrary.php b/src/ste/STEStandardLibrary.php index 915b699..17532a3 100644 --- a/src/ste/STEStandardLibrary.php +++ b/src/ste/STEStandardLibrary.php @@ -3,147 +3,147 @@ namespace kch42\ste; 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"])) { - return nl2br(htmlspecialchars(str_replace("\r\n", "\n", $sub($ste)))); - } else { - return htmlspecialchars($sub($ste)); - } - } - - static public function strlen($ste, $params, $sub) { - return strlen($sub($ste)); - } - - static public function arraylen($ste, $params, $sub) { - if(empty($params["array"])) { - throw new RuntimeError("Missing array parameter in ."); - } - $a = $ste->get_var_by_name($params["array"], false); - return (is_array($a)) ? count($a) : ""; - } - - static public function inc($ste, $params, $sub) { - if(empty($params["var"])) { - throw new RuntimeError("Missing var parameter in ."); - } - $ref = &$ste->get_var_reference($params["var"], true); - $ref++; - } - - static public function dec($ste, $params, $sub) { - if(empty($params["var"])) { - throw new RuntimeError("Missing var parameter in ."); - } - $ref = &$ste->get_var_reference($params["var"], true); - $ref--; - } - - 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"])) { - throw new RuntimeError("Missing array parameter in ."); - } - $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"])) { - throw new RuntimeError("Missing array parameter in ."); - } - return implode($sub($ste), $ste->get_var_by_name($params["array"])); - } - - static public function split($ste, $params, $sub) { - if(empty($params["array"])) { - throw new RuntimeError("Missing array parameter in ."); - } - if(empty($params["delim"])) { - throw new RuntimeError("Missing delim parameter in ."); - } - $ste->set_var_by_name($params["array"], explode($params["delim"], $sub($ste))); - } - - static public function array_add($ste, $params, $sub) { - if(empty($params["array"])) { - throw new RuntimeError("Missing array parameter in ."); - } - - $ar = &$ste->get_var_reference($params["array"], true); - if(empty($params["key"])) { - $ar[] = $sub($ste); - } else { - $ar[$params["key"]] = $sub($ste); - } - } - - static public function array_filter($ste, $params, $sub) - { - if(empty($params["array"])) { - throw new RuntimeError("Missing array parameter in ."); - } - - $ar = $ste->get_var_by_name($params["array"]); - 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)) { - 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) { - 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)) { - 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)) { - 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) { - 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)) { - 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); - } - - $ste->set_var_by_name($params["array"], $ar); - } + 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"])) { + return nl2br(htmlspecialchars(str_replace("\r\n", "\n", $sub($ste)))); + } else { + return htmlspecialchars($sub($ste)); + } + } + + static public function strlen($ste, $params, $sub) { + return strlen($sub($ste)); + } + + static public function arraylen($ste, $params, $sub) { + if(empty($params["array"])) { + throw new RuntimeError("Missing array parameter in ."); + } + $a = $ste->get_var_by_name($params["array"], false); + return (is_array($a)) ? count($a) : ""; + } + + static public function inc($ste, $params, $sub) { + if(empty($params["var"])) { + throw new RuntimeError("Missing var parameter in ."); + } + $ref = &$ste->get_var_reference($params["var"], true); + $ref++; + } + + static public function dec($ste, $params, $sub) { + if(empty($params["var"])) { + throw new RuntimeError("Missing var parameter in ."); + } + $ref = &$ste->get_var_reference($params["var"], true); + $ref--; + } + + 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"])) { + throw new RuntimeError("Missing array parameter in ."); + } + $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"])) { + throw new RuntimeError("Missing array parameter in ."); + } + return implode($sub($ste), $ste->get_var_by_name($params["array"])); + } + + static public function split($ste, $params, $sub) { + if(empty($params["array"])) { + throw new RuntimeError("Missing array parameter in ."); + } + if(empty($params["delim"])) { + throw new RuntimeError("Missing delim parameter in ."); + } + $ste->set_var_by_name($params["array"], explode($params["delim"], $sub($ste))); + } + + static public function array_add($ste, $params, $sub) { + if(empty($params["array"])) { + throw new RuntimeError("Missing array parameter in ."); + } + + $ar = &$ste->get_var_reference($params["array"], true); + if(empty($params["key"])) { + $ar[] = $sub($ste); + } else { + $ar[$params["key"]] = $sub($ste); + } + } + + static public function array_filter($ste, $params, $sub) + { + if(empty($params["array"])) { + throw new RuntimeError("Missing array parameter in ."); + } + + $ar = $ste->get_var_by_name($params["array"]); + 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)) { + 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) { + 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)) { + 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)) { + 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) { + 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)) { + 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); + } + + $ste->set_var_by_name($params["array"], $ar); + } } diff --git a/src/ste/Scope.php b/src/ste/Scope.php index 26481ac..9326921 100644 --- a/src/ste/Scope.php +++ b/src/ste/Scope.php @@ -3,144 +3,144 @@ namespace kch42\ste; class Scope implements \ArrayAccess { - private $parent = NULL; - public $vars = array(); - - private static function parse_name($name) { - $remain = $name; - $fields = array(); - - while($remain !== "") { - $br_open = strpos($remain, '['); - if($br_open === false) { - $fields[] = $remain; - break; - } - - $br_close = strpos($remain, ']', $br_open); - if($br_close === false) { - throw new RuntimeError("Invalid varname \"$name\". Missing closing \"]\"."); - } - - $fields[] = substr($remain, 0, $br_open); - - $field = substr($remain, $br_open+1, $br_close-$br_open-1); - $more = substr($remain, $br_close+1); - - if(strpos($field, '[') !== false) { - throw new RuntimeError("A variable field must not contain a '[' character."); - } - - if((strlen($more) > 0) && ($more[0] !== '[')) { - // TODO: better error message, not very non-programmer friendly... - throw new RuntimeError("A variable name must be of format name('[' name ']')*."); - } - - $remain = $field . $more; - } - - return $fields; - } - - private function &get_topvar_reference($name, $localonly) { - if(array_key_exists($name, $this->vars)) { - $ref = &$this->vars[$name]; - return $ref; - } - - if((!$localonly) && ($this->parent !== NULL)) { - $ref = &$this->parent->get_topvar_reference($name, $localonly); - return $ref; - } - - throw new VarNotInScope(); - } - - public function &get_var_reference($name, $create_if_not_exist, $localonly=false) { - $nullref = NULL; - - $fields = self::parse_name($name); - if(count($fields) == 0) { - return $nullref; // TODO: or should we throw an exception here? - } - - $first = $fields[0]; - - $ref = NULL; - try { - $ref = &$this->get_topvar_reference($first, $localonly); - } catch(VarNotInScope $e) { - if($create_if_not_exist) { - $this->vars[$first] = (count($fields) > 0) ? array() : ""; - $ref = &$this->vars[$first]; - } else { - return $nullref; - } - } - - for($i = 1; $i < count($fields); $i++) { - $field = $fields[$i]; - - if(!is_array($ref)) { - return $nullref; - } - - if(!array_key_exists($field, $ref)) { - if(!$create_if_not_exist) { - return $nullref; - } - - if($i < count($fields) - 1) { - $ref[$field] = array(); - } else { - $ref[$field] = ""; - } - } - - $ref = &$ref[$field]; - } - - return $ref; - } - - public function set_var_by_name($name, $val) { - $ref = &$this->get_var_reference($name, true); - $ref = $val; - } - - public function set_local_var($name, $val) { - $ref = &$this->get_var_reference($name, true, true); - $ref = $val; - } - - public function get_var_by_name($name) { - $ref = $this->get_var_reference($name, false); - return $ref === NULL ? "" : $ref; - } - - public function new_subscope() { - $o = new self(); - $o->parent = $this; - return $o; - } - - /* implementing ArrayAccess */ - - public function offsetSet($offset, $value) { - $this->set_var_by_name($offset, $value); - } - public function offsetGet($offset) { - return $this->get_var_by_name($offset); - } - public function offsetExists($offset) { - try { - $this->get_topvar_reference($offset); - return true; - } catch(VarNotInScope $e) { - return false; - } - } - public function offsetUnset($offset) { - unset($this->vars[$offset]); - } + private $parent = NULL; + public $vars = array(); + + private static function parse_name($name) { + $remain = $name; + $fields = array(); + + while($remain !== "") { + $br_open = strpos($remain, '['); + if($br_open === false) { + $fields[] = $remain; + break; + } + + $br_close = strpos($remain, ']', $br_open); + if($br_close === false) { + throw new RuntimeError("Invalid varname \"$name\". Missing closing \"]\"."); + } + + $fields[] = substr($remain, 0, $br_open); + + $field = substr($remain, $br_open+1, $br_close-$br_open-1); + $more = substr($remain, $br_close+1); + + if(strpos($field, '[') !== false) { + throw new RuntimeError("A variable field must not contain a '[' character."); + } + + if((strlen($more) > 0) && ($more[0] !== '[')) { + // TODO: better error message, not very non-programmer friendly... + throw new RuntimeError("A variable name must be of format name('[' name ']')*."); + } + + $remain = $field . $more; + } + + return $fields; + } + + private function &get_topvar_reference($name, $localonly) { + if(array_key_exists($name, $this->vars)) { + $ref = &$this->vars[$name]; + return $ref; + } + + if((!$localonly) && ($this->parent !== NULL)) { + $ref = &$this->parent->get_topvar_reference($name, $localonly); + return $ref; + } + + throw new VarNotInScope(); + } + + public function &get_var_reference($name, $create_if_not_exist, $localonly=false) { + $nullref = NULL; + + $fields = self::parse_name($name); + if(count($fields) == 0) { + return $nullref; // TODO: or should we throw an exception here? + } + + $first = $fields[0]; + + $ref = NULL; + try { + $ref = &$this->get_topvar_reference($first, $localonly); + } catch(VarNotInScope $e) { + if($create_if_not_exist) { + $this->vars[$first] = (count($fields) > 0) ? array() : ""; + $ref = &$this->vars[$first]; + } else { + return $nullref; + } + } + + for($i = 1; $i < count($fields); $i++) { + $field = $fields[$i]; + + if(!is_array($ref)) { + return $nullref; + } + + if(!array_key_exists($field, $ref)) { + if(!$create_if_not_exist) { + return $nullref; + } + + if($i < count($fields) - 1) { + $ref[$field] = array(); + } else { + $ref[$field] = ""; + } + } + + $ref = &$ref[$field]; + } + + return $ref; + } + + public function set_var_by_name($name, $val) { + $ref = &$this->get_var_reference($name, true); + $ref = $val; + } + + public function set_local_var($name, $val) { + $ref = &$this->get_var_reference($name, true, true); + $ref = $val; + } + + public function get_var_by_name($name) { + $ref = $this->get_var_reference($name, false); + return $ref === NULL ? "" : $ref; + } + + public function new_subscope() { + $o = new self(); + $o->parent = $this; + return $o; + } + + /* implementing ArrayAccess */ + + public function offsetSet($offset, $value) { + $this->set_var_by_name($offset, $value); + } + public function offsetGet($offset) { + return $this->get_var_by_name($offset); + } + public function offsetExists($offset) { + try { + $this->get_topvar_reference($offset); + return true; + } catch(VarNotInScope $e) { + return false; + } + } + public function offsetUnset($offset) { + unset($this->vars[$offset]); + } } diff --git a/src/ste/StorageAccess.php b/src/ste/StorageAccess.php index 81f7439..0798b41 100644 --- a/src/ste/StorageAccess.php +++ b/src/ste/StorageAccess.php @@ -12,44 +12,44 @@ namespace kch42\ste; * This means, that you are not limited to store the Templates inside directories, you can also use a database or something else. */ interface StorageAccess { - /* - * Constants: Template modes - * - * MODE_SOURCE - The Templates source - * MODE_TRANSCOMPILED - The transcompiled template - */ - const MODE_SOURCE = 0; - const MODE_TRANSCOMPILED = 1; - - /* - * Function: load - * Loading a template. - * - * Parameters: - * $tpl - The name of the template. - * &$mode - Which mode is preferred? One of the