it-swarm-eu.dev

Soll ich einen Parser-Generator verwenden oder meinen eigenen Lexer- und Parser-Code rollen?

Was spezifisch Vor- und Nachteile jeder Art der Arbeit an einer Programmiersprachengrammatik?

Warum/wann sollte ich meine eigenen rollen? Warum/wann sollte ich einen Generator verwenden?

83
Maniero

Es gibt wirklich drei Optionen, die alle drei in unterschiedlichen Situationen vorzuziehen sind.

Option 1: Parser-Generatoren oder "Sie müssen eine Sprache analysieren und möchten sie nur zum Laufen bringen, verdammt"

Angenommen, Sie werden gebeten, JETZT einen Parser für ein altes Datenformat zu erstellen. Oder Sie brauchen Ihren Parser, um schnell zu sein. Oder Sie benötigen einen Parser, der leicht zu warten ist.

In diesen Fällen ist es wahrscheinlich am besten, einen Parser-Generator zu verwenden. Sie müssen nicht mit den Details herumspielen, Sie müssen nicht viel komplizierten Code erhalten, um richtig zu funktionieren. Sie schreiben einfach die Grammatik auf, an die sich die Eingabe hält, schreiben einen Bearbeitungscode und Presto: Instant Parser.

Die Vorteile liegen auf der Hand:

  • Es ist (normalerweise) recht einfach, eine Spezifikation zu schreiben, insbesondere wenn das Eingabeformat nicht zu seltsam ist (Option 2 wäre besser, wenn dies der Fall ist).
  • Am Ende erhalten Sie eine sehr leicht zu wartende Arbeit, die leicht zu verstehen ist: Eine Grammatikdefinition fließt normalerweise viel natürlicher als Code.
  • Die von guten Parser-Generatoren generierten Parser sind normalerweise viel schneller als handgeschriebener Code. Handgeschriebener Code kann schneller sein, aber nur, wenn Sie sich auskennen - aus diesem Grund verwenden die meisten weit verbreiteten Compiler einen handgeschriebenen Parser für rekursiven Abstieg.

Bei Parser-Generatoren müssen Sie auf eines achten: Manchmal können Sie Ihre Grammatik ablehnen. Für einen Überblick über die verschiedenen Arten von Parsern und wie sie Sie beißen können, möchten Sie vielleicht hier beginnen. Hier Sie finden einen Überblick über viele Implementierungen und die Arten von Grammatiken, die sie akzeptieren.

Option 2: handgeschriebene Parser oder "Sie möchten Ihren eigenen Parser erstellen und möchten benutzerfreundlich sein".

Parser-Generatoren sind nett, aber sie sind nicht sehr benutzerfreundlich (der Endbenutzer, nicht Sie). Normalerweise können Sie keine guten Fehlermeldungen und keine Fehlerbehebung bereitstellen. Vielleicht ist Ihre Sprache sehr seltsam und Parser lehnen Ihre Grammatik ab oder Sie benötigen mehr Kontrolle als der Generator Ihnen gibt.

In diesen Fällen ist die Verwendung eines handgeschriebenen Parsers für rekursiven Abstieg wahrscheinlich der beste. Obwohl es kompliziert sein kann, es richtig zu machen, haben Sie die vollständige Kontrolle über Ihren Parser, sodass Sie alle Arten von netten Dingen ausführen können, die Sie mit Parser-Generatoren nicht tun können, wie Fehlermeldungen und sogar Fehlerbehebung (versuchen Sie, alle Semikolons aus einer C # -Datei zu entfernen : Der C # -Compiler wird sich beschweren, aber die meisten anderen Fehler trotzdem erkennen, unabhängig vom Vorhandensein von Semikolons.

Handgeschriebene Parser arbeiten normalerweise auch besser als generierte, vorausgesetzt, die Qualität des Parsers ist hoch genug. Wenn Sie es jedoch nicht schaffen, einen guten Parser zu schreiben - normalerweise aufgrund (einer Kombination aus) mangelnder Erfahrung, Wissen oder Design -, ist die Leistung normalerweise langsamer. Für Lexer gilt jedoch das Gegenteil: Im Allgemeinen verwenden generierte Lexer Tabellensuchen, wodurch sie schneller sind als (die meisten) handgeschriebenen.

In Bezug auf die Bildung lehrt Sie das Schreiben eines eigenen Parsers mehr als die Verwendung eines Generators. Schließlich müssen Sie immer komplizierteren Code schreiben und genau verstehen, wie Sie eine Sprache analysieren. Wenn Sie jedoch lernen möchten, wie Sie Ihre eigene Sprache erstellen (also Erfahrung im Sprachdesign sammeln), ist entweder Option 1 oder Option 3 vorzuziehen: Wenn Sie eine Sprache entwickeln, wird sich dies wahrscheinlich stark ändern. und Option 1 und 3 erleichtern Ihnen das Arbeiten.

Option 3: handgeschriebene Parser-Generatoren oder "Sie versuchen, viel aus diesem Projekt zu lernen, und es würde Ihnen nichts ausmachen, einen raffinierten Code zu erhalten, den Sie häufig wiederverwenden können."

Dies ist der Weg, den ich gerade gehe: Sie schreiben Ihren eigenen Parser-Generator. Dies ist zwar höchst trivial, aber wenn Sie dies tun, werden Sie wahrscheinlich am meisten lernen.

Um Ihnen eine Vorstellung davon zu geben, was ein solches Projekt bedeutet, erzähle ich Ihnen von meinen eigenen Fortschritten.

Der Lexer-Generator

Ich habe zuerst meinen eigenen Lexer-Generator erstellt. Normalerweise entwerfe ich Software, beginnend mit der Verwendung des Codes. Daher habe ich mir überlegt, wie ich meinen Code verwenden möchte, und diesen Code geschrieben (in C #):

Lexer<CalculatorToken> calculatorLexer = new Lexer<CalculatorToken>(
    new List<StringTokenPair>()
    { // This is just like a Lex specification:
      //                    regex   token
        new StringTokenPair("\\+",  CalculatorToken.Plus),
        new StringTokenPair("\\*",  CalculatorToken.Times),
        new StringTokenPair("(",    CalculatorToken.LeftParenthesis),
        new StringTokenPair(")",    CalculatorToken.RightParenthesis),
        new StringTokenPair("\\d+", CalculatorToken.Number),
    });

foreach (CalculatorToken token in
             calculatorLexer.GetLexer(new StringReader("15+4*10")))
{ // This will iterate over all tokens in the string.
    Console.WriteLine(token.Value);
}

// Prints:
// 15
// +
// 4
// *
// 10

Die eingegebenen String-Token-Paare werden in eine entsprechende rekursive Struktur konvertiert, die die regulären Ausdrücke beschreibt, die sie unter Verwendung der Ideen eines arithmetischen Stapels darstellen. Dies wird dann in einen NFA (nichtdeterministischer endlicher Automat) umgewandelt, der wiederum in einen DFA (deterministischer endlicher Automat) umgewandelt wird. Sie können dann Zeichenfolgen mit dem DFA abgleichen.

Auf diese Weise erhalten Sie eine gute Vorstellung davon, wie genau Lexer funktionieren. Wenn Sie es richtig machen, können die Ergebnisse Ihres Lexer-Generators ungefähr so ​​schnell sein wie bei professionellen Implementierungen. Sie verlieren auch keine Ausdruckskraft im Vergleich zu Option 2 und nicht viel Ausdruckskraft im Vergleich zu Option 1.

Ich habe meinen Lexer-Generator in etwas mehr als 1600 Codezeilen implementiert. Mit diesem Code funktioniert das oben Genannte, aber er generiert den Lexer jedes Mal, wenn Sie das Programm starten, im laufenden Betrieb: Ich werde irgendwann Code hinzufügen, um ihn auf die Festplatte zu schreiben.

Wenn Sie wissen möchten, wie Sie Ihren eigenen Lexer schreiben, ist this ein guter Anfang.

Der Parser-Generator

Sie schreiben dann Ihren Parser-Generator. Ich verweise noch einmal auf hier , um einen Überblick über die verschiedenen Arten von Parsern zu erhalten. Als Faustregel gilt: Je mehr sie analysieren können, desto langsamer sind sie.

Da Geschwindigkeit für mich kein Problem darstellt, habe ich mich für die Implementierung eines Earley-Parsers entschieden. Erweiterte Implementierungen eines Earley-Parsers wurden gezeigt sind ungefähr doppelt so langsam wie andere Parsertypen.

Als Gegenleistung für diesen Geschwindigkeitstreffer erhalten Sie die Möglichkeit, any Arten von Grammatik zu analysieren, auch mehrdeutige. Dies bedeutet, dass Sie sich keine Gedanken darüber machen müssen, ob Ihr Parser eine Linksrekursion enthält oder was ein Schichtreduzierungskonflikt ist. Sie können Grammatiken auch einfacher mit mehrdeutigen Grammatiken definieren, wenn es nicht darauf ankommt, welcher Analysebaum das Ergebnis ist, z. B. spielt es keine Rolle, ob Sie 1 + 2 + 3 als (1 + 2) +3 oder als 1 analysieren + (2 + 3).

So kann ein Code mit meinem Parser-Generator aussehen:

Lexer<CalculatorToken> calculatorLexer = new Lexer<CalculatorToken>(
    new List<StringTokenPair>()
    {
        new StringTokenPair("\\+",  CalculatorToken.Plus),
        new StringTokenPair("\\*",  CalculatorToken.Times),
        new StringTokenPair("(",    CalculatorToken.LeftParenthesis),
        new StringTokenPair(")",    CalculatorToken.RightParenthesis),
        new StringTokenPair("\\d+", CalculatorToken.Number),
    });

Grammar<IntWrapper, CalculatorToken> calculator
    = new Grammar<IntWrapper, CalculatorToken>(calculatorLexer);

// Declaring the nonterminals.
INonTerminal<IntWrapper> expr = calculator.AddNonTerminal<IntWrapper>();
INonTerminal<IntWrapper> term = calculator.AddNonTerminal<IntWrapper>();
INonTerminal<IntWrapper> factor = calculator.AddNonTerminal<IntWrapper>();

// expr will be our head nonterminal.
calculator.SetAsMainNonTerminal(expr);

// expr: term | expr Plus term;
calculator.AddProduction(expr, term.GetDefault());
calculator.AddProduction(expr,
                         expr.GetDefault(),
                         CalculatorToken.Plus.GetDefault(),
                         term.AddCode(
                         (x, r) => { x.Result.Value += r.Value; return x; }
                         ));

// term: factor | term Times factor;
calculator.AddProduction(term, factor.GetDefault());
calculator.AddProduction(term,
                         term.GetDefault(),
                         CalculatorToken.Times.GetDefault(),
                         factor.AddCode
                         (
                         (x, r) => { x.Result.Value *= r.Value; return x; }
                         ));

// factor: LeftParenthesis expr RightParenthesis
//         | Number;
calculator.AddProduction(factor,
                         CalculatorToken.LeftParenthesis.GetDefault(),
                         expr.GetDefault(),
                         CalculatorToken.RightParenthesis.GetDefault());
calculator.AddProduction(factor,
                         CalculatorToken.Number.AddCode
                         (
                         (x, s) => { x.Result = new IntWrapper(int.Parse(s));
                                     return x; }
                         ));

IntWrapper result = calculator.Parse("15+4*10");
// result == 55

(Beachten Sie, dass IntWrapper einfach ein Int32 ist, außer dass C # erfordert, dass es eine Klasse ist, daher musste ich eine Wrapper-Klasse einführen.)

Ich hoffe, Sie sehen, dass der obige Code sehr mächtig ist: Jede Grammatik, die Sie sich einfallen lassen können, kann analysiert werden. Sie können der Grammatik beliebige Codebits hinzufügen, die viele Aufgaben ausführen können. Wenn Sie dies alles zum Laufen bringen, können Sie den resultierenden Code sehr einfach wiederverwenden, um viele Aufgaben zu erledigen: Stellen Sie sich vor, Sie erstellen einen Befehlszeileninterpreter mit diesem Code.

78
Alex ten Brink

Wenn Sie noch nie einen Parser geschrieben haben, würde ich Ihnen empfehlen, dies zu tun. Es macht Spaß, und Sie lernen, wie die Dinge funktionieren, und Sie lernen, den Aufwand zu schätzen, den Parser- und Lexer-Generatoren Ihnen ersparen, wenn Sie next Zeit für einen Parser benötigen.

Ich würde auch vorschlagen, dass Sie versuchen, http://compilers.iecc.com/crenshaw/ zu lesen, da es eine sehr bodenständige Einstellung dazu hat.

22
user1249

Der Vorteil des Schreibens eines eigenen Parsers für rekursiven Abstieg besteht darin, dass Sie hochwertige Fehlermeldungen für Syntaxfehler generieren können. Mit Parser-Generatoren können Sie an bestimmten Stellen Fehlerproduktionen erstellen und benutzerdefinierte Fehlermeldungen hinzufügen. Parser-Generatoren entsprechen jedoch nicht der Fähigkeit, die vollständige Kontrolle über das Parsen zu haben.

Ein weiterer Vorteil des Schreibens Ihrer eigenen ist, dass es einfacher ist, eine einfachere Darstellung zu analysieren, die keine Eins-zu-Eins-Entsprechung zu Ihrer Grammatik aufweist.

Wenn Ihre Grammatik festgelegt ist und Fehlermeldungen wichtig sind, sollten Sie Ihre eigene rollen oder zumindest einen Parser-Generator verwenden, der Ihnen die benötigten Fehlermeldungen liefert. Wenn sich Ihre Grammatik ständig ändert, sollten Sie stattdessen Parser-Generatoren verwenden.

Bjarne Stroustrup spricht darüber, wie er YACC für die erste Implementierung von C++ verwendet hat (siehe Das Design und die Entwicklung von C++ ). In diesem ersten Fall wünschte er sich, er würde stattdessen seinen eigenen Parser für rekursive Abstammung schreiben!

14
Macneil

Option 3: Weder (Roll deinen eigenen Parser-Generator)

Nur weil es einen Grund gibt, [~ # ~] antlr [~ # ~] , bison , Coco/R , Grammatica , JavaCC , Lemon , Parboiled , SableCC , Quex , etc - das bedeutet nicht, dass Sie sofort Ihren eigenen Parser + Lexer rollen sollten.

Identifizieren warum all diese Tools sind nicht gut genug - warum lassen sie Sie Ihr Ziel nicht erreichen?

Sofern Sie nicht sicher sind, dass die Kuriositäten in der Grammatik, mit der Sie sich befassen, einzigartig sind, sollten Sie nicht nur einen einzelnen benutzerdefinierten Parser + Lexer dafür erstellen. Erstellen Sie stattdessen ein Tool, das das erstellt, was Sie möchten, aber auch zur Erfüllung zukünftiger Anforderungen verwendet werden kann, und geben Sie es dann als Freie Software frei, um zu verhindern, dass andere Personen das gleiche Problem wie Sie haben.

10
Peter Boughton

Wenn Sie Ihren eigenen Parser rollen, müssen Sie direkt über die Komplexität Ihrer Sprache nachdenken. Wenn die Sprache schwer zu analysieren ist, wird sie wahrscheinlich schwer zu verstehen sein.

In den frühen Tagen gab es großes Interesse an Parser-Generatoren, motiviert durch eine hochkomplizierte (manche würden sagen "gequält") Sprachsyntax. JOVIAL war ein besonders schlechtes Beispiel: Es erforderte zwei Lookahead-Symbole, zu einer Zeit, als alles andere höchstens ein Symbol erforderte. Dies machte das Generieren des Parsers für einen JOVIAL-Compiler schwieriger als erwartet (wie General Dynamics/Fort Worth Division auf die harte Tour lernte, als sie JOVIAL-Compiler für das F-16-Programm beschafften).

Heutzutage ist der rekursive Abstieg allgemein die bevorzugte Methode, da er für Compiler-Autoren einfacher ist. Compiler für rekursiven Abstieg belohnen ein einfaches, sauberes Sprachdesign nachdrücklich, da es viel einfacher ist, einen Parser für rekursiven Abstieg für eine einfache, saubere Sprache zu schreiben als für eine verschlungene, unordentliche.

Schließlich: Haben Sie darüber nachgedacht, Ihre Sprache in LISP einzubetten und einen LISP-Dolmetscher das schwere Heben für Sie erledigen zu lassen? AutoCAD hat das getan und festgestellt, dass es ihnen das Leben viel leichter gemacht hat. Es gibt einige leichte LISP-Interpreter, von denen einige eingebettet werden können.

8
John R. Strohm

Ich habe einmal einen Parser für kommerzielle Anwendungen geschrieben und yacc verwendet. Es gab einen konkurrierenden Prototyp, bei dem ein Entwickler das Ganze von Hand in C++ schrieb und es ungefähr fünfmal langsamer arbeitete.

Der Lexer für diesen Parser habe ich komplett von Hand geschrieben. Es dauerte - sorry, es war vor fast 10 Jahren, also erinnere ich mich nicht genau - ungefähr 1000 Zeilen in C .

Der Grund, warum ich den Lexer von Hand geschrieben habe, war die Eingabegrammatik des Parsers. Es war eine Anforderung, die meine Parser-Implementierung erfüllen musste, im Gegensatz zu etwas, das ich entworfen hatte. (Natürlich hätte ich es anders gestaltet. Und besser!) Die Grammatik war stark kontextabhängig und sogar das Lexieren hing an einigen Stellen von der Semantik ab. Zum Beispiel könnte ein Semikolon Teil eines Tokens an einer Stelle sein, aber ein Trennzeichen an einer anderen Stelle - basierend auf einer semantischen Interpretation eines Elements, das zuvor analysiert wurde. Also "vergrub" ich solche semantischen Abhängigkeiten im handgeschriebenen Lexer und das ließ mich mit einem ziemlich einfachen [~ # ~] bnf [~ # ~] das war einfach in yacc zu implementieren.

HINZUGEFÜGT als Antwort auf Macneil: yacc bietet eine sehr leistungsfähige Abstraktion, mit der der Programmierer in Terminals, Nicht-Terminals, denken kann. Produktionen und ähnliches. Außerdem hat es mir bei der Implementierung der Funktion yylex() geholfen, mich auf die Rückgabe des aktuellen Tokens zu konzentrieren und mir keine Gedanken darüber zu machen, was davor oder danach war. Der C++ - Programmierer arbeitete auf Zeichenebene ohne den Vorteil einer solchen Abstraktion und entwickelte schließlich einen komplizierteren und weniger effizienten Algorithmus. Wir kamen zu dem Schluss, dass die langsamere Geschwindigkeit nichts mit C++ selbst oder irgendwelchen Bibliotheken zu tun hat. Wir haben die reine Parsing-Geschwindigkeit mit Dateien gemessen, die in den Speicher geladen wurden. Wenn wir ein Problem mit der Dateipufferung hätten, wäre yacc nicht das Werkzeug unserer Wahl, um es zu lösen.

AUCH WOLLEN HINZUFÜGEN: Dies ist kein Rezept zum Schreiben von Parsern im Allgemeinen, sondern nur ein Beispiel dafür, wie es in einer bestimmten Situation funktioniert hat.

6
azheglov

Es hängt davon ab, was Ihr Ziel ist.

Versuchen Sie zu lernen, wie Parser/Compiler funktionieren? Dann schreiben Sie Ihre eigenen von Grund auf neu. Nur so können Sie wirklich lernen, all die Vor- und Nachteile ihrer Arbeit zu schätzen. Ich habe in den letzten paar Monaten eine geschrieben, und es war eine interessante und wertvolle Erfahrung, insbesondere die Momente "Ah, deshalb macht Sprache X das ...".

Müssen Sie schnell etwas für eine Bewerbung innerhalb einer Frist zusammenstellen? Verwenden Sie dann möglicherweise ein Parser-Tool.

Benötigen Sie etwas, das Sie in den nächsten 10, 20, vielleicht sogar 30 Jahren erweitern möchten? Schreiben Sie Ihre eigenen und nehmen Sie sich Zeit. Es wird sich lohnen.

3
GrandmasterB

Das hängt ganz davon ab, was Sie analysieren müssen. Können Sie Ihre eigenen schneller rollen, als Sie die Lernkurve eines Lexers erreichen könnten? Ist das zu analysierende Material statisch genug, dass Sie die Entscheidung später nicht bereuen werden? Finden Sie bestehende Implementierungen zu komplex? Wenn ja, viel Spaß beim Rollen, aber nur, wenn Sie keine Lernkurve durchlaufen.

In letzter Zeit hat mir der Zitronenparser wirklich gefallen, der wohl der einfachste und einfachste ist, den ich je benutzt habe. Um die Wartung zu vereinfachen, verwende ich das nur für die meisten Anforderungen. SQLite verwendet es ebenso wie einige andere bemerkenswerte Projekte.

Aber ich interessiere mich überhaupt nicht für Lexer, darüber hinaus stören sie mich nicht, wenn ich einen verwenden muss (daher Zitrone). Sie könnten es sein, und wenn ja, warum nicht einen machen? Ich habe das Gefühl, dass Sie wieder eine verwenden werden, die es gibt, aber kratzen Sie den Juckreiz, wenn Sie müssen :)

3
Tim Post

Haben Sie überlegt Martin Fowlers Language Workbench-Ansatz ? Zitat aus dem Artikel

Die offensichtlichste Änderung, die eine Sprach-Workbench an der Gleichung vornimmt, ist die einfache Erstellung externer DSLs. Sie müssen keinen Parser mehr schreiben. Sie müssen die abstrakte Syntax definieren - aber das ist eigentlich ein ziemlich einfacher Schritt zur Datenmodellierung. Außerdem erhält Ihr DSL ein leistungsfähiges IDE - obwohl Sie einige Zeit damit verbringen müssen, diesen Editor zu definieren. Der Generator ist immer noch etwas, was Sie tun müssen, und ich habe das Gefühl, dass es nicht viel ist einfacher als je zuvor. Aber dann ist der Bau eines Generators für ein gutes und einfaches DSL einer der einfachsten Teile der Übung.

Wenn ich das lese, würde ich sagen, dass die Tage des Schreibens Ihres eigenen Parsers vorbei sind und es besser ist, eine der verfügbaren Bibliotheken zu verwenden. Sobald Sie die Bibliothek beherrschen, profitieren alle DSLs, die Sie in Zukunft erstellen, von diesem Wissen. Außerdem müssen andere Ihre Herangehensweise an das Parsen nicht lernen.

Bearbeiten, um den Kommentar (und die überarbeitete Frage) abzudecken

Vorteile des eigenen Rollens

  1. Sie werden den Parser besitzen und all diese schönen Erfahrungen beim Durchdenken einer komplizierten Reihe von Problemen sammeln
  2. Sie können sich etwas Besonderes einfallen lassen, an das noch niemand gedacht hat (unwahrscheinlich, aber Sie scheinen ein kluger Kerl zu sein).
  3. Es wird Sie mit einem interessanten Problem beschäftigen

Kurz gesagt, Sie sollten sich selbst rollen, wenn Sie wirklich tief in den Darm eines ernsthaft schwierigen Problems eindringen möchten, zu dessen Bewältigung Sie sich stark motiviert fühlen.

Vorteile der Verwendung der Bibliothek eines anderen

  1. Sie vermeiden es, das Rad neu zu erfinden (ein häufiges Problem bei der Programmierung, dem Sie zustimmen werden).
  2. Sie können sich auf das Endergebnis konzentrieren (Sie glänzende neue Sprache) und sich nicht zu viele Gedanken darüber machen, wie es analysiert wird usw.
  3. Sie werden Ihre Sprache viel schneller in Aktion sehen (aber Ihre Belohnung wird geringer sein, weil Sie nicht alles waren)

Wenn Sie ein schnelles Endergebnis erzielen möchten, verwenden Sie daher die Bibliothek eines anderen Benutzers.

Insgesamt kommt es darauf an, wie sehr Sie das Problem und damit die Lösung besitzen möchten. Wenn Sie alles wollen, rollen Sie Ihre eigenen.

3
Gary Rowe

Der große Vorteil beim Schreiben Ihrer eigenen ist, dass Sie wissen, wie Sie Ihre eigenen schreiben. Der große Vorteil bei der Verwendung eines Tools wie yacc besteht darin, dass Sie wissen, wie Sie das Tool verwenden. Ich bin ein Fan von Baumwipfel für die erste Erkundung.

2
philosodad

Warum nicht einen Open-Source-Parser-Generator abspalten und zu Ihrem eigenen machen? Wenn Sie keine Parser-Generatoren verwenden, ist Ihr Code sehr schwer zu pflegen, wenn Sie die Syntax Ihrer Sprache stark geändert haben.

In meinen Parsern habe ich reguläre Ausdrücke (ich meine im Perl-Stil) verwendet, um Token zu erstellen, und einige praktische Funktionen verwendet, um die Lesbarkeit des Codes zu verbessern. Ein vom Parser generierter Code kann jedoch schneller sein, indem Statustabellen und lange switch-cases erstellt werden, wodurch die Größe des Quellcodes erhöht werden kann, sofern Sie nicht .gitignore Sie.

Hier sind zwei Beispiele meiner benutzerdefinierten Parser:

https://github.com/SHiNKiROU/DesignScript - ein BASIC-Dialekt, da ich zu faul war, Lookaheads in Array-Notation zu schreiben, habe ich die Qualität von Fehlermeldungen geopfert https: // github. com/SHiNKiROU/ExprParser - Ein Formelrechner. Beachten Sie die seltsamen Metaprogrammier-Tricks

1
Ming-Tang

"Soll ich dieses bewährte 'Rad' verwenden oder neu erfinden?"

0
JBRWilkinson