{"id":1002,"date":"2011-03-01T17:56:05","date_gmt":"2011-03-01T16:56:05","guid":{"rendered":"http:\/\/blog.benny-baumann.de\/?p=1002"},"modified":"2011-10-08T14:37:51","modified_gmt":"2011-10-08T12:37:51","slug":"parser-bau-fur-einsteiger","status":"publish","type":"post","link":"http:\/\/blog.benny-baumann.de\/?p=1002","title":{"rendered":"Parser-Bau f\u00fcr Einsteiger"},"content":{"rendered":"<p>Wenn man in seinem Programm mathematische Ausdr\u00fccke, die vom Nutzer eingegeben wurden, auswerten m\u00f6chte, so kann man dies entweder mit Hilfe <a href=\"http:\/\/blog.benny-baumann.de\/?p=332\">b\u00f6sartiger Konstrukte<\/a> tun, oder aber, mit Hilfe eines Parsers, die Ausdr\u00fccke selber auswerten und dann in einer kontrollierten Umgebung ausf\u00fchren. Die M\u00f6glichkeit einer solchen kontrollierten Ausf\u00fchrung ist in Script-Sprachen wie JavaScript notwendig, kann aber auch in Template-Systemen gute Dienste leisten, wenn mit den dem Template \u00fcbergebenen Daten noch Berechnungen zu erledigen sind, oder einfache Transformationen zu implementieren sind.<!--more--><\/p>\n<h2>Einleitung<\/h2>\n<p>Der Bereich der Informatik, in die diese Thematik f\u00e4llt, wird als <a href=\"http:\/\/de.wikipedia.org\/wiki\/Compilerbau\">Compilerbau<\/a> bezeichnet und besch\u00e4ftigt sich damit, wie gegebene Ausdr\u00fccke eingelesen und strukturell umgebaut werden k\u00f6nnen. Meist handelt es sich bei der Eingabe um menschenlesbaren Quelltext, (z.B. in einer Hochsprache wie C), w\u00e4hrend die Ausgabe durch Bytecode oder in Maschinensprache erfolgt.<\/p>\n<p>Ein Compiler besteht dabei aus einer ganzen Reihe von einzelnen Verarbeitungsschritte, die je nach Architektur des Compilers (Single Pass vs. Multi Pass vs. JIT) unterschiedlich konstruiert werden und basierend auf dieser Architektur unterschiedlich komplexe Ausdr\u00fccke verarbeiten k\u00f6nnen. So ist es zwar m\u00f6glich, reinen Assembler mit einem einzigen Durchlauf durch den Quelltext zu \u00fcbersetzen, sobald aber z.B. die Verarbeitung von Variablen f\u00fcr die Erzeugung der Ausgabe wichtig wird, ergeben sich hierf\u00fcr eine Reihe von H\u00fcrden, die meist durch mehrmaliges Lesen des Quelltextes in aufeinander folgenden Phasen gel\u00f6st werden.<\/p>\n<p>Da ich diesen Beitrag aber speziell an Leute richten m\u00f6chte, die bisher noch nicht mit dem Thema Compilerbau in Ber\u00fchrung gekommen sind, werde ich hier keinen vollst\u00e4ndigen Compiler vorstellen, sondern mich darauf beschr\u00e4nken einen Code-Parser zu beschreiben. Als Programmiersprache dient hierbei PHP, w\u00e4hrend die von diesem Parser akzeptierte <a href=\"http:\/\/de.wikipedia.org\/wiki\/Syntax#Die_Syntax_formaler_Sprachen_.28formale_Syntax.29\">Syntax<\/a> eine Anlehnung an C bzw. PHP darstellt. Wer etwas tiefer in das Thema einsteigen m\u00f6chte und zudem Delphi beherrscht, dem sei das Tutorial zu Scriptsprachen (<a href=\"http:\/\/wiki.delphigl.com\/index.php\/Tutorial_Scriptsprachen_Teil_1\">Teil 1<\/a> und <a href=\"http:\/\/wiki.delphigl.com\/index.php\/Tutorial_Scriptsprachen_Teil_2\">Teil 2<\/a>) ans Herz gelegt. Eine vollst\u00e4ndiige Abhandlung des Themas w\u00fcrde hier aber den Rahmen sprengen &#8211; was auch schon so der Fall sein wird.<\/p>\n<h2>Die Syntax<\/h2>\n<p>Wie bereits angedeutet, \u00e4hnelt die Syntax ein wenig C und PHP, wobei die Operatoren im Wesentlichen der C-Semantik folgen, w\u00e4hrend Variablen der Syntax von PHP entsprechen. Neben den bereits aus diesen Sprachen bekannten Rechenoperationen werden als Teil unseres Parsers eine Reihe zus\u00e4tzlicher Operationen entstehen, die in keiner von beiden Sprachen implementiert sind.<\/p>\n<p>Die Syntax der Sprache unterscheidet (auf Parser-Ebene) zwischen Argumenten und Operatoren. Argumente sind hierbei entweder Literale, d.h. Ganzzahlen, Gleitkomma-Zahlen oder Strings, oder Variablen. Operatoren sind jegliche anderen Symbole, die im Quelltext auftauchen, wobei Whitespace (Leerzeichen und Tabulatoren) ignoriert werden. Der Einfachheit halber nehmen wir zudem an, dass wir als Eingabe f\u00fcr den Parser bereits Tokens erhalten, d.h. unser Quelltext bereits in seine einzelnen Teile zerlegt wurde und daher bereits ein Array mit allen f\u00fcr das Analysieren des Quelltexts notwendigen Elementen vorliegt. <\/p>\n<p>Aus diesen Elementen soll im Laufe der Verarbeitung eine \u00e4quivalente Darstellung erzeugt werden, die sich einfach von einer Maschine interpretieren l\u00e4sst. Eine m\u00f6gliche solche Darstellung ist der sogenannte Syntax-Baum bzw. Parser Tree. Dieser speichert in jedem Knoten einen Operator und in den zugeh\u00f6rigen Kindknoten die ben\u00f6tigten Operatoren. Gefundene Argumente bilden hierbei die Bl\u00e4tter dieses Baumes.<\/p>\n<p>Ein f\u00fcr unsere Definition g\u00fcltiger Ausdruck ist z.B. 1 + 2 * 3 + 4. Dieser soll im weiteren Verlauf in einen etwa folgende Baumstruktur \u00fcberf\u00fchrt werden:<\/p>\n<pre>\r\nOpBinaryPlus:\r\n  OpBinaryPlus:\r\n    ArgNumber:\r\n      1\r\n    OpBinaryMultiply:\r\n      ArgNumber:\r\n        2\r\n      ArgNumber:\r\n        3\r\n  ArgNumber:\r\n    4\r\n<\/pre>\n<h2>Vorbereitungen<\/h2>\n<p>Bevor wir aber mit dem eigentlichen Bau des Parsers beginnen k\u00f6nnen, muss neben der Syntax auch noch das Verhalten von Operatoren gekl\u00e4rt werden, denn nicht jeder Operator verh\u00e4lt sich gleich. Nehmen wir hierzu einmal folgende 3 Beispiele:<\/p>\n<pre>\r\n!$verstanden\r\n$hinz & $kunz\r\n42 ? $true++ : --$false\r\n<\/pre>\n<p>W\u00e4hrend das erste Beispiel mit dem logischen NOT-Operator einen un\u00e4ren Pr\u00e4fix-Operator enth\u00e4lt, finden sich im zweiten Beispiel ein bitweiser, bin\u00e4rer AND-Operator. Das dritte Beispiel besteht gar aus 3 Operatoren: einem tern\u00e4ren Operator (a ? b : c), einem un\u00e4ren Pr\u00e4fix-Operator f\u00fcr Pr\u00e4dekrementation, sowie einem un\u00e4ren Suffix-Operator f\u00fcr Post-Inkrementation.<\/p>\n<p>Schaut man sich insbesondere das dritte Beispiel an, so bleibt die Frage, wie man dieses Beispiel korrekt analysieren kann. Das Schl\u00fcsselwort hierf\u00fcr ist <a href=\"http:\/\/de.wikipedia.org\/wiki\/Operatorrangfolge\">Operatorrangfolge<\/a>:<\/p>\n<pre>$gleicher = $gleich == $gleich<\/pre>\n<p>Die Operatoren-Rangfolge ist hierbei analog den bereits aus der Schule bekannten Vorrang-Regeln Punkt-vor-Strich sowie den diversen Klammer-Regeln zu verstehen. So erfolgt die Auswertung von Ausdr\u00fccken zwar grundlegend erst einmal von links nach rechts, treffen wir jedoch auf einen Operator, der seine Argumente st\u00e4rker bindet, als die Operation links davon, so k\u00f6nnen wir uns um diese &#8222;h\u00f6her priorisierte&#8220; Operation Klammern vorstellen, die entsprechend zuerst ausgerechnet werden m\u00fcssen.<\/p>\n<p>Diese Pr\u00fcfung der Priorit\u00e4t kann im einfachsten Fall durch die Reihenfolge der Suche nach Operatoren innerhalb des Parsers erfolgen, ben\u00f6tigt f\u00fcr unseren Fall aber noch etwas Feintuning, wie sich sp\u00e4ter zeigen wird. Da sich viele der Operatoren in Bezug auf die Argumente aber \u00e4hneln, insbesondere beim Aufbau, werden wir diese Chance ergreifen und entsprehcend Gruppen von Operatoren bilden:<\/p>\n<ul>\n<li>Un\u00e4re Pr\u00e4fix-Operatoren (+, -, ++, &#8211;, ~, !)<\/li>\n<li>Un\u00e4re Postfix-Operatoren (++, &#8211;, !)<\/li>\n<li>Bin\u00e4re Operatoren (+, -, *, \/, %, &#038;, &#038;&#038;, |, ||, ^, ^^, ., =, &lt;, &gt;, &lt;=, &gt;=, ==, !=, ===, !==, ::, ->)<\/li>\n<li>Tern\u00e4re Operatoren (?:)<\/li>\n<li>Sonstige (z.B. Klammern, Array-Zugriffe, Funktionsaufrufe)<\/li>\n<\/ul>\n<p>Neben der Rangfolge sollten wir uns sp\u00e4ter zus\u00e4tzlich auch um die Assoziativit\u00e4t der Operatoren k\u00fcmmern, da diese in der Regel linksassoziativ ist, f\u00fcr den hier vorgestellten Parser aber vorrangig rechtsassoziativ sein wird (der Einfachheit halber). Aber auch dazu mehr, denn f\u00fcr die meisten Berechnungen, bei denen das Distributivgesetz gilt, spielt die Assoziativit\u00e4t keine Rolle; und ist maximal von der Auswertungsreihenfolge gew\u00f6hnungsbed\u00fcrftig.<\/p>\n<h2>Erster Code<\/h2>\n<pre lang=\"php\">\r\ndefine ('PARSE_TERM', 1);\r\ndefine ('PARSE_SUBTERM', 2);\r\ndefine ('PARSE_EOF', 3);\r\n\r\nabstract class Term {\r\n    public abstract function compile();\r\n    public abstract function check();\r\n    public abstract function evaluate();\r\n    public function isWritable() {\r\n        return false;\r\n    }\r\n    public function dump($level = 0) {\r\n        $space = str_repeat('  ', $level);\r\n        echo $space;\r\n        echo get_class($this);\r\n        echo \":\\n\";\r\n    }\r\n}\r\n<\/pre>\n<p>Mit der Klasse Term haben wir hier unsere wichtigste Klasse, neben dem Parser. Von Term werden sowohl alle Argumente, als auch alle Operatoren abgeleitet. Die Funktion compile ist f\u00fcr den eigentlichen Parser nicht n\u00f6tig, kann aber wenn man alles richtig implementiert hat, verwendet werden, um den fertigen Ausdruck in g\u00fcltiges PHP zu \u00fcbersetzen. Diese M\u00f6glichkeit ergibt sich direkt aus unserem Parser-Baum und erfordert auch nicht allzu viel Aufwand. Aber dazu gleich mehr. Die Methode check \u00fcberpr\u00fcft die syntaktische Korrektheit der in einem Term gespeicherten Bestandteile. Die Methode evaluate schlie\u00dflich erlaubt die Ausf\u00fchrung bzw. Auswertung unseres Ausdrucks. \u00c4hnlich wie die Methode zum &#8222;Compilen&#8220; ergibt sich auch diese Methode fast automatisch.<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\nabstract class Argument extends Term {\r\n    public $value = null;\r\n    public abstract function canParse($symbol);\r\n    public function dump($level = 0) {\r\n        parent::dump($level);\r\n        $space = str_repeat('  ', $level+1);\r\n        echo $space;\r\n        echo $this-&gt;value;\r\n        echo \"\\n\";\r\n    }\r\n}\r\n<\/pre>\n<p>Die Klasse Argument schlie\u00dflich definiert die Grundlagen f\u00fcr alle auftauchenden Argumente im Quelltext. Neben einem Wert, der den Inhalt dieses Symbols angibt, gibt es mit der Methode canParse den ersten elementaren Bestandteil des Parsers. Die Funktion canParse gibt hierbei an, ob das gegebene Symbol wie gew\u00fcnscht geparst werden kann, sprich ob &#8222;$abc&#8220; eine Zahl ist oder nicht (in dem Fall w\u00fcrde ArgVariable ja sagen). R\u00fcckgabe ist hierbei entweder false f\u00fcr &#8222;kann ich nicht verarbeiten&#8220; oder eine Instanz der jeweiligen Klasse, die das value-Feld korrekt initialisiert hat, um sp\u00e4ter den Quelltext reproduzieren zu k\u00f6nnen.<\/p>\n<p>Aber weiter zur n\u00e4chsten wichtigen Klasse, ohne die unser Parser sonst gar keinen Spa\u00df machen w\u00fcrde: Einen Operator!<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\nabstract class Operator extends Term {\r\n    protected $args = array();\r\n    public abstract function canParse($symbol, $lterms, $opsym);\r\n    public function storeArgs($arguments) {\r\n        $this-&gt;args = $arguments;\r\n    }\r\n    public function dump($level = 0) {\r\n        parent::dump($level);\r\n        foreach($this-&gt;args as $arg) {\r\n            $arg-&gt;dump($level+1);\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>Jeder Operator besteht hierbei aus einer Reihe von anderen Termen, den sogenannten Argumenten f\u00fcr diesen Operator. Ferner besitzt ein Operator analog zu Argumenten die M\u00f6glichkeit, zu sagen, ob er mit einem Token zurecht kommt. Token sind hierbei entwerder Argumente, bereits geparste Terme oder Operatoren, die als Strings \u00fcbergeben werden. Zus\u00e4tzlich besitzt jeder Operator eine Methode, um die in einem Kontext gefundenen Argumente (Terme) abzuspeichern. Diese Methode wird uns sp\u00e4ter beim Aufbau des Parser Trees noch einmal begegnen.<\/p>\n<h2>Parsen von Argumenten<\/h2>\n<p>Da wir nun die grundlegenden Bestandteile des Parser Trees kurz vorgestellt haben, k\u00f6nnen wir nun mit dem ersten Teil der Parsing-Arbeit beginnen. Nehmen wir uns nun erst einmal den einfachen Teil heraus: Die Argumente:<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\nclass ArgNumber extends Argument {\r\n    public function compile() {\r\n        return ''.$this-&gt;value;\r\n    }\r\n    public function check() {\r\n        return is_numeric($this-&gt;value);\r\n    }\r\n    public function evaluate() {\r\n        return $this-&gt;value;\r\n    }\r\n    public function canParse($symbol) {\r\n        if(!is_numeric($symbol)) {\r\n            return false;\r\n        }\r\n        $obj = new self();\r\n        $obj-&gt;value = (float)$symbol;\r\n        return $obj;\r\n    }\r\n}\r\n\r\nclass ArgString extends Argument {\r\n    public function compile() {\r\n        return \"'\".str_replace(\"'\", \"\\\\'\", str_replace('\\\\', '\\\\\\\\', $this-&gt;value)). \"'\";\r\n    }\r\n    public function check() {\r\n        return is_string($this-&gt;value);\r\n    }\r\n    public function evaluate() {\r\n        return $this-&gt;value;\r\n    }\r\n    public function canParse($symbol) {\r\n        if(!is_string($symbol)) {\r\n            return false;\r\n        }\r\n        if(2 &gt; strlen($symbol)) {\r\n            return false;\r\n        }\r\n        if(\"'\" != $symbol[0]) {\r\n            return false;\r\n        }\r\n        if(\"'\" != $symbol[strlen($symbol)-1]) {\r\n            return false;\r\n        }\r\n        $obj = new self();\r\n        $obj-&gt;value = substr($symbol, 1, -1);\r\n        $obj-&gt;value = str_replace('\\\\\\'', '\\'', $obj-&gt;value);\r\n        $obj-&gt;value = str_replace('\\\\\\\\', '\\\\', $obj-&gt;value);\r\n        return $obj;\r\n    }\r\n}\r\n\r\nclass ArgVariable extends Argument {\r\n    public function compile() {\r\n        return '$'.$this-&gt;value;\r\n    }\r\n    public function check() {\r\n        return true;\r\n    }\r\n    public function evaluate() {\r\n        return $GLOBALS[$this-&gt;value];\r\n    }\r\n    public function isWritable() {\r\n        return true;\r\n    }\r\n    public function canParse($symbol) {\r\n        if(!is_string($symbol)) {\r\n            return false;\r\n        }\r\n        if(!strlen($symbol)) {\r\n            return false;\r\n        }\r\n        if('$' != $symbol[0]) {\r\n            return false;\r\n        }\r\n        $obj = new self();\r\n        $obj-&gt;value = substr($symbol, 1);\r\n        return $obj;\r\n    }\r\n}\r\n<\/pre>\n<p>An dieser Stelle d\u00fcrfte es vorerst keine \u00dcberraschungen geben, abgesehen davon, dass dieser Source nicht unbedingt der effizienteste ist. Aber zur Demonstration reicht es durchaus aus. Dank OOP k\u00f6nnen wir sp\u00e4ter immer noch recht einfach Anpassungen an das Verhalten vornehmen. Insbesondere bei Variablen sollte noch ein Fehler auffallen, der sich aber leicht durch eine zus\u00e4tzliche Pr\u00fcfung beheben l\u00e4sst.<\/p>\n<h2>Die Operatoren<\/h2>\n<p>Etwas aufw\u00e4ndiger sieht es nun bei den Operatoren aus, da es davon einfach eine ganze Menge gibt. Wie oben aber bereits angedeutet wurde, l\u00e4sst sich hier eine ganze Menge Arbeit sparen, wenn man an die Evolutionstheorie glaubt und diese daher f\u00fcr sich zu nutzen wei\u00df. Statt also blind alle Operatoren mit einem Mal zu definieren, legen wir erstmal ein paar Grundklassen an, von denen wir sp\u00e4ter einfach ableiten k\u00f6nnen:<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\nabstract class UnaryOperator extends Operator {\r\n    public function canParse($symbol, $lterms, $opsym) {\r\n        if(empty($lterms) && empty($opsym)) {\r\n            \/\/The first part ist the operator?\r\n            if(is_string($symbol)) {\r\n                \/\/If we get a non-string value, this can't be an operator\r\n                return $this-&gt;opSign == $symbol ? PARSE_TERM : false;\r\n            }\r\n            return false;\r\n        }\r\n\r\n        if(!empty($opsym)) {\r\n            if(is_string($symbol)) {\r\n                return false;\r\n            }\r\n            \/\/We already got an operator? If we also got an value, this is an error!\r\n            return empty($lterms) ? PARSE_EOF : false;\r\n        }\r\n\r\n        if(!empty($lterms)) {\r\n            \/\/We already got a value? If we didn't get an operator, this is an error!\r\n            return empty($opsym) ? false : PARSE_EOF;\r\n        }\r\n\r\n        \/\/We should never reach here!\r\n        return false;\r\n    }\r\n    public function compile() {\r\n        return $this-&gt;opSign . $this-&gt;args[0]-&gt;compile();\r\n    }\r\n    public function check() {\r\n        return 1 == count($this-&gt;args);\r\n    }\r\n}\r\n\r\nabstract class UnaryPostOperator extends UnaryOperator {\r\n    public function canParse($symbol, $lterms, $opsym) {\r\n        if(empty($lterms) && empty($opsym)) {\r\n            \/\/The first part is some term\r\n            return PARSE_TERM;\r\n        }\r\n\r\n        if(!empty($opsym)) {\r\n            return false;\r\n        }\r\n\r\n        if(!empty($lterms)) {\r\n            \/\/The first part ist the operator?\r\n            return $this-&gt;opSign == $symbol ? PARSE_EOF : false;\r\n        }\r\n\r\n        \/\/We should never reach here!\r\n        return false;\r\n    }\r\n    public function compile() {\r\n        return $this-&gt;args[0]-&gt;compile() . $this-&gt;opSign;\r\n    }\r\n}\r\n\r\nabstract class BinaryOperator extends Operator {\r\n    public function canParse($symbol, $lterms, $opsym) {\r\n        if(empty($lterms) && empty($opsym)) {\r\n            return PARSE_TERM;\r\n        }\r\n\r\n        \/\/We need a term to the left\r\n        if(empty($lterms)) {\r\n            if(is_string($symbol)) {\r\n                \/\/The first can't be an operator\r\n                return false;\r\n            }\r\n\r\n            \/\/We can handle any argument\r\n            return true;\r\n        }\r\n\r\n        \/\/We also have an operator\r\n        if(empty($opsym)) {\r\n            if(is_string($symbol)) {\r\n                \/\/ We got the first argument and just got the operator\r\n                return $this-&gt;opSign == $symbol ? PARSE_TERM : false;\r\n            } else {\r\n                \/\/Error: Two arguments after each other\r\n                return false;\r\n            }\r\n        }\r\n\r\n        \/\/We got another operator?\r\n        if(is_string($symbol)) {\r\n            \/\/We don't like it :P\r\n            return false;\r\n        }\r\n\r\n        if(1 == count($lterms) && 1 == count($opsym)) {\r\n            return PARSE_EOF;\r\n        }\r\n\r\n        \/\/We should never reach here!\r\n        return false;\r\n    }\r\n    public function compile() {\r\n        return $this-&gt;args[0]-&gt;compile() . $this-&gt;opSign . $this-&gt;args[1]-&gt;compile();\r\n    }\r\n    public function check() {\r\n        return 2 == count($this-&gt;args);\r\n    }\r\n}\r\n\r\nabstract class TernaryOperator extends Operator {\r\n    public function compile() {\r\n        return false; \/\/Unimplemented for now.\r\n        return $this-&gt;args[0]-&gt;compile() . $this-&gt;opSign1 . $this-&gt;args[1]-&gt;compile() . $this-&gt;opSign2 . $this-&gt;args[2]-&gt;compile();\r\n    }\r\n    public function check() {\r\n        return 3 == count($this-&gt;args);\r\n    }\r\n}\r\n<\/pre>\n<p>Wie angek\u00fcndigt, ersparen wir uns durch diese Vorbereitungen einiges an Arbeit. Dies macht es aber umso wichtiger, dass genau dieser Aspekt des Parsers sehr gut verstanden ist, um sp\u00e4ter den Abl\u00e4ufen folgen zu k\u00f6nnen. Lassen wir hierzu die Methoden check und compile einmal au\u00dfen vor, so verbleibt nur die canParse-Methode, deren Aufgabe die strukturelle Analyse der Eingabedaten ist.<\/p>\n<p>Die canParse-Methode besitzt 3 Parameter:<\/p>\n<ol>\n<li>$symbol: Das aktuelle, zum Operator hinzuzuf\u00fcgende Symbol<\/li>\n<li>$lterms: Bereits links vom aktuellen Punkt gefundene Terme<\/li>\n<li>$opsyms: Bereits gefundene Operator-Symbole f\u00fcr den aktuellen Kontext<\/li>\n<\/ol>\n<p>Der Aufruf der Methode erfolgt immer wenn ein Operator in einem Kontext (dazu unten im Parser mehr) gefunden wird, oder wenn auf Grund von Baum-Umstrukturierungen ein neuer Term in den Kontext einzuordnen ist. Die Methode canParse beschreibt somit anhand des R\u00fcckgabewertes, welche Transitionen f\u00fcr den Operatoren-Parser Tree m\u00f6glich sind. Ein einfaches Beispiel stellen hierbei die un\u00e4ren Pr\u00e4fix-Operatoren dar, deren Transitionen wie folgt verlaufen:<\/p>\n<ol>\n<li>Wenn noch nichts im Kontext vorhanden ist, suche nach unserem Operator. Wird dieser in Symbol \u00fcbergeben, akzeptieren wir diesen und sagen an, dass wir als n\u00e4chstes einen Operator brauchen, der gleich stark oder st\u00e4rker bindet, als wir.<\/li>\n<li>Wir haben bereits unseren Operator uns fehlt aber noch ein Argument. Wir pr\u00fcfen, dass wir einen Term (Argument oder fertigen Operator) \u00fcbergeben bekommen und geben wenn dies der Fall ist zur\u00fcck, dass unser Kontext somit abgeschlossen ist. Wird uns ein zweiter Operator \u00fcbergeben, geben wir einen Fehler zur\u00fcck.<\/li>\n<li>Befinden wir uns in einem beliebigen anderen Zustand, geben wir einen Fehler zur\u00fcck<\/li>\n<\/ol>\n<p>Nun mag gegebenenfalls verwunderlich sein, was im Falle von <\/p>\n<pre>!!$var<\/pre>\n<p>passiert. Das sollte sich aber bei einem kurzen Blick auf die R\u00fcckgabewerte der canParse-Methode verdeutlichen:<\/p>\n<ul>\n<li><strong>false<\/strong>: dieser Token ist an der aktuellen Position unzul\u00e4ssig.<\/li>\n<li><strong>true<\/strong>: Der Token ist an der aktuellen Position zul\u00e4ssig und keine Kontext-Operation ist n\u00f6tig.<\/li>\n<li><strong>PARSE_TERM<\/strong>: der Token ist g\u00fcltig und nachfolgend wird ein Term erwartet, der gleich oder st\u00e4rker bindet als unser Operator.<\/li>\n<li><strong>PARSE_SUBTERM<\/strong>: Der Token ist g\u00fcltig und es wird ein Term erwartet, in dem alle anderen Operatoren zul\u00e4ssig sind. Die Rangfolge ist durch Begrenzungszeichen durch unseren Operator eindeutig.<\/li>\n<li><strong>PARSE_EOF<\/strong>: Der Token ist g\u00fcltig und beendet den aktuellen Kontext.<\/li>\n<\/ul>\n<p>Kontext-Operation hei\u00dft hierbei, dass der Parser innerhalb des Parser Trees eine neue Ebene anf\u00e4ngt, oder eine solche beendet, indem die bisher gesammelten Terme der Ebene einsammelt und zu einem Operator-Objekt zusammenfasst, was an den Parent \u00fcbergeben wird. Aber dazu gleich mehr.<\/p>\n<h2>Der Parser<\/h2>\n<p>Nach dem wir nun die ganzen Vorbereitungen hinter uns haben, k\u00f6nnen wir uns nun um den eigentlichen Parser k\u00fcmmern. Da dieser Schritt aber etwas umfangreicher wird, bauen wir den Parser an dieser Stelle Schritt f\u00fcr Schritt zusammen.<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\nclass ParserTree {\r\n    private $root = null;\r\n\r\n    public function dump() {\r\n        echo \"Generierter Parser-Tree:\\n\";\r\n        echo \"----\\n\";\r\n        if(!is_object($this-&gt;root)) {\r\n            echo \"Kein Parser-Tree vorhanden!\\n\";\r\n        } else {\r\n            $this-&gt;root-&gt;dump();\r\n        }\r\n        echo \"----\\n\";\r\n    }\r\n\r\n    private $availableOps = array();\r\n    private $availableArgs = array();\r\n\r\n    public function registerArguments($args)\r\n    {\r\n        $this-&gt;availableArgs = $args;\r\n        foreach($this-&gt;availableArgs as &$arg) {\r\n            $arg[1] = new $arg[0];\r\n        }\r\n    }\r\n    public function registerOperators($ops)\r\n    {\r\n        $this-&gt;availableOps = $ops;\r\n        foreach($this-&gt;availableOps as &$op) {\r\n            $op[1] = new $op[0];\r\n        }\r\n    }\r\n\r\n    \/\/...\r\n}\r\n<\/pre>\n<p>Diese Methoden sind erst einmal Verwaltungsmethoden, auf deren Arbeit wir sp\u00e4ter eingehen. Anhand des Namens d\u00fcrfte aber auch bereits jetzt ersichtlich werden, was diese tun. Wichtiger sind eher die folgenden Methoden, die uns innerhalb des Parsers eine Menge an Arbeit abnehmen und ein paar Code-Dopplungen zu vermeiden helfen:<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\n    private $level = 0;\r\n    private $context = array();\r\n\r\n    private function addContext($AllowedOps) {\r\n        $ctx = array(\r\n            'Args' =&gt; array(),\r\n            'Ops' =&gt; array(),\r\n            'Allow' =&gt; $AllowedOps\r\n            );\r\n        array_push($this-&gt;context, $ctx);\r\n        $this-&gt;level = count($this-&gt;context)-1;\r\n    }\r\n    private function &removeContext() {\r\n        $ctx = array_pop($this-&gt;context);\r\n        $this-&gt;level = count($this-&gt;context)-1;\r\n\r\n        if(empty($this-&gt;context)) {\r\n            $this-&gt;addContext($this-&gt;availableOps);\r\n        }\r\n\r\n        return $ctx;\r\n    }\r\n\r\n    private function isArgument($token) {\r\n        foreach($this-&gt;availableArgs as $type) {\r\n            $canParse = $type[1]-&gt;canParse($token);\r\n            if(false !== $canParse) {\r\n                \/\/This token could be parsed as some argument\r\n                return $canParse;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    private function &getContext() {\r\n        return $this-&gt;context[$this-&gt;level];\r\n    }\r\n    private function hasContextOps() {\r\n        $ctx =& $this-&gt;getContext();\r\n        return count($ctx['Ops']);\r\n    }\r\n    private function hasContextTerms() {\r\n        $ctx =& $this-&gt;getContext();\r\n        return count($ctx['Args']);\r\n    }\r\n    private function getContextOp() {\r\n        $ctx =& $this-&gt;getContext();\r\n        return $ctx['Ops'][0][1];\r\n    }\r\n\r\n    public function filterOps($ops, $curOp) {\r\n        \/\/Start with all operations allowed, but remove everything, until we find the operator\r\n        while($checkOp = array_shift($ops)) {\r\n            if($curOp[0] == $checkOp[0]) {\r\n                array_unshift($ops, $checkOp);\r\n                break;\r\n            }\r\n        }\r\n        return $ops;\r\n    }\r\n<\/pre>\n<p>Zuerst ein paar Worte zu den beiden Variablen $level und $context: $context bildet, wie man im Source erkennen kann, einen Stapel einer Struktur, die aus drei Teilen besteht. Diese 3 Teile sind Args f\u00fcr die in dieser Ebene gespeicherte Argumente\/Terme, Ops f\u00fcr in dieser Ebene gespeicherte Operatoren und Allow f\u00fcr in dieser Ebene erlaubte Operatoren. $level bildet eine Abk\u00fcrzung f\u00fcr den aktuell g\u00fcltigen Kontext, damit dieser nicht immer durch Z\u00e4hlen ermittelt werden muss.<\/p>\n<p>Die Methode addContext f\u00fcgt einen neuen, leeren Kontext ein und initialisiert, welche Operationen f\u00fcr diesen Kontext zul\u00e4ssig sind. Die Methode removeContext entfernt den aktuellen Kontext und gibt diesen zur\u00fcck. Au\u00dferdem stellt removeContext sicher, dass immer ein g\u00fcltiger Kontext existiert, was ggf. durch Anlegen eines leeren Kontext, in dem alle Operatoren erlaubt sind, erreicht wird. Mit den Methoden getContext und getContextOp kann der aktuelle Kontext, bzw. die in diesem g\u00fcltige Rechenoperation abgefragt werden.<\/p>\n<p>Die Methode filterOps schlie\u00dflich erm\u00f6glicht es, aus der Liste der Operatoren all diejenigen zu entfernen, die weniger stark als ein gegebener Operator binden. Hierbei richtet sich die Priorit\u00e4t eines Operators nach der Reihenfolge in der Operatorenliste, wobei sp\u00e4ter aufgef\u00fchrte Operatoren st\u00e4rker binden.<\/p>\n<h2>Die Token-Analyse<\/h2>\n<p>Womit wir glaube bei dem Kapitel w\u00e4ren, worauf die meisten sehr lange gewartet haben: Wie wird denn nun der eigentliche Parser Tree erzeugt? Auch hier bietet es sich an, den Quelltext etwas aufzuteilen, da dieser auf Grund der Komplexit\u00e4t recht umfangreich ist:<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\n    private $parseToken = array();\r\n\r\n    public function parse($tokenlist) {\r\n        \/\/We have an empty expression ...\r\n        $this->context = array();\r\n        $this->addContext($this->availableOps);\r\n\r\n        \/\/Start with the whole expression ...\r\n        $this->parseToken = $tokenlist;\r\n\r\n        \/\/As long as we still have tokens ...\r\n        while(count($this->parseToken) || $this->level || $this->hasContextOps()) {\r\n            \/\/Handle EOF.\r\n            if(!count($this->parseToken)) {\r\n                array_unshift($this->parseToken, array('eof', ''));\r\n            }\r\n\r\n            \/\/Grab one ...\r\n            $token = array_shift($this->parseToken);\r\n\r\n            \/\/...\r\n        }\r\n\r\n        $this->root = $this->context[0]['Args'][0];\r\n\r\n        return true;\r\n    }\r\n<\/pre>\n<p>Der erste Teil unseres eigentlichen Parsers wird durch einen kurzen Teil f\u00fcr eine Reihe kleinerer Initialisierungen gebildet, der von der Hauptschleife gefolgt wird und schlie\u00dflich mit ein paar Aufr\u00e4umarbeiten beendet wird. Wer sich jetzt wundert, wie das mit dem $this->context[0][&#8218;Args&#8216;][0] zu Stande kommt: Das besagt einfach nur, vom letzten verbleibenden Kontext bitte das erste Argument nehmen. Dieses existiert an dieser Stelle immer, da der Baum zur Not solange reduziert und zusammengefasst wird, bis keine verbleibende Operation mehr vorliegt. L\u00e4sst sich aus den vorhandenen Operationen nicht der gesamte Baum reduzieren, wird dies bereits in der Schleife erkannt und als Fehler gemeldet.<\/p>\n<p>In jedem Verarbeitungsschritt wird nun ein Token eingelesen und in der Baumstruktur eingeordnet. Hier gibt es 2 voneinander getrennte Schritte: Pr\u00fcfen auf Argumente vs. verarbeiten von Operatoren.<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\n            \/\/Check if it's an argument\r\n            $tokenArgType = $this->isArgument($token);\r\n\r\n            \/\/If we have an argument, let's check how to continue ...\r\n            if(false !== $tokenArgType) {\r\n                \/\/Do we already have an operator\r\n                if($this->hasContextOps()) {\r\n                    \/\/There are three possibilities:\r\n                    $ctx =& $this->getContext();\r\n                    $parseState = $this->getContextOp()->canParse($token, $ctx['Args'], $ctx['Ops']);\r\n\r\n                    \/\/ ...\r\n                }\r\n\r\n                \/\/Put the argument in place!\r\n                $ctx =& $this->getContext();\r\n                array_push($ctx['Args'], $tokenArgType);\r\n\r\n                \/\/Continue with next token\r\n                continue;\r\n            }\r\n\r\n            \/\/ ...\r\n<\/pre>\n<p>Treffen wir bei unserer Suche auf ein Argument, so gibt es zwei F\u00e4lle, die wir betrachten m\u00fcssen, denn entweder haben wir bereits einen Operator verf\u00fcgbar oder wir haben bisher einen leeren Kontext. Ist der Kontext bisher leer, gehen wir einfach einmal davon aus, dass wir einfach das Argument speichern und im n\u00e4chsten Schritt dann mehr dazu erfahren. Gibt es bereits einen Operator f\u00fcr diesen Kontext, m\u00fcssen wir nat\u00fcrlich auswerten, ob dieser mit dem gefundenen Argument einverstanden ist:<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\n                    if(false == $parseState) {\r\n                        \/\/0. The operator can't handle this\r\n                        echo \"Syntaxfehler: Operator kann Token nicht parsen\\n\";\r\n                    } else if(true === $parseState) {\r\n                        \/\/1. The operator just want's more data ;-)\r\n                        \/\/e.g. Identifier -->\"(\"< -- Term ) --> Function call\r\n                        \/\/To get this token, if we get the identifier here, we just need to continue.\r\n                    } else if(PARSE_TERM == $parseState) {\r\n                        \/\/ 2. The Parser requires a second operator\r\n                        \/\/ This value means: all operators including more-binding ones are allowed\r\n                        $ctx =& $this->getContext();\r\n                        $ctxOp = $ctx['Ops'][0];\r\n                        $newOps = $this->filterOps($ctx['Allow'], $ctxOp);\r\n                        $this->addContext($newOps);\r\n                    } else if(PARSE_SUBTERM == $parseState) {\r\n                        \/\/ 3. The Parser requires a sub-expression\r\n                        \/\/ This value means: all operators are allowed\r\n                        $this->addContext($this->availableOps);\r\n                    } else if(PARSE_EOF == $parseState) {\r\n                        \/\/ 4. The Parser can terminate this state\r\n                        \/\/The goal here is to fill in the params into a new object and replace the current level's contents with this object\r\n                        $ctx =& $this->removeContext();\r\n                        array_push($ctx['Args'], $tokenArgType);\r\n\r\n                        $tokenArgType = clone $this->getContextOp();\r\n                        $tokenArgType->storeArgs($ctx['Args']);\r\n                    } else {\r\n                        \/\/ 5. We have an internal error\r\n                        echo \"Interner Fehler\\n\";\r\n                        return false;\r\n                    }\r\n<\/pre>\n<p>In $parserState steht f\u00fcr diese Verarbeitung die R\u00fcckgabe der oben bereits besprochenen canParse-Methode. Standard-Aktion ist hierbei f\u00fcr alle Token, dass sie entweder einen neuen Kontext f\u00fcr nachfolgende Operationen anlegen, oder einen bestehenden Kontext aufl\u00f6sen, um daraus einen abgeschlossenen Operator zu bilden, der dann im aktuellen Kontext eingef\u00fcgt wird.<\/p>\n<p>Ist dieser Schritt erledigt, k\u00f6nnen wir zwar bereits Argumente einsortieren, da wir aber bisher keine Operatoren verarbeiten, kommen wir insgesamt nicht allzu weit. Das \u00e4ndert sich aber im folgenden Schritt:<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\n            $ctx =& $this->getContext();\r\n\r\n            \/\/We seem to have gotten an operator\r\n            $canParseOps = array();\r\n            foreach($ctx['Allow'] as $allowedOp) {\r\n                $canParse = $allowedOp[1]->canParse($token, $ctx['Args'], $ctx['Ops']);\r\n                if($canParse) {\r\n                    $canParseOps[] = array($allowedOp, $canParse);\r\n                }\r\n            }\r\n\r\n            \/\/More than one operator can handle this; but we can't ;-)\r\n            if(1 &lt; count($canParseOps)) {\r\n                echo \"Syntaxfehler (Multi Ops!)\\n\";\r\n                var_dump($canParseOps, $ctx);\r\n                return false;\r\n            }\r\n\r\n            \/\/There are no operators left to handle this; try unstacking ;-)\r\n            if(0 == count($canParseOps)) {\r\n                \/\/ ...\r\n            }\r\n\r\n            $currentOp = array_shift($canParseOps);\r\n            list($tokenArgType, $parseState) = $currentOp;\r\n\r\n            if(false === $parseState) {\r\n                \/\/This should never get reached!\r\n                echo \"Interner Fehler\\n\";\r\n                return;\r\n            }\r\n\r\n            $ctx =& $this->getContext();\r\n            array_push($ctx['Ops'], $tokenArgType);\r\n\r\n            \/\/ ...\r\n<\/pre>\n<p>Im Falle eines Operators ist die Arbeit etwas umfangreicher. Zuerst fragen wir bei allen Operatoren an, ob diese mit dem aktuellen Kontext etwas anfangen k\u00f6nnen und schauen dann, dass sich hoffentlich nur einer meldet. Melden sich mehrere, ist dies aller wahrscheinlichkeit ein Syntax-Fehler, oder wir haben uns vertan \ud83d\ude09 In jedem Fall k\u00f6nnen wir aber unseren Operator beruhigt auf den Operatoren-Stack legen, da Operatoren, die bereits vorher bereits einmal nein gesagt haben, aus der aktuellen Behandlung bereits ausgeschlossen wurden. Ein pl\u00f6tzliches &#8222;ach doch&#8220; ist somit unwahrscheinlich und deutet daher auf einen Fehler in unserer Implementierung der Operatoren hin, sollte es doch passieren. <\/p>\n<p>Mit unserem frisch geparsten Operator auf dem Stack k\u00f6nnen wir uns nun um die Verarbeitung des $parserState k\u00fcmmern, was analog der bereits f\u00fcr Argumente gesehenen Verarbeitung erfolgt:<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\n            if(true === $parseState) {\r\n            } else if(PARSE_TERM == $parseState) {\r\n                \/\/ 2. The Parser requires a second operator\r\n                \/\/ This value means: all operators including more-binding ones are allowed\r\n                $ctx =& $this->getContext();\r\n                $ctxOp = $ctx['Ops'][0];\r\n                $newOps = $this->filterOps($ctx['Allow'], $ctxOp);\r\n                $this->addContext($newOps);\r\n            } else if(PARSE_SUBTERM == $parseState) {\r\n                \/\/ 3. The Parser requires a sub-expression\r\n                \/\/ This value means: all operators are allowed\r\n                $this->addContext($this->availableOps);\r\n            } else if(PARSE_EOF == $parseState) {\r\n                \/\/ 4. The Parser can terminate this state\r\n                \/\/The goal here is to fill in the params into a new object and replace the current level's contents with this object\r\n                $tokenArgType = clone $this->getContextOp();\r\n                $ctx =& $this->removeContext();\r\n                $tokenArgType->storeArgs($ctx['Args']);\r\n\r\n                \/\/Put the argument in place!\r\n                $ctx =& $this->getContext();\r\n                array_push($ctx['Args'], $tokenArgType);\r\n            } else {\r\n                \/\/ 5. We have an internal error\r\n                echo \"Interner Fehler (Unimplemented)\\n\";\r\n                return false;\r\n            }\r\n<\/pre>\n<p>Womit wir auch schon die normale Verarbeitung von Argumenten und Operatoren abgeschlossen h\u00e4tten. Jetzt kommen wir zu den Sonderf\u00e4llen \ud83d\ude09 Denn manchmal kommt es vor, dass in einem Zustand keiner unserer Operatoren einen bestimmten Token haben m\u00f6chte. In diesem Fall gibt es zwei M\u00f6glichkeiten:<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\n                if(!$this->hasContextOps()) {\r\n                    \/\/ ...\r\n                } else if($this->hasContextTerms()) {\r\n                    \/\/ ...\r\n                } else {\r\n                    \/\/No arguments and no terms ... It's an error!\r\n                    echo \"Syntaxfehler (No Ops!)\\n\";\r\n                    $ctx =& $this->getContext();\r\n                    var_dump($ctx);\r\n                    return false;\r\n                }\r\n\r\n                if(!empty($token)) {\r\n                    array_unshift($this->parseToken, $token);\r\n                }\r\n                continue;\r\n<\/pre>\n<p>Sollten wir hierbei einen Token bekommen haben, der nicht das Ende des Ausdrucks angibt, so geben wir bescheid, dass wir diesen noch einmal vorgelegt brauchen, da wir uns nicht um diesen k\u00fcmmern konnten, da wir erst einmal am Parser Tree kleinere Umbauarbeiten vorzunehmen hatten.<\/p>\n<p>Im Falle, dass wir bisher noch keinen Operator f\u00fcr einen Kontext haben, k\u00f6nnen wir unser Gl\u00fcck damit versuchen, dass wir den aktuellen Kontext aufl\u00f6sen und die bisherigen Ergebnisse an den Parent \u00fcberreichen, was wie folgt umgesetzt werden kann:<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\n                    \/\/ Only try unstacking if we don't have any operator on this level\r\n                    \/\/Also, for now, only unstack for one single argument\r\n                    if(1 != $this->hasContextTerms()) {\r\n                        echo \"Syntaxfehler (Unstack Arg Mismatch!)\\n\";\r\n                        var_dump($ctx);\r\n                        return false;\r\n                    }\r\n\r\n                    \/\/Restore the previous level\r\n                    $ctx =& $this->removeContext();\r\n                    $args = $ctx['Args'];\r\n\r\n                    \/\/We got all we need from the child context; switch to the current one.\r\n                    $ctx =& $this->getContext();\r\n\r\n                    \/\/Apply the last argument on this level\r\n                    $currentOp = $this->getContextOp();\r\n                    $parseState = $currentOp->canParse($args[0], $ctx['Args'], $ctx['Ops']);\r\n\r\n                    \/\/Check if we got some more finished tokens\r\n                    if(false === $parseState) {\r\n                        echo \"Syntaxfehler (Unstack Token Syntax!)\\n\";\r\n                        var_dump($ctx);\r\n                        return false;\r\n                    }\r\n\r\n                    \/\/Store the argument as part of the current context\r\n                    array_push($ctx['Args'], $args[0]);\r\n\r\n                    if(true === $parseState) {\r\n                        \/\/No need for further actions; we'll continue in the next step\r\n                    } else if(PARSE_TERM == $parseState) {\r\n                        \/\/The next thing expected is a sub-extression.\r\n                        \/\/So we just create one.\r\n                        $ctx =& $this->getContext();\r\n                        $ctxOp = $ctx['Ops'][0];\r\n                        $newOps = $this->filterOps($ctx['Allow'], $ctxOp);\r\n                        $this->addContext($newOps);\r\n                    } else if(PARSE_SUBTERM === $parseState) {\r\n                        \/\/The next thing expected is a whole new expression allowing all arguments\r\n                        \/\/So we just create one.\r\n                        $this->addContext($this->availableOps);\r\n                    } else if(PARSE_EOF === $parseState) {\r\n                        \/\/This operation just finished one token.\r\n                        \/\/Exit this context and round things up.\r\n                        \/\/Put the argument in place!\r\n                        $tokenArgType = clone $this->getContextOp();\r\n                        $ctx =& $this->removeContext();\r\n                        $tokenArgType->storeArgs($ctx['Args']);\r\n\r\n                        \/\/Put the argument in place!\r\n                        $ctx =& $this->getContext();\r\n                        array_push($ctx['Args'], $tokenArgType);\r\n                    }\r\n<\/pre>\n<p>F\u00fcr den Fall, dass wir bereits einen Operator haben, fragen wir diesen einfach, ob das zuletzt hinzugef\u00fcgte Argument diesen abgeschlossen hat:<\/p>\n<pre lang=\"php\">\r\n                    \/\/We have an operator; let's pretend, the last operation was just performed\r\n                    $ctx =& $this->getContext();\r\n                    $tokenArgType = array_pop($ctx['Args']);\r\n                    $currentOp = $this->getContextOp();\r\n                    $parseState = $currentOp->canParse($tokenArgType, $ctx['Args'], $ctx['Ops']);\r\n                    array_push($ctx['Args'], $tokenArgType);\r\n\r\n                    \/\/Check if we got some more finished tokens\r\n                    if(false === $parseState) {\r\n                        echo \"Syntaxfehler (Unstack MultiToken Syntax!)\\n\";\r\n                        var_dump($ctx);\r\n                        return false;\r\n                    } else if(true === $parseState) {\r\n                        \/\/We got a token, that can't be handled, but the previous one was asked to continue --> Syntax error\r\n                        echo \"Syntaxfehler (Unstack Continuation Requested!)\\n\";\r\n                        var_dump($ctx);\r\n                        return false;\r\n                    } else if(PARSE_TERM == $parseState) {\r\n                        \/\/We got a token, that can't be handled, but the previous one was asked for a term --> Syntax error\r\n                        echo \"Syntaxfehler (Unstack Term Requested!)\\n\";\r\n                        var_dump($ctx);\r\n                        return false;\r\n                    } else if(PARSE_SUBTERM === $parseState) {\r\n                        \/\/We got a token, that can't be handled, but the previous one was asked for a subterm --> Syntax error\r\n                        echo \"Syntaxfehler (Unstack Subterm Requested!)\\n\";\r\n                        var_dump($ctx);\r\n                        return false;\r\n                    } else if(PARSE_EOF === $parseState) {\r\n                        \/\/This operation just finished one token.\r\n                        \/\/Exit this context and round things up.\r\n                        $tokenArgType = clone $this->getContextOp();\r\n                        $ctx =& $this->removeContext();\r\n                        $tokenArgType->storeArgs($ctx['Args']);\r\n\r\n                        \/\/Put the argument in place!\r\n                        $ctx =& $this->getContext();\r\n                        array_push($ctx['Args'], $tokenArgType);\r\n                    }\r\n<\/pre>\n<p>Was hier auffallen sollte, ist die Tatsache, dass au\u00dfer PARSE_EOF alle m\u00f6glichen Schritte zu einem Parser-Fehler f\u00fchren, was insofern logisch ist, dass der Parser zu dem hier erneut hinzugef\u00fcgten Parser bereits einmal zugestimmt hat. Da diese Pr\u00fcfung aber rein deterministisch sein muss, um einen konsistenten Parser-Baum zu erhalten, ist in diesem Fall nur PARSE_EOF zul\u00e4ssig. Ferner f\u00fchren PARSE_TERM und PARSE_SUBTERM an dieser Stelle zu einem Widerspruch, da der Parser aufgefordert wird, mit dem aktuellen Kontext fortzusetzen, was aber niemand tun m\u00f6chte.<\/p>\n<p>Womit wir auch bereits durch w\u00e4ren &#8230; Naja, nicht ganz \ud83d\ude1b<\/p>\n<h2>Der Testlauf<\/h2>\n<p>Denn wenn wir einfach nur uns den bisher erstellten Parser instantiieren w\u00fcrden, um dann <\/p>\n<pre lang=\"php\" escaped=\"true\">$tree-&gt;parse($expr);<\/pre>\n<p> aufzurufen, w\u00fcrde uns der Parser mit Unglaube anschauen und seinen Dienst vereiteln. Da fehlt n\u00e4mlich noch was, was wir oben mit als erstes f\u00fcr den Parser implementiert haben. Richtig! Die verf\u00fcgbaren Argumente und Operatoren.\u00dcbergeben wir diese vor dem Parsen, geht es:<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\n$example = array(\r\n    '1',\r\n    '+',\r\n    '2',\r\n    '*',\r\n    '3',\r\n    '+',\r\n    '4',\r\n    );\r\n\r\necho \"&lt;pre&gt;\";\r\necho \"Test:\\n\";\r\n$tree = new ParserTree();\r\n$tree-&gt;registerArguments(array(\r\n    array('ArgNumber'),\r\n    array('ArgString'),\r\n    array('ArgVariable'),\r\n    ));\r\n\r\n$tree-&gt;registerOperators(array(\r\n    \/\/Brackets have highest Priority\r\n    \/\/array('OpLeftBracket'),\r\n\r\n    array('OpBinaryFieldStatic'),\r\n    array('OpBinaryFieldObject'),\r\n\r\n    array('OpUnaryPreIncrement'),\r\n    array('OpUnaryPreDecrement'),\r\n    array('OpUnaryPostIncrement'),\r\n    array('OpUnaryPostDecrement'),\r\n\r\n    array('OpBinaryAssign'),\r\n\r\n    array('OpUnaryPlus'),\r\n    array('OpUnaryMinus'),\r\n    array('OpUnaryBitNot'),\r\n    array('OpUnaryLogicNot'),\r\n\r\n    array('OpBinaryConcat'),\r\n\r\n    array('OpBinaryPlus'),\r\n    array('OpBinaryMinus'),\r\n    array('OpBinaryMultiply'),\r\n    array('OpBinaryDivide'),\r\n    array('OpBinaryModulo'),\r\n    array('OpBinaryPower'),\r\n\r\n    array('OpBinaryBitAND'),\r\n    array('OpBinaryLogicAND'),\r\n    array('OpBinaryBitOR'),\r\n    array('OpBinaryLogicOR'),\r\n    array('OpBinaryBitXOR'),\r\n    array('OpBinaryLogicXOR'),\r\n\r\n    array('OpBinaryEqual'),\r\n    array('OpBinaryNotEqual'),\r\n\r\n    array('OpBinaryIdentical'),\r\n    array('OpBinaryNotIdentical'),\r\n\r\n    array('OpTernaryIf'),\r\n    ));\r\n$tree-&gt;parse($example);\r\n$tree-&gt;dump();\r\necho \"&lt;\/pre&gt;\";\r\n<\/pre>\n<p>L\u00e4uft alles wie gew\u00fcnscht, sehen wir einen netten Parser-Baum auf unserem Bildschirm:<\/p>\n<pre>\r\nGenerierter Parser-Tree:\r\n----\r\nOpBinaryPlus:\r\n  OpBinaryPlus:\r\n    ArgNumber:\r\n      1\r\n    OpBinaryMultiply:\r\n      ArgNumber:\r\n        2\r\n      ArgNumber:\r\n        3\r\n  ArgNumber:\r\n    4\r\n----\r\n<\/pre>\n<h2>Bindungskr\u00e4fte<\/h2>\n<p>Was nun noch fehlt, ist die korrekte Behandlung von Operator-Assoziativit\u00e4t und das Erlauben gleichrangiger Operatoren. Das ist in diesem einfachen Parser bisher noch nicht implementiert, l\u00e4uft aber auf Grund der Art, wie derzeit die Baumstruktur aufgebaut wird, auf eine bevorzugt rechtsassoziative Behandlung von Operatoren hinaus. Dies mag zwar f\u00fcr viele F\u00e4lle reichen, erzeugt aber bereits bei Ketten-Divisionen nicht ganz offensichtliche Effekte. Denn definiert man die Division rechtsassoziativ, so ist 1 \/ 2 \/ 3 durchaus korrekt 1.5, w\u00e4hrend uns die Schulmathematik wei\u00dfmachen m\u00f6chte, dass da 1 \/ 6 rauskommt. Aber dazu an anderer Stelle einmal mehr, weil es sonst diesen bereits sehr l\u00e4nglichen Beitrag entg\u00fcltig sprengen w\u00fcrde.<\/p>\n<h2>Zum Abschluss<\/h2>\n<p>Bliebe beim Abschluss dieses Beitrags nur noch zu erw\u00e4hnen, dass dieser Parser bei weitem nicht vollst\u00e4ndig ist und durchaus eine Reihe von Problemen aufweist, die auf Anhieb nicht auffallen m\u00f6gen. Aber bereits bei Dingen wie einem Operator, der abh\u00e4ngig vom n\u00e4chsten Schl\u00fcsselwort bin\u00e4r oder trin\u00e4r ist, scheitert der Parser in dieser Form. hierf\u00fcr l\u00e4sst sich prinzipiell eine L\u00f6sung finden, die durch ein &#8222;Look Ahead&#8220; durch die canParse-Methode m\u00f6glich ist, aber die hierf\u00fcr am Parser n\u00f6tigen \u00c4nderungen sind nicht offensichtlich und f\u00fchren wie eine ganze Reihe anderer m\u00f6glicher \u00c4nderungen etwas zu weit. Interessanterweise l\u00e4sst sich die foreach-Syntx von PHP jedoch parsen, obwohl diese variabel ist, da man mit Hilfe des =>-Operators als Hilfsmittel beide F\u00e4lle f\u00fcr den Schleifenkopf abdecken kann.<\/p>\n<p>Ich habe in diesem Beitrag bewusst auch nicht die Implementierung aller Operatoren angegeben, da diese bis auf wenige Ausnahmen offensichtlich sind und daher hier unn\u00f6tig ~400 Zeilen verschlucken w\u00fcrden (Ja, die Operatoren erzeugen auf Grund ihrer Anzahl extrem Spaghetti-Code). Wer den gesamten Parser einmal haben m\u00f6chte, kann mir bescheid geben, dann sende ich ihm eine Kopie.<\/p>\n<p>Ansonsten hoffe ich, dieser Beitrag war informativ. Wer noch fragen hat, darf diese wie immer gern in den Kommentaren anbringen, oder direkt an mich via Mail zusenden.<\/p>\n<p class=\"wp-flattr-button\"><a href=\"http:\/\/blog.benny-baumann.de\/?flattrss_redirect&amp;id=1002&amp;md5=901435a6ed59c90ec09eec581d5a0d2c\" title=\"Flattr\" target=\"_blank\"><img src=\"http:\/\/blog.benny-baumann.de\/wp-content\/plugins\/flattr\/img\/flattr-badge-large.png\" srcset=\"http:\/\/blog.benny-baumann.de\/wp-content\/plugins\/flattr\/img\/flattr-badge-large.png\" alt=\"Flattr this!\"\/><\/a><\/p>","protected":false},"excerpt":{"rendered":"<p>Wenn man in seinem Programm mathematische Ausdr\u00fccke, die vom Nutzer eingegeben wurden, auswerten m\u00f6chte, so kann man dies entweder mit Hilfe b\u00f6sartiger Konstrukte tun, oder aber, mit Hilfe eines Parsers, die Ausdr\u00fccke selber auswerten und dann in einer kontrollierten Umgebung ausf\u00fchren. Die M\u00f6glichkeit einer solchen kontrollierten Ausf\u00fchrung ist in Script-Sprachen wie JavaScript notwendig, kann aber [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[29],"tags":[310,98,311,21],"class_list":["post-1002","post","type-post","status-publish","format-standard","hentry","category-software","tag-compiler","tag-developement","tag-parser","tag-php"],"_links":{"self":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/1002","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1002"}],"version-history":[{"count":5,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/1002\/revisions"}],"predecessor-version":[{"id":1133,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/1002\/revisions\/1133"}],"wp:attachment":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1002"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1002"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1002"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}