25
Sep

Let’s build a compiler #14 Scopes und längere Funktionen – Compilerbau ANTLR Tutorial deutsch HD


Willkommen zurück zu “Let’s build a compiler”. In der letzten Folge haben wir angefangen einfache Funktionen in unsere Sprache aufzunehmen. Diese konnten jedoch nur aus einem return-Statement und nichts weiter bestehen. In dieser Folge werden wir unsere Funktionen so erweitern, dass sie auch weiteren Code enthalten können. In dem Rahmen werden wir uns auch erstmal mit Scopes beschäftigen. Denn wir wollen die Variablentabelle mehrerer Funktionen trennen. Hier haben wir unser Beispiel aus letzter Folge: Das einfache “randomNumber” mit dem “return 4”. Das kopiere ich jetzt einmal hier raus, füg’ das in einem Texteditor ein und dann können wir hier leichter drin rumformatieren. Jetzt in diesem etwas besser lesbaren Beispiel werden wir den Quelltext ein wenig komplexer gestalten. Zum Beispiel könnten wir sagen: Wir haben hier ein Integer “i” dem weisen wir dann die Vier zu und dann wollen wir diese Variable hier zurückgeben. Es sollte also immernoch das gleiche Ergebnis rauskommen, aber der Quelltext ist jetzt ein bißchen komplizierter geworden. Und damit haben ein Beispiel für eine Funtkion die noch mehr als nur ein Returnstatement hat. Kopieren wir das ganze also wieder und fügen dies hier ein. Dafür muss ich natürlich zuerstmal diese Zeile hier kopieren, um dann das hier nochmal zu kopieren und hier einzufügen. So, dann fehlt hier hinten noch ein Komma und dann können wir den Test mal laufen lassen. Schauen wir uns das Ergebnis an: Und hier oben hat ANTLR gemeckert: Es sagt “mismatched input ‘int'”, es hätte nämlich ein “return” erwartet. Wenn wir jetzt auf unsere Grammatik schauen dann sehen wir jetzt hier, dass hinter der sich öffnenden geschweiften Klammer direkt ein “return” kommen sollte, nämlich das und nicht unser “int” mit der Variablendefinition. Also wollen wir jetzt hier, zwischen der sich öffnended Klammer und dem “return” noch beliebige Statements erlauben. Dafür füge ich jetzt hier also ein dass Statements vorkommen dürfen die eine “statementList” sind. Was die “statementList” jetzt genau ist müssen wir natürlich auch noch definieren. Dafür fügen wir hier eine neue Regeln ein. Also bedeutet unsere Statementlist dass wir ein Statement haben jeweils gefolgt von einem Semikolon und das darf dann beliebig häufig vorkommen, auch null mal vorkommen, denn es kann ja tatsächlich sein dass unsere Funktion einfach nur ein “return” enthält und kein sonstiges Statement. Dann generieren wir unseren Parser wieder neu und gehen hier auf “F5”, führen unseren Test aus und wie ihr seht, es gibt jetzt hier oben keinen Fehler von ANTLR oder Jasmin mehr aber hier unten haben wir noch eine UndeclaredVariableException, nämlich die Variable “i” auf die wir hier zugreifen ist angeblich nicht definiert. Das liegt natürlich daran, dass wir jetzt zwar definiert haben in unser Grammatik dass dieser Teil hier vorkommen darf, in Form der statementList, aber wir generieren nie Instruktionen für diesen Code hier und deswegen ist natürlich auch die Variable “i” nicht definiert. Also gehen wir wieder in unseren Visitor zu unser “visitFunctionDefinition” und … dann … erzeugen wir jetzt erstmal den Code für unsere Statements. Und diese Liste fügen wir dann an dieser Stelle hier ein. Jetzt kann es allerdings auch sein dass diese “statementInstructions” hier “null” sind, denn wenn wir keine Instructions, also wenn wir keine Statements haben, dann kann dieses “visit” hier auch nicht generieren, es kommt einfach nur “null” zurück, weil das der Defaultrückgabewert ist und dann wollen wir hier an der Stelle auch nichts ausgeben. Also sagen wir hier dass bitte wenn “statementInstructions” gleich gleich null ist, dann soll hier bitte nichts hinzugefügt werden und ansonsten eben die “statementInstructions” mit dem Zeilenumbruch am Ende. Dann führen wir unsere Tests nochmal aus. Und wie wir sehen: Es hat alles funktioniert, alle Tests sind durchgelaufen, auch unser Neuer hier. Werfen wir hier nochmal einen Blick auf unseren Quelltext. Was wäre eigentlich wenn wir hier die Variable “int i” nocheinmal haben? Und dann beispielsweie hier 42 zuweisen und dies auch noch am Ende ausgeben. Jetzt haben wir die Variable “i” nämlich zweimal nämlich einmal hier und einmal hier. Wenn wir jetzt “randomNumber” aufrufen, dann müsste hier etwas zurückkommen oder auch nicht und hier müsste auch etwas ausgegeben werden. Was würde ihr für intuitiv halten was mit diesem Quelltext passiert? Ich würde sagen: Es wäre logisch wenn das hier, diese “i” so eine Art globale Variable wäre und das hier oben, im randomNumber eine lokale Variable und daher würde sich diese globale Variable hinter der lokalen verstecken. Wenn “randomNumber” aufgerufen wird, wird hier mit der lokalen Variable gearbeitet, es wird 4 zurückgegeben, hier wird vier ausgegeben und hier diese Funktion also an dieser Stelle ist natürlich von der Variable hier oben nichts bekannt und daher würde dann hier die 42 ausgegeben. Also müsste erst vier und dann 42 rauskommen. Schauen wir uns mal an was denn tatsächlich der Fall ist, was unser Compiler aus dem Quelltext erstellt. Dann kopieren wir uns den in einen neuen Testfall, ich kopier erstmal wieder unser Template hier oben und dann müsste ja meiner Meinung nach hier zuerst die vier ausgegeben werden …und… danach nocheinmal die 42. Dann führen wir den Compiler einmal aus und gucken was tatsächlich passiert ist. Tätsächlich haben wir das garnichts ausgegeben, sondern wir haben eine VariableAlreadyDefinedExcpeition bekommen, weil in Zeile 6, das ist also… 1, 2, 3, 4, 5, 6, das ist also diese Zeile hier, ist die Variable “i” angeblich schon definiert. Was mit anderen Worten heißt: Hier unten, dieser Teil weiss welche Variablen hier oben definiert sind und das sollte eigentlich nicht der Fall sein. Es ist auch tatsächlich ein Problem, denn auf die lokale Variablentabelle von dieser Funktion könnten wir, selbst wenn wir wollen nicht von einer anderen Funktion, also von unser “main”-Funktion her aus zugreifen. Dann müssten wir die Variablen auch woanders ablegen als in unser lokalen Variablentabelle. Das Problem was wir jetzt hier haben beschäftigt sich mit Scopes. Ein so ein Scope besagt: Diese Variablen sind in einem definierten Bereich gültig und ein so ein Definitionsbereich ist ein Scope. Und wir haben dann jetzt hier einen globalen Scope, nämlich den hier unten oder von mir aus ist es auch ein lokaler Scope, nämlich der der “main”-Funktion aber hier haben wir auf jedenfall einen anderen Scope, nämlich einen lokalen Scope der Funktion “randomNumber”. Da wollen wir jetzt mal schauen was wir da machen können. Also schauen wir nochmal in unseren Visitor und schauen nochmal nach wie unser System mit der Variablendefinition eigentlich funktioniert. Wir haben hier oben diese Map mit der Variablentabelle, das ist einfach nur eine HashMap von Strings to Integers und die ordnet entsprechend jedem Variablennamen eine Position in der Variablentabelle zu. Jetzt haben wir aber nicht nur eine Variablentabelle sondern ganz viele, nämlich eine pro Funktion und das ist zumindest mal eine der “main”-Funktion und eine unser lokalen Funktion “randomNumber” in dem Beispiel welches wir eben hatten. Wir könnten auch noch sagen, dass der Scope der “main”-Funktion global sein soll, dann hätten wir eben einen globalen Scope und einen lokalen Scope für unsere Funktion “randomNumber”. Und wenn wir noch mehr Funktionen hätten, dann eben noch mehr lokale Scopes. Wir müssen jetzt also in der Lage sein zwischen diesen Scopes hin- und herzuwecheln und zwar an den richtigen Stellen. Das heißt immer wenn wir uns gerade mit dem Inhalt einer Funktion beschäftigen brauchen wir die Variablentabelle die zu unser aktuellen Funktion passt. Das heißt zu Funktionen kommen wir immer wenn wir eine “functionDeclaration” bekommen. Jetzt haben wir hier diese “functionDefinition”s, jetzt können wir hier sagen, dass vor der Funktion… unser alter Scope, das ist die Variablentabelle, dass wir uns die merken und zwar ist das die “oldVariables” und dann legen wir uns in unser aktuellen Variablentabelle eine neue Variablentabelle an, die am Anfang erstmal leer ist. Jetzt können wir wunderbar mit dieser Tabelle arbeiten und am Ende müssen wir natürlich diese alte Tabelle auch wiederherstellen, diese “oldVariables”, das heißt am Ende der Methode können wir das was wir hier temporär verwendet haben, diese Variablestabelle einfach vergessen und dann hier entsprechend sagen: “variables” ist wieder “oldVariables”. Jetzt muss das natürlich passieren vor dem “return”, das heißt wir ziehen das hier in eine temporäre Variable, die unser result enthält mit dem generierten Code und die geben wir erst nachdem wir die Variablentabelle wiederhergestellt haben zurück. Dann probieren wir nochmal aus, was jetzt passiert. Und wie ihr seht: Alle Tests haben funktioniert, auch das Beispiel jetzt: Unsere Variablen sind jetzt getrennt, das heißt hier kommt tatsächlich erst die vier und dann die 42 raus. Was natürlich noch nicht passiert ist ist dass wir jetzt einen globalen Scope haben, ich habe es jetzt erstmal so implementiert dass wir zwei lokale Scopes haben, also die “main”-Funktion hier unten, die hat einen lokalen Scope und die hier hat einen lokalen. Das heißt wir könnten jetzt auch nicht von hier auf die “main”-Funktion zugreifen. Jetzt kann man natürlich sagen dass das Absicht ist, aber auf lange Sicht werden wir auch noch globale Variablen einbauen, einfach weil wir es können. In dieser Folge haben wir gelernt wie wir vor dem “return”-Statement beliebige weitere Instruktionen zulassen. Außerdem haben wir gelernt wie wir die Variablentabbellen mehrere Funktionen mit einfachen Mitteln voneinander trennen können. Trotzdem gibt es bei den Funktionen noch viel zu tun: Das Returnstatement muss aktuell noch immer das letzte Statement in jeder Funktion sein. Es gibt keine globalen Variablen. Wir unterstützen noch keine Parameter und auch keine Funktionen ohne Rückgabewert. Wie wir all dies umsetzen seht ihn den nächsten Folgen. Schreibt mir eure Fragen, Probleme und Wünsche in die Kommentare. Lasst mir einen Daumen da wenn euch das Video gefallen hat und ein Abo damit ihr keine neue Folge verpasst. Vielen Dank an Gadarol der mir für die Aufnahmen sein Studio zur Verfügung stellt und euch vielen Dank für eure Aufmerksamkeit. Mein Name ist yankee, ich hoffe es hat euch Spaß gemacht, bis zum nächsten Mal wenn es wieder heißt: “Let’s build a compiler”.

Tags: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,

3 Comments

  • Maik Herbermann says:

    Wieder ein super Video,

    so langsam versteht man das ganze System, kennt die Zusammenhänge und weiß was man machen muss. Mal sehen, ob ich auch meinen eigenen Kompiler baue.

  • Dominik K. says:

    Mach weiter so, dass kommt immer noch sehr gut rüber 😉
    Kannst du schon absehen, wieviele Folgen dieses Projekt umfassen wird? Du kannst dir sicher vorstellen, dass ich das schlecht abschätzen kann 😀
    LG

  • Yafei Yan says:

    very helpful! hilfreich!

Leave a Reply

Your email address will not be published. Required fields are marked *