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:
X = A B C ;
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:
subroutine X()
if ~(A()) return false;
if ~(B()) { error(); return false; }
if ~(C()) { error(); return false; }
// insert semantic action here: generate code, do the work, ....
return true;
end X;
Ä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:
T = '(' T ')' ;
Dies kann wie folgt codiert werden:
subroutine T()
if ~(left_paren()) return false;
if ~(T()) { error(); return false; }
if ~(right_paren()) { error(); return false; }
// insert semantic action here: generate code, do the work, ....
return true;
end T;
Wenn Sie eine BNF-Regel mit einer Alternative haben:
P = Q | R ;
dann Code P mit alternativen Auswahlmöglichkeiten:
subroutine P()
if ~(Q())
{if ~(R()) return false;
return true;
}
return true;
end P;
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:
L = A | L A ;
Sie können dies mithilfe der Iteration wie folgt codieren:
subroutine L()
if ~(A()) then return false;
while (A()) do { /* loop */ }
return true;
end L;
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 .