summaryrefslogtreecommitdiff
path: root/Parser.php
diff options
context:
space:
mode:
Diffstat (limited to 'Parser.php')
-rw-r--r--Parser.php456
1 files changed, 0 insertions, 456 deletions
diff --git a/Parser.php b/Parser.php
deleted file mode 100644
index e3b8d07..0000000
--- a/Parser.php
+++ /dev/null
@@ -1,456 +0,0 @@
-<?php
-
-namespace kch42\ste;
-
-/*
- * Class: Parser
- * The class, where the parser lives in. Can not be constructed manually.
- * Use the static method parse.
- */
-class Parser {
- private $text;
- private $name;
- private $off;
- private $len;
-
- 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];
- }
- }
- }
-}