summaryrefslogtreecommitdiff
path: root/src/ste/Parser.php
diff options
context:
space:
mode:
Diffstat (limited to 'src/ste/Parser.php')
-rw-r--r--src/ste/Parser.php890
1 files changed, 445 insertions, 445 deletions
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 <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]);
- }
-
- 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" => "<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;
- }
- }
-
- $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 <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];
- }
- }
- }
+ 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 <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]);
+ }
+
+ 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" => "<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;
+ }
+ }
+
+ $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 <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];
+ }
+ }
+ }
}