Ich programmiere schon seit vielen Jahren, aber eine Aufgabe, die mich immer noch überfordert, ist die Angabe einer Grammatik für einen Parser, und selbst nach dieser übermäßigen Anstrengung bin ich mir nie sicher, ob die Grammatik, die ich mir ausgedacht habe, gut ist ( durch ein vernünftiges Maß an "gut").
Ich erwarte nicht, dass es einen Algorithmus zur Automatisierung des Vorgangs zum Festlegen einer Grammatik gibt, aber ich hoffe, dass es Möglichkeiten gibt, das Problem so zu strukturieren, dass das Rätselraten und das Ausprobieren meines aktuellen Ansatzes weitgehend beseitigt werden.
Mein erster Gedanke war, über Parser zu lesen, und ich habe einige davon gemacht, aber alles, was ich zu diesem Thema gelesen habe, nimmt die Grammatik als gegeben (oder trivial genug, dass man sie durch Inspektion spezifizieren kann) und konzentriert sich auf das Problem der Übersetzung dieser Grammatik in einen Parser. Ich interessiere mich gleich vorher für das Problem: wie man die Grammatik überhaupt spezifiziert.
Ich interessiere mich hauptsächlich für das Problem, eine Grammatik anzugeben , die formal eine Sammlung konkreter Beispiele (positiv und negativ) darstellt. Dies unterscheidet sich vom Problem beim Entwerfen einer neuen Syntax . Vielen Dank an Macneil, der auf diesen Unterschied hingewiesen hat.
Ich hatte den Unterschied zwischen einer Grammatik und einer Syntax nie wirklich geschätzt, aber jetzt, wo ich anfange, dies zu sehen, konnte ich meine erste Klarstellung schärfen, indem ich sagte, dass ich hauptsächlich an dem Problem interessiert bin, eine Grammatik anzugeben, die a erzwingt Vordefinierte Syntax: In meinem Fall ist die Basis für diese Syntax normalerweise eine Sammlung von positiven und negativen Beispielen.
Wie wird die Grammatik für einen Parser angegeben? Gibt es ein Buch oder eine Referenz, die de facto als Standard für die Beschreibung von Best Practices, Entwurfsmethoden und anderen hilfreichen Informationen zur Angabe einer Grammatik für einen Parser gilt? Auf welche Punkte sollte ich mich konzentrieren, wenn ich über Parser-Grammatik lese?
Antworten:
Aus den Beispieldateien müssen Sie Entscheidungen treffen, die darauf basieren, wie stark Sie diese Beispiele verallgemeinern möchten. Angenommen, Sie hatten die folgenden drei Beispiele: (jedes ist eine separate Datei)
Sie könnten einfach zwei Grammatiken angeben, die diese Beispiele akzeptieren:
Trivial Grammatik Eins:
Triviale Grammatik Zwei:
Der erste ist trivial, weil er nur die drei Samples akzeptiert . Der zweite ist trivial, weil er alles akzeptiert, was möglicherweise diese Tokentypen verwenden könnte. [Für diese Diskussion gehe ich davon aus, dass Sie sich keine großen Sorgen um das Tokenizer-Design machen: Es ist einfach, Bezeichner, Zahlen und Interpunktionszeichen als Ihre Token anzunehmen, und Sie können jedes Token-Set aus jeder Skriptsprache ausleihen, die Sie möchten wie auch immer.]
Das Verfahren, das Sie befolgen müssen, besteht darin, auf der höchsten Ebene zu beginnen und zu entscheiden, wie viele Instanzen ich zulassen möchte. Wenn es sinnvoll sein kann, ein syntaktisches Konstrukt beliebig oft zu wiederholen, z. B.
method
s in einer Klasse, möchten Sie eine Regel mit der folgenden Form:Was in EBNF besser ausgedrückt wird als:
Es ist wahrscheinlich offensichtlich, wenn Sie nur null oder eine Instanz möchten (was bedeutet, dass das Konstrukt optional ist, wie bei der
extends
Klausel für eine Java-Klasse), oder wenn Sie eine oder mehrere Instanzen zulassen möchten (wie bei einem Variableninitialisierer in einer Deklaration) ). Sie müssen Probleme berücksichtigen, wie das Erfordernis eines Trennzeichens zwischen Elementen (wie,
in einer Argumentliste), das Erfordernis eines Abschlusszeichens nach jedem Element (wie in den;
zu trennenden Anweisungen) oder das Erfordernis eines Trennzeichens oder Abschlusszeichens (wie im Fall) mit Methoden in einer Klasse).Wenn Ihre Sprache arithmetische Ausdrücke verwendet, ist es für Sie einfach, die Vorrangregeln einer vorhandenen Sprache zu kopieren. Es ist am besten, sich an etwas Bekanntes zu halten, wie z. B. Cs Ausdrücke, als sich für etwas Exotisches zu entscheiden, aber nur, wenn alles andere gleich ist.
Neben Prioritätsproblemen (was wird miteinander analysiert) und Wiederholungsproblemen (wie viele Elemente sollten auftreten, wie werden sie getrennt?) Müssen Sie auch über die Reihenfolge nachdenken: Muss immer etwas vor einem anderen Element stehen? Wenn eine Sache enthalten ist, sollte eine andere ausgeschlossen werden?
An diesem Punkt könnten Sie versucht sein, einige Regeln grammatikalisch durchzusetzen. Wenn beispielsweise
Person
das Alter einer Person angegeben ist, möchten Sie nicht, dass auch das Geburtsdatum angegeben wird. Sie können zwar Ihre Grammatik dafür konstruieren, es ist jedoch möglicherweise einfacher, dies mit einem "semantischen Überprüfungsdurchlauf" zu erzwingen, nachdem alles analysiert wurde. Dies vereinfacht die Grammatik und sorgt meiner Meinung nach für bessere Fehlermeldungen bei Regelverstößen.quelle
Für die meisten Parser-Generatoren ist dies normalerweise eine Variante des Backus-Naur -
<nonterminal> ::= expression
Formats. Ich gehe davon aus, dass Sie so etwas verwenden und nicht versuchen, Ihre Parser von Hand zu erstellen. Wenn Sie einen Parser für ein Format erstellen können, für das Sie die Syntax angegeben haben (ich habe unten ein Beispielproblem aufgeführt), ist die Angabe von Grammatiken nicht Ihr Problem.Ich denke, Sie haben es mit Syntax zu tun, die aus einer Reihe von Samples abgeleitet wird. Das ist wirklich mehr Mustererkennung als Syntaxanalyse. Wenn Sie darauf zurückgreifen müssen, bedeutet dies, dass derjenige, der Ihre Daten bereitstellt, Ihnen die Syntax nicht mitteilen kann, da er das Format nicht gut beherrscht. Wenn Sie die Möglichkeit haben, zurückzudrängen und ihnen eine formale Definition zu geben, tun Sie dies. Es ist nicht fair für sie, Ihnen ein vages Problem zu geben, wenn Sie für die Folgen eines Parsers verantwortlich gemacht werden könnten, der auf einer abgeleiteten Syntax basiert, die falsche Eingaben akzeptiert oder gute Eingaben ablehnt.
"Gut" in Ihrer Situation müsste bedeuten "analysiert die positiven und lehnt die negativen ab." Ohne eine andere formale Spezifikation der Syntax Ihrer Eingabedatei sind die Beispiele Ihre einzigen Testfälle, und Sie können nichts Besseres tun. Sie könnten Ihren Fuß nach unten setzen und sagen, dass nur die positiven Beispiele gut sind und alles andere ablehnen, aber das entspricht wahrscheinlich nicht dem Geist dessen, was Sie erreichen wollen.
Unter vernünftigen Umständen ist das Testen einer Grammatik wie das Testen von allem anderen: Sie müssen sich genügend Testfälle einfallen lassen, um alle Varianten der Nichtterminals (und Terminals, wenn sie von einem Lexer generiert werden) zu testen.
Beispielproblem
Schreiben Sie eine Grammatik, die Textdateien analysiert, die eine Liste enthalten, wie in den folgenden Regeln definiert:
Eingabebeispiel (alle gültig):
quelle
Die Antworten von Macneil und Blrfl sind großartig. Ich möchte nur einige Kommentare zum Beginn des Prozesses hinzufügen.
Eine Syntax ist nur eine Möglichkeit, ein Programm darzustellen . Die Syntax Ihrer Sprache sollte also durch Ihre Antwort auf diese Frage bestimmt werden: Was ist ein Programm?
Man könnte sagen, dass ein Programm eine Sammlung von Klassen ist. Okay, das gibt uns
als Ausgangspunkt. Oder du musst es vielleicht schreiben
Was ist nun eine Klasse? Es hat einen Namen; eine optionale Oberklassenspezifikation; und eine Reihe von Konstruktor-, Methoden- und Felddeklarationen. Außerdem benötigen Sie eine Möglichkeit, eine Klasse in eine einzelne (eindeutige) Einheit zu gruppieren, und dies sollte einige Zugeständnisse an die Benutzerfreundlichkeit mit sich bringen (z. B. Kennzeichnen mit dem reservierten Wort
class
). Okay:Das ist eine Notation ("konkrete Syntax"), die Sie wählen können. Oder Sie können sich genauso gut dafür entscheiden:
oder
Sie haben diese Entscheidung wahrscheinlich bereits implizit getroffen, insbesondere wenn Sie Beispiele haben, aber ich möchte nur den Punkt verstärken: Die Struktur der Syntax wird durch die Struktur der Programme bestimmt, die sie darstellen. Das ist es, was Sie über die "trivialen Grammatiken" von Macneils Antwort hinausführt. Beispielprogramme sind jedoch nach wie vor sehr wichtig. Sie dienen zwei Zwecken. Erstens helfen sie Ihnen dabei, auf abstrakter Ebene herauszufinden, was ein Programm ist. Zweitens helfen sie Ihnen bei der Entscheidung, welche konkrete Syntax Sie verwenden sollten, um die Struktur Ihrer Sprache darzustellen.
Sobald Sie die Struktur festgelegt haben, sollten Sie sich erneut mit Fragen wie dem Zulassen von Leerzeichen und Kommentaren, dem Beheben von Mehrdeutigkeiten usw. befassen Analyse-Technologie, die Sie verwenden.
Versuchen Sie schließlich nicht, alles über Ihre Sprache in der Grammatik darzustellen. Beispielsweise möchten Sie möglicherweise bestimmte Arten von nicht erreichbarem Code verbieten (z. B. eine Anweisung nach einem
return
, wie in Java). Sie sollten wahrscheinlich nicht versuchen, das in die Grammatik zu stopfen, weil Sie entweder Dinge verpassen (whoops, wasreturn
ist , wenn in geschweiften Klammern oder wenn beide Zweige einerif
Anweisung zurückkehren?) Oder Ihre Grammatik viel zu kompliziert machen managen. Es ist eine kontextsensitive Einschränkung. schreibe es als separaten Durchgang. Ein weiteres sehr verbreitetes Beispiel für eine kontextsensitive Einschränkung ist ein Typensystem. Sie könnten Ausdrücke wie1 + "a"
in der Grammatik ablehnen , wenn Sie hart genug gearbeitet haben, aber Sie könnten nicht ablehnen1 + x
(wox
hat Typ Zeichenfolge). SoVermeiden Sie halbherzige Einschränkungen in der Grammatik und machen Sie sie korrekt als separaten Durchgang.quelle