Ich schreibe einen kleinen Interpreter für eine einfache BASIC-ähnliche Sprache als Übung auf einem AVR-Mikrocontroller in C unter Verwendung der avr-gcc-Toolchain. Ich frage mich jedoch, ob es Open-Source-Tools gibt, die mir beim Schreiben von Lexer und Parser helfen könnten.
Wenn ich dies schreiben würde, um es auf meiner Linux-Box auszuführen, könnte ich flex / bison verwenden. Jetzt, wo ich mich auf eine 8-Bit-Plattform beschränkt habe, muss ich alles von Hand machen oder nicht?
Antworten:
Ich habe einen Parser für eine einfache Befehlssprache implementiert, die auf ATmega328p abzielt . Dieser Chip hat 32k ROM und nur 2k RAM. Der Arbeitsspeicher ist definitiv die wichtigere Einschränkung. Wenn Sie noch nicht an einen bestimmten Chip gebunden sind, wählen Sie einen mit möglichst viel Arbeitsspeicher aus. Dies wird Ihr Leben viel einfacher machen.
Zuerst habe ich überlegt, Flex / Bison zu verwenden. Ich habe mich aus zwei Hauptgründen gegen diese Option entschieden:
Nachdem ich Flex & Bison abgelehnt hatte, suchte ich nach anderen Generatorwerkzeugen. Hier sind einige, die ich in Betracht gezogen habe:
Vielleicht möchten Sie auch einen Blick auf den Vergleich von Wikipedia werfen .
Letztendlich habe ich sowohl den Lexer als auch den Parser von Hand codiert.
Zum Parsen habe ich einen rekursiven Abstiegsparser verwendet. Ich denke, Ira Baxter hat dieses Thema bereits angemessen behandelt, und es gibt viele Online-Tutorials.
Für meinen Lexer habe ich reguläre Ausdrücke für alle meine Terminals geschrieben, die entsprechende Zustandsmaschine grafisch dargestellt und als eine riesige Funktion implementiert, die
goto
's zum Wechseln zwischen Zuständen verwendet. Das war langweilig, aber die Ergebnisse haben großartig funktioniert. Abgesehen davongoto
ist es ein großartiges Werkzeug für die Implementierung von Zustandsautomaten - alle Ihre Zustände können eindeutige Beschriftungen direkt neben dem relevanten Code haben, es gibt keinen Funktionsaufruf oder Overhead für Zustandsvariablen und es ist ungefähr so schnell wie möglich. C hat wirklich kein besseres Konstrukt zum Erstellen statischer Zustandsmaschinen.Denken Sie darüber nach: Lexer sind eigentlich nur eine Spezialisierung von Parsern. Der größte Unterschied besteht darin, dass reguläre Grammatiken normalerweise für die lexikalische Analyse ausreichen, während die meisten Programmiersprachen (meistens) kontextfreie Grammatiken haben. Es hindert Sie also wirklich nichts daran, einen Lexer als Parser für rekursiven Abstieg zu implementieren oder einen Parser-Generator zum Schreiben eines Lexers zu verwenden. Es ist normalerweise nicht so bequem wie die Verwendung eines spezielleren Tools.
quelle
Wenn Sie Parser auf einfache Weise codieren möchten oder nur wenig Platz zur Verfügung stehen, sollten Sie einen Parser für rekursiven Abstieg manuell codieren. Dies sind im Wesentlichen LL (1) -Parser. Dies ist besonders effektiv für Sprachen, die so "einfach" wie Basic sind. (Ich habe einige davon in den 70ern gemacht!). Die gute Nachricht ist, dass diese keinen Bibliothekscode enthalten. genau das, was du schreibst.
Sie sind ziemlich einfach zu codieren, wenn Sie bereits eine Grammatik haben. Zuerst müssen Sie die linken rekursiven Regeln (z. B. X = XY) entfernen. Dies ist im Allgemeinen ziemlich einfach, daher lasse ich es als Übung. (Sie müssen dies nicht für Listenbildungsregeln tun; siehe Diskussion unten).
Wenn Sie dann die BNF-Regel des Formulars haben:
Erstellen Sie für jedes Element in der Regel (X, A, B, C) eine Unterroutine, die einen Booleschen Wert mit der Aufschrift "Ich habe das entsprechende Syntaxkonstrukt gesehen" zurückgibt. Für X Code:
Ähnliches gilt für A, B, C.
Wenn ein Token ein Terminal ist, schreiben Sie Code, der den Eingabestream auf die Zeichenfolge überprüft, aus der das Terminal besteht. Überprüfen Sie beispielsweise für eine Zahl, ob der Eingabestream Ziffern enthält, und bewegen Sie den Eingabestream-Cursor über die Ziffern hinaus. Dies ist besonders einfach, wenn Sie aus einem Puffer heraus analysieren (bei BASIC erhalten Sie in der Regel jeweils eine Zeile), indem Sie einfach einen Puffer-Scan-Zeiger vor- oder zurückbewegen. Dieser Code ist im Wesentlichen der Lexer-Teil des Parsers.
Wenn Ihre BNF-Regel rekursiv ist ... machen Sie sich keine Sorgen. Codieren Sie einfach den rekursiven Aufruf. Dies behandelt Grammatikregeln wie:
Dies kann wie folgt codiert werden:
Wenn Sie eine BNF-Regel mit einer Alternative haben:
dann Code P mit alternativen Auswahlmöglichkeiten:
Manchmal stoßen Sie auf Regeln zur Listenbildung. Diese neigen dazu, rekursiv zu bleiben, und dieser Fall ist leicht zu handhaben. Die Grundidee besteht darin, Iteration anstelle von Rekursion zu verwenden, und dies vermeidet die unendliche Rekursion, die Sie auf "offensichtliche" Weise erhalten würden. Beispiel:
Sie können dies mithilfe der Iteration wie folgt codieren:
Auf diese Weise können Sie mehrere hundert Grammatikregeln an ein oder zwei Tagen codieren. Es gibt mehr Details zu ergänzen, aber die Grundlagen hier sollten mehr als genug sein.
Wenn Sie sehr wenig Platz haben, können Sie eine virtuelle Maschine erstellen, die diese Ideen umsetzt. Das habe ich in den 70ern gemacht, als man 8K 16-Bit-Wörter bekommen konnte.
Wenn Sie dies nicht manuell codieren möchten, können Sie es mit einem Metacompiler ( Meta II) automatisieren ) , der im Wesentlichen dasselbe erzeugt. Dies ist ein umwerfender technischer Spaß, der selbst bei großen Grammatiken die ganze Arbeit kostet.
August 2014:
Ich bekomme viele Anfragen, wie man einen AST mit einem Parser erstellt. Einzelheiten dazu, die diese Antwort im Wesentlichen ausarbeiten, finden Sie in meiner anderen SO-Antwort unter https://stackoverflow.com/a/25106688/120163
Juli 2015:
Es gibt viele Leute, die einen einfachen Ausdrucksauswerter schreiben wollen. Sie können dies tun, indem Sie die gleichen Aktionen ausführen, die der obige Link "AST Builder" vorschlägt. Rechnen Sie einfach, anstatt Baumknoten zu erstellen. Hier ist ein Ausdrucksauswerter, der auf diese Weise erstellt wurde .
quelle
Sie können flex / bison unter Linux mit seiner nativen gcc verwenden, um den Code zu generieren, den Sie dann mit Ihrer AVR gcc für das eingebettete Ziel kreuzkompilieren.
quelle
GCC kann auf eine Vielzahl von Plattformen kompiliert werden, aber Sie führen Flex und Bison auf der Plattform aus, auf der Sie den Compiler ausführen. Sie spucken nur C-Code aus, den der Compiler dann erstellt. Testen Sie es, um zu sehen, wie groß die resultierende ausführbare Datei wirklich ist. Beachten Sie, dass sie Laufzeitbibliotheken (
libfl.a
usw.) haben, die Sie auch zu Ihrem Ziel kompilieren müssen.quelle
Versuchen Sie Boost :: Spirit. Es ist eine reine Header-Bibliothek, in die Sie einen sehr schnellen, sauberen Parser vollständig in C ++ einfügen können. Überladene Operatoren in C ++ werden anstelle einer speziellen Grammatikdatei verwendet.
quelle
Schauen Sie sich LUA an, anstatt das Rad neu zu erfinden : www.lua.org . Es ist eine Interpretationssprache, die in andere Software eingebettet und auf kleinen Systemen wie eingebetteten Systemen verwendet werden soll. Integrierter Parsing-Baum für prozedurale Syntax, Steuerlogik, Mathematik und Variablenunterstützung - Sie müssen nichts neu erfinden, was Tausende von anderen bereits getestet und verwendet haben. Und es ist erweiterbar, dh Sie können die Grammatik erweitern, indem Sie Ihre eigenen C-Funktionen hinzufügen.
quelle