 Ja, herzlich willkommen zur vierten Einheit im Themenblock Söntax, der Veranstaltung Programmierparadigmen. Im Söntax-Themeblock geht es darum, wie wir spezifizieren können, welcher Code überhaupt Teile einer Programmiersprache ist. Und um das jetzt ein bisschen wieder einzuordnen, also wir hatten gesagt, es gibt einmal die Tokens, das sind so die Wörter der Programmiersprache. Und das Tool, was bestimmt oder feststellt, ob eine gegebenen Zeichenkette tatsächlich ein legaler Token ist, das nennt sich Scanner. Und was der Scanner als Ergebnis jetzt liefert, ist eine Sequenz von Tokens. Und der nächste Schritt, den wir uns in dieser und in den kommenden Einheiten anschauen werden, ist der Pasa, nämlich das Tool, was dann diese Sequenz von Tokens nimmt, feststellt, ob die tatsächlich ein legales Programm ergeben und wenn ja das Ganze als Pastry oder als Baum zurückgibt. Hier ist nochmal die Übersicht, also wir sind jetzt in diesem letzten großen Teil des Themenblocks Söntax, wo wir uns mit Pasing beschäftigen. Und insbesondere werden wir uns top down und bottom up Pasa anschauen. Was ist das überhaupt? Das sehen wir im Prinzip schon mal ganz grob erklärt hier. Also jeder Pasa hat das Ziel, den gegebenen Stream aus Tokens oder die Sequenz von Tokens in ein Pastry, also eine Baumstruktur umzuwandeln. Und die Frage ist jetzt, wie genau man das macht. Und da gibt es im Prinzip zwei grobe Antworten, nämlich einmal top down und einmal bottom up. Top down bedeutet, dass wir vom Wurzelknoten des Pastries, den wir erstellen wollen, anfangen. Und dann Schritt für Schritt die Nicht-Themen-Terminale, die da noch drin sind, weiter ausfalten. Und mithilfe der Produktionsregeln der Grammatik in weitere Nicht-Terminale oder schlussendlich dann Terminale umwandeln. Eine große Herausforderung ist dabei festzulegen, was man tut, falls mehrere Regeln angewandt werden können. Also wenn ein Nicht-Terminal nach mehreren Dingen ableidbar ist, zum Beispiel, weil da der senkrechte Strich in der Grammatik verwendet wird, der sagt dieses oder jenes ist möglich. Dann muss dieser top down Pasa nämlich vorher sagen, welche der Regeln zu nutzen ist und wie man das macht, werden wir uns anschauen. Die andere Variante ist, dass das ganze bottom up, also von den Leaf-Notes bis hoch zum Wurzelknoten konstruiert wird. Und was der Pasa da macht, ist, dass ihr die Tokens Schritt für Schritt einliest und immer, wenn sich eine Menge von Tokens in ein Teilbaum zusammensetzen lässt, dann macht der Pasa das. Und wenn immer mehrere Teilbäume in größere Bäume zusammensetzt werden kann, dann wird auch das wieder gemacht, bis wir irgendwann diesen Baum von unten herauf komplett aufgebaut haben und den vollständigen Parsery erhalten. Bevor wir uns jetzt Algorithmen anschauen, die top down und bottom up Parsing implementieren, schauen wir uns erst mal ein Beispiel an, um das ganze intuitiv besser zu verstehen. Also werden wir eine kleine Grammatik einer fiktiven Programmiersprache. In der Programmiersprache gibt es Statements, die nennt sich Simple Statement, und diese Statements können ineinander verschachtelt werden in Blöcke, die dann jeweils mit Beginn anfangen und mit End enden. Unser Start-Symbol ist hier oben das P, was für Programm steht, und das bedeutet, oder sagt, dass jedes Programm mit Beginn und End umklammert ist und innen drin dann dieses Nicht-Terminal-Doppel-S bzw. Zeichen oder Tokens, die daraus expandiert werden können, enthält. Das Doppel-S steht einfach nur für mehrere Statements und heißt konkret, dass da entweder ein Statement drin sein kann, was dann gefolgt ist, von wieder dem Doppel-S, und das Doppel-S kann schlussendlich auch zum Epsilon abgeleitet werden. Das heißt, wir können schlussendlich aufhören, Statements mit Semicolon aneinander zu ketten. S selbst ist entweder ein Simple Statement oder eben dieser Block, den wir hier unten sehen, der mit Beginn und End umklammert ist und in der Mitte dann wieder ein weiteres Doppel-S für weitere Statements hat. Kleines Beispiel-Programm, was man daraus ableiten kann, ist hier unten zu sehen, wo wir das Beginn und End für das komplette Programm haben und innen drin ist in dem Fall nur ein Block, der aus zwei Simple Statements besteht. Wir könnten darin aber natürlich auch weitere Beginn und End-Blöcke mit weiteren Statements oder noch weiteren Blöcken haben. Dann schauen wir uns mal für dieses Beispiel an, wie das Paasen mit einem Top-Down oder Bottom-Up-Paser ungefähr ablaufen würde und zwar zunächst für Top-Down. Also der Top-Down-Paser beginnt mit dem Statement der Grammatik, das ist in unserem Fall ein P. Ich schreibe jeweils noch dran, in welche Reihenfolge die einzelnen Teile dieses Baumes aufgebaut werden, also die Eins würde einfach nur, dass das hier der erste Schritt ist. Für P gibt es eine Regel, die sagt zunächst kommt Beginn, dann Doppel-S und dann End. Das heißt, wir werden drei Kinder dieses P-Knotens haben, nämlich das Beginn hier, das Doppel-S hier und dann das End hier ganz rechts. Der zweite Schritt ist, dass dieses Doppel-S jetzt expandiert wird. Das heißt, der Paser schaut in die Grammatik, stellt fest, oh, es gibt zwei Regeln für Doppel-S. Wir könnten das entweder nach S, Semikolon, Doppel-S, ableiten oder nach dem leeren Not. Das heißt, der Paser muss irgendwo her wissen, welche diese Regeln er jetzt nimmt und wie das genau funktioniert, wenn wir noch genau erkennen. Gehen wir jetzt mal davon aus, dass wir magischerweise wissen, dass die erste Regel benutzt werden soll. Das heißt, wir leiten dieses Doppel-S nach S, Semikolon, Doppel-S ab. Das heißt, hier gibt es wieder drei Kinder, nämlich das S, das Semikolon und dann ein neues Doppel-S. Der Paser ist weiter top-down und geht in dem Fall von links nach rechts. Das heißt, als dritten Schritt würden wir dieses S hier drüben weiter ableiten. Hier ist wieder die selbe Situation wie gerade eben schon. Wir haben zwei Regeln, die wir benutzen könnten, entweder S nach Simpel-Statement abzuleiten oder S nach Beginn Doppel-S end abzuleiten. Woher der Paser jetzt weiß, welche Regel zu nehmen ist, wird wie gesagt später erklärt. Gehen wir mal davon aus, dass er magischerweise weiß, dass hier ein Simpel-Statement hergehört. Das heißt, wir können uns jetzt dem letzten aktuell verbliebenen Nicht-Terminal zuwenden, nämlich dem Doppel-S hier drüben. Wieder gibt es zwei Regeln und wieder gehen wir mal davon aus, dass der Paser magischerweise weiß, dass hier S-S-Semikolon-Doppel-S zuwählen ist. Das heißt, wir haben jetzt wieder zwei neue Nicht-Terminale, die noch nicht expandiert sind. Der Paser muss weiterarbeiten, bis alle Nicht-Terminale in Terminale expandiert wurden. Als nächstes ist dieses S hier drüben dran mit dem fünften Schritt. Das wird zu einem weiteren Simpel-Statement abgeleitet und dann schlussendlich als sechster und dann auch letzter Schritt leiten wir dieses Doppel-S nach dem leeren Wort epsilon ab. Und was wir dann schlussendlich bekommen, ist ein vollständiger Pass-Tree, der genau diese Tokens, die wir in unserem Beispiel haben, beschreibt, dass die Leaf-Notes des Baumes sind. So, alternativ zum Top-Down-Passing, schauen wir uns als nächstes Beispiel mal an, wie das ganze Bottom-Up funktionieren könnte. In dem Fall würde der Paser damit beginnen, einen Token nach dem anderen einzulesen. Das heißt, zunächst lesen wir dieses Beginn ein. Dieses eine Token ist nicht genug, um irgendeine der Regeln, die wir in der Kramatik haben, anzuwenden. Das heißt, der Toker macht weiter und liest den nächsten Token ein, nämlich dieses Simpel-Statement. Simpel-Statement ist ausreichend, um eine Regel anzuwenden, nämlich die Vorletzte in der Kramatik. Das heißt, wir sagen, ah, Simpel-Statement kann vom Nicht-Terminal S abgeleitet werden. Und der erste Schritt ist also diese Regel hier einmal anzuwenden. Mit allem, was wir jetzt drin haben, können wir wieder keine weitere Regel anbenden. Das heißt, der Paser liest weiter und liest als nächstes dieses Sämikolan. Das ist auch nicht ausreichend, um eine weitere Regel anzuwenden. Also wird wieder ein Token eingelesen, nämlich ein weiteres Simpel-Statement. Und hier können wir dieselbe Regel, die wir gerade eben schon mal benutzt haben, anwenden, indem wir feststellen, dass das S nicht-Terminal zum Simpel-Statement abgeleitet werden kann. Anschließend geht das Spiel weiter. Wir haben einen weiteren Token, nämlich ein weiteres Simikolan. Und stellen fest, dass auch das alles noch nicht ausreicht, um weiter einzulesen. Was der Paser an der Stelle machen würde, ist das letzte Token einlesen, das End. Und jetzt reicht es immer noch nicht, um eine weitere Regel anzuwenden. Aber zum Glück haben wir diese dritte Regel hier, die uns zeigt, wir können Doppel-S nach Epsilon ableiten. Und wenn sonst nichts geht, dann geht diese Regel. Das heißt, der Paser kann die jetzt einfach mal hier irgendwo einfügen und sagen, okay, ich wende als dritten Schritt diese Regel an, die es mir sagt, ich kann Doppel-S nach Epsilon ableiten. Jetzt sind wir in der Situation, dass wir aus den Teilbäumen, die wir hier schon haben, weitere Regeln ableiten können. Und insbesondere können wir die zweite Regel benutzen, die sagt, Doppel-S kann abgeleitet werden nach S-Simikolan-Doppel-S. Denn wir haben ja ein S, ein Simikolan unter Doppel-S hier in unserem Wald von Teilbäumen stehen. Und zwar indem wir die Regel hier anbenden als vierten Schritt und sagen, dass Doppel-S nach diesen drei Sachen S-Simikolan-Doppel-S abgeleitet werden kann. Jetzt geht es Spiel weiter, wir schauen wieder, was können wir mit den Teilbäumen, die wir schon haben, anstellen und stellen fest, dass wir nochmal diese zweite Regel benutzen können. Hier im fünften Schritt, wo das S von hier trüben, das Simikolan von hier und das Doppel-S, was wir gerade erstellt haben, verwendet werden. Und schlussendlich können wir mit dem, was jetzt noch übrig ist, den Beginn, dem Doppel-S mit der Fünfe oben dran und dem End ganz da unten, die allererste Regel benutzen, als sechsten und letzten Schritt, indem wir sagen, ja, das P wird abgeleitet nach Beginn, Doppel-S und End. Und siehe da, da haben wir wieder unseren Pass-Tree, diesmal aufgebaut, von unten nach oben. Und wie das algorithmisch dann genau umgesetzt wird, sehen wir später dann auch noch. So, jetzt habe ich zwei Arten, wie man diesen Pass-Tree aufbauen kann, schon mal am Beispiel illustriert. Allgemein gibt es eine ganze Reihe von verschiedenen Parsing-Algorithmen, die man klassifizieren kann und die zwei wichtigsten Klassen sehen wir hier. Das sind nämlich diese LLK-Paser und die LRK-Paser. Der wahrscheinlich wichtigste Unterschied zwischen den beiden ist, dass die LLK-Paser top-down funktionieren, also so wie das erste kleine Beispiel, was ich gezeigt habe, woher die LR-Paser bottom-up funktionieren und den Baum sozusagen von unten nach oben aufbauen, so wie im zweiten Beispiel, was ich gezeigt habe. Woher kommen diese Buchstaben, also das LL und das LR, der jeweils erste Buchstabe spricht oder beschreibt die Reihenfolge, in der die Tokens gelesen werden und in beiden Fällen ist das Left to Right, also von links nach rechts, also sozusagen in der Reihenfolge, in der wie üblicherweise auch die Tokens in der Programmiersprache selbst als Menschen lesen würden. Der jeweils zweite Buchstabe, nämlich das L bzw. R bei der zweiten Klasse von Parsern, beschreibt die Reihenfolge, in der die Ableitung vorgenommen werden. Bei den LLK-Pasern passiert das auch von links nach rechts. Das heißt, wenn wir mehrere Stellen haben, wo wir jetzt ein Nicht-Determinal durch eine Regel weiter ableiten können, dann fangen wir bei dem Link-Kisten Nicht-Terminal in unserem Baum an. Bodengegen wir bei den LR-Pasern den rechtesten Suchbaum benutzen und die Regeln zuerst auf der rechten Seite des Baumes anwenden. Die Algorithmen, die diese verschiedenen Klassen von Parsing Algorithmen implementieren, gibt es eine ganze Reihe von Algorithmen. Wir werden uns zwei davon hier angucken. Zwei Klassen von Algorithmen, nämlich die predictive, also vorhersagenden Algorithmen für die LLK-Paser, wo das Vorhersagende ist, dass der Parser auswählen muss, welche aus mehreren möglichen Regeln angewandt wird. Auf der anderen Seite schauen wir uns Shift-Reduce Algorithmen für die LR-Paser an und was das genau bedeutet, sehen wir dann auch recht bald. Was ich jetzt noch nicht gesagt habe, ist, was das K da jeweils bedeutet, was in dem Namen dieser Pasa-Klassen drinsteht. Das K steht einfach dafür, wie viele Tokens der Pasa gegebenenfalls über den aktuellen Token hinaus nach vorne schaut, um festzustellen, zum Beispiel welche Regel aus verschiedenen möglichen Regeln denn jetzt angewandt werden soll. Das heißt also ein LL1-Paser z.B. würde ein Token weiter schauen, als der, bei dem er aktuell schon ist. Oder ein LL2-Paser würde eben zwei Tokens weiter nach vorn schauen, als der aktuelle Token. So, jetzt schauen wir uns diese beiden Klassen von Parsern mal ein bisschen genauer an und fangen mit den LLK-Pasern, also Top-Down-Pasern an. Wie gesagt, nochmal zur Wiederholung. Das LLK steht für Left to Right Scanning, Leftmost Derivation und K ist die Anzahl der Tokens, die der Pasa vorausschauen kann. Es gibt da zwei Ansätze oder zwei Klassen von Ansätzen, die wir hier ein bisschen genauer anschauen werden, nämlich zum einen Recursive Descent-Pasa und zum anderen Table Driven-Pasa. Das ist so ein bisschen ähnlich wie bei den Scanners, die wir schon gesehen haben. Also die Recursive Descent-Pasa sind die, die man im Zweifelsfall eher mal per Hand schreiben würde. Also wenn man für eine einfache Sprache mal eben ein Pasa schreiben muss, dann würde man wahrscheinlich genau diesen Ansatz wählen. Wo hingegen Table Driven bedeutet, dass der Pasa automatisch generiert wird und ein generisches Pasing-Programm mithilfe einer Tabelle den eigentlichen Pasing-Algorithmus umsetzt. Egal, in welchen dieser beiden Arten von LLK-Pasern wir jetzt implementieren, der allgemeine Algorithmus sieht immer ungefähr so aus, wie es jetzt hier gerade steht. Also wir haben in diesem Algorithmus immer ein Nicht-Terminal, was unser aktuelles Nicht-Terminal ist und am Anfang ist das das Staatssymbol der Grammatik. Und dann die eigentliche Arbeit geschieht in einer Schleife, die so lange fortläuft bis kein Input mehr da ist, also sprich bis alle Tokens eingelesen wurden. Und was jetzt diese Schleife macht, ist, die schaut sich immer die nächsten K-Tokens an, also den aktuellen Plus, die K, die wir vorausschauen dürfen. Und außerdem das aktuelle Nicht-Terminal, in dem wir sind. Mit diesen beiden Informationen muss der Pasa oder diese Algorithmus dann eine der verfügbaren Regeln anwenden. Wie genau das gemacht wird, schauen wir uns später noch an. Und wenn wir jetzt so eine Regel erhaben, die sagt, dieses Nicht-Terminal, das wir haben, kann abgelattet werden nach einer Sequenz von Nicht-Terminalen und Terminalen, dann gehen wir durch diese Sequenz durch und für jedes X, für jedes Element X in dieser Sequenz schauen wir, ob das ein Terminal oder ein Nicht-Terminal ist. Wenn es ein Nicht-Terminal ist, dann ist das einfach ein weiteres Nicht-Terminal, was wir später dann noch expandieren müssen. Und wir setzen das jetzt erstmal so in den Pass-Tree ein. Wenn es ein Terminal ist, dann müssen wir schauen, ob das tatsächlich mit dem nächsten Token übereinstimmt, also ob dieses Terminal der nächste Token ist, den wir in unserer Input-Sequenz tatsächlich sehen. Wenn das so ist, dann haben wir quasi einen weiteren Token konsumiert und können dann den nächsten Token als unseren aktuellen Token betrachten. Und das Ganze passiert dann für jedes Element X in dieser rechten Seite der Regel R, die wir ausgewählt haben. Und anschließend geht die Schleife hier weiter, bis tatsächlich alle Tokens eingelesen wurden oder bis man feststellt, dass die Token-Sequenz nicht in ein Pass-Tree übersetzbar ist, also sprich, dass das kein legales Programm entsprechend unsere Grammatik ist. Mit diesem allgemeinen Algorithmus im Hinterkopf schauen wir jetzt die erste Variante von diesen LLK-Parsen an und nämlich den Recursive-Descent-Paser. Also Recursive-Descent, also recursiv-absteigender-Paser, der hat zwei Arten von Funktionen und Funktionen. Zum einen ist da eine Funktion für jedes Nicht-Terminal N in unsere Grammatik. Also wenn wir jetzt drei Nicht-Terminale in der Grammatik insgesamt haben, dann hätten wir schon mal drei Funktionen, die halt jeweils diese drei Nicht-Terminale abbilden. Außerdem haben wir eine Matchfunktion, was die macht, erklären wir gleich. Jetzt schauen wir erstmal genauer in diese Funktion für die Nicht-Terminale rein. Im Prinzip, was diese Funktion machen ist, imitieren das Benutzen der Ableitungsregeln, wo das Nicht-Terminal N auf der linken Seite ist. Wenn es mehrere mögliche Regeln gibt für das eine Nicht-Terminal N, dann muss diese Funktion also auswählen, welche dieser Möglichkeiten benutzt wird. Und zwar wird es gemacht basierend auf den nächsten K-Tokens, die wir ja im Pasa kennen. Wenn wir auf der rechten Seite der angewandten Regel, wenn wir uns dann für eine Regel entschieden haben, ein weiteres Nicht-Terminal sehen, heißt, dass wir das Schluss endlich auch noch expandieren und das funktioniert in diesen recursive Descent-Pasern so, dass wir die entsprechende Funktion für dieses neue Nicht-Terminal dann aufrufen. Das ist dann auch der Grund, warum das ganze recursive heißt, weil rekursiv eben diese Funktion wieder aufgerufen werden, um dann Schritt für Schritt alle Nicht-Terminale zu expandieren. Wenn wir auf der rechten Seite der Regel, die wir anwenden, ein Terminal sehen, heißt, dass wir erwarten, dass jetzt eigentlich auch dann das entsprechende Token in unserer Input-Sequenz erscheint. Und genau das ist, was wir mithilfe dieser Match-Funktion dann überprüfen. Also da geben wir ein Token, das wir erwarten. Match schaut dann, ist das das nächste Token in der Input-Sequenz. Wenn ja, dann wird dieses Token rausgenommen. Und wenn nicht, heißt das, dass wir an einer Stelle angekommen sind, wo es nicht weitergeht. Und dann wird ein entsprechender Fehler ausgegeben. Das heißt, der String, beziehungsweise die Token-Sequenz, ist dann eben doch nicht Teil der Programmiersprache. Schauen wir das Ganze mal mit einem Beispiel an, und zwar mit einer kleinen Beispielgrammatik, die wir hier auf der linken Seite sehen. In der Grammatik ist das Start-Symbol, das S, grundsätzlich in alle groß geschriebenen Buchstaben, Nicht-Terminale und alle klein geschriebenen Buchstaben, Terminale, also unsere Tokens. Das Start-Symbol S kann in zweierlei Art und Beisen losgehen entweder. Wir haben ein kleines A und haben dann noch ein Nicht-Terminal B. Oder es geht mit einem kleinen B los, und dann kommt das Nicht-Terminal C. Und die B und C Nicht-Terminale sind dann hier unten durch die weiteren Regeln erklärt. Und was wir jetzt auf der rechten Seite sehen, ist der entsprechende Code eines recursive descent passes für diese Grammatik. Und wie ich gerade gesagt habe, gibt es für jedes Nicht-Terminal in der Grammatik, also sprich für S, B und C hier eine entsprechende Funktion. Und was diese Funktion jeweils macht, ist, die schaut sich an, was der nächste Token oder die nächsten K-Tokens sind, die folgen. In dem Fall ist das ein SK gleich eins, wir schauen immer nur einen Token voraus und schauen dann hier, fangen wir mal mit B an. Wenn die B-Regel angewendet werden soll, wissen wir, dass das nächste Token ein kleines B sein muss. Das heißt, diese B-Funktion schaut sich an, ob der Input Token gleich B ist. Und wenn das so ist, dann wissen wir, dass wir jetzt zwei Bs einlesen wollen. Und das ist genau, was hier passiert, indem wir zweimal Match aufrufen. Also schauen oder sagen, der nächste Token müsste ein B sein und dann nochmal ein B. Und anschließend werden wir ein nicht Determinale C haben und das wird implementiert, indem wir die Funktion, die dieses Nicht Determinal behandelt, hier rekursiv aufrufen. Etwas komplexeres Beispiel ist für S. Denn hier haben wir zwei Möglichkeiten. S hat eben diese zwei Regeln, entweder A, B oder B, C. Und der Paar entscheidet sich hier für die Regel, indem wir eben anschauen, was das nächste Token ist. Wenn das ein A entspricht, dann gehen wir hier rein und rufen Match von A auf. Und andersrum, wenn das nächste ein B sein sollte, rufen wir Match von B auf. Und dann eben jeweils gefolgt von den rekursiven Aufrufen von B und C, um eben die jeweiligen Nicht Determinale hier auch noch abzudecken. Für den Fall, dass wir irgendwo nicht weiterkommen, also wenn der nächste Input Token eben nichts von dem ist, was wir erwarten, dann wird jeweils diese Arrow Funktion aufgerufen, die eben angibt, dass die Token Sequenz nicht der Grammatik entspricht. So, dann schauen wir uns diesen Algorithmus mal einem kleinen Beispiel an. Und zwar einem konkreten String, der in diese Grammatik rein gehört. Und zwar wollen wir hier den String pasen A, B, B, C, C. Also, kurzer Blick auf die Grammatik zeigt, dass das ein legaler String sein sollte. Jetzt schauen wir mal wieder Pasa, der Recursive Descent Pasa, das in dem Fall machen würde. Ich zeige einfach mal die Schritte, die der Pasa hier durchgeht und werde ihr auch ein bisschen durchnummern. Für jeden Schritt haben wir eine Reihe von Tokens, die eben noch nicht eingelesen wurden. Und das ist der noch übrige Input. Und dann haben wir gewisse Aktionen, die jeweils ausgeführt werden in diesem Schritt. Fangen wir mal mit Schritt 1 an. Also am Anfang ist natürlich der ganze Input da, also A, B, B, C, C. Wir rufen zunächst aus irgendeiner Form von Main-Methode einmal die S-Funktion auf, denn S ist ja das Startsymbol unserer Grammatik. So, was machen wir in S? Wir schauen uns den Input-Token, also sprich den nächsten Token an. Wir sehen, ah, der ist ein A. Also gehen wir in den ersten Branch da rein. Das heißt, wir rufen Match von A auf. Und was Match von A tun wird, ist, das erste A, oder das einzige A auch in unserem Input zu konsumieren. Und anschließend rufen wir das B auf, was jetzt rekursiv dann die B-Methode aufruft. Das bringt uns zu Schritt 2, weil wir jetzt in der zweiten Funktion sind, dass A ist weg. Das heißt, der übrig gebliebene Input ist B, B, C, C. Wir sind jetzt in der B-Methode, wo wir schauen, ob der nächste Input-Token ein B ist. Das ist der Fall. Und in dem Fall rufen wir zweimal Match auf mit B und anschließend C. Einmal dann nochmal, weil in der Regel für B steht zwei kleine B's und dann das nicht Determinal C. Und dieser Aufruf der C-Funktion bringt uns dann zu unserem dritten Schritt. Die beiden B's wurden mittlerweile konsumiert von den beiden Aufrufen von Match von B. Das heißt, wir haben nur noch C, C übrig und sind jetzt in der C-Funktion. Sehen da, der Input-Token ist tatsächlich ein C und deswegen rufen wir zweimal Match von C auf Nummer 1 und Nummer 2. Und in diesem Fahrt, wo wir zweimal Match von C aufrufen, ist kein weiterer Aufruf eine andere Funktion. Also wir haben keine weitere Rekursionen, sondern sind an der Stelle dann erstmal fertig. Vielleicht auch noch zu zeigen, wie der eigentliche Baum aussieht, weil das in dem Pseudocode für den Paser, den ich jetzt hier auf den Folien hab, eben nicht drin ist, aber der wird natürlich während des Parsens trotzdem aufgebaut, auch wenn es da nicht explizit steht. Das würde so aussehen, dass wir dann das S oben haben für jedes Match wird ein Knoten eingefügt mit einem Terminal. Und wenn wir in eine andere Funktion reingehen, rufen wir dann, oder fügen wir dann entsprechend ein Nicht-Terminal Knoten in den Baum ein. Das B ruft zweimal Match auf mit den beiden B's, die erscheinen hier und anschließend die Funktion für das Nicht-Terminal C und in C meschen wir dann die beiden verbleibenden C's und haben somit unseren Pass-Tree erstellt. Ja, und damit sind wir schon am Ende dieser 4. Einheit im Block-Syntax. Es kommen noch zwei weitere Einheiten. Was sie jetzt gelernt haben, ist, was eigentlich so ein Paser ist, dass es verschiedene Arten oder Klassen von Parsern gibt, nämlich Top-Down und Bottom-Up, und sie haben immer schon die erste Form eines Top-Down-Parsers gesehen, nämlich den Einfachsten, den man so kann, den Recursive-Descent-Paser. Und die anderen Klassen, beziehungsweise eine andere Art von Top-Down-Paser und auch eine Art von Bottom-Up-Paser, schauen wir uns dann in den verbleibenden Einheiten an. Bis dann, erst mal vielen Dank fürs Zuhören und bis zum nächsten Mal.