Welchen Datentyp soll ein Lexer, wie im Titel erwähnt, zurückgeben / dem Parser geben? Beim Lesen des Artikels zur lexikalischen Analyse , den Wikipedia verfasst hat, wurde Folgendes festgestellt:
In der Informatik ist die lexikalische Analyse das Umwandeln einer Folge von Zeichen (wie in einem Computerprogramm oder einer Webseite) in eine Folge von Token ( Zeichenfolgen mit einer identifizierten "Bedeutung").
Im völligen Widerspruch zu der obigen Aussage, als eine andere Frage, die ich auf einer anderen Site gestellt habe ( Code Review, wenn Sie neugierig sind), beantwortet wurde, erklärte die antwortende Person:
Der Lexer liest normalerweise die Zeichenkette und wandelt sie in einen Strom von Lexemen um. Die Lexeme müssen nur ein Strom von Zahlen sein .
und er gab dieses Bild:
nl_output => 256
output => 257
<string> => 258
Später in dem Artikel erwähnte er Flex
einen bereits existierenden Lexer und sagte, das Schreiben von "Regeln" damit sei einfacher als das Schreiben eines Lexers von Hand. Er fuhr fort, mir dieses Beispiel zu geben:
Space [ \r\n\t]
QuotedString "[^"]*"
%%
nl_output {return 256;}
output {return 257;}
{QuotedString} {return 258;}
{Space} {/* Ignore */}
. {error("Unmatched character");}
%%
Um meinen Einblick zu erweitern und mehr Informationen zu erhalten, habe ich den Wikipedia-Artikel über Flex gelesen . Der Flex-Artikel zeigte, dass Sie mit Token auf folgende Weise einen Satz von Syntaxregeln definieren können:
digit [0-9]
letter [a-zA-Z]
%%
"+" { return PLUS; }
"-" { return MINUS; }
"*" { return TIMES; }
"/" { return SLASH; }
"(" { return LPAREN; }
")" { return RPAREN; }
";" { return SEMICOLON; }
"," { return COMMA; }
"." { return PERIOD; }
":=" { return BECOMES; }
"=" { return EQL; }
"<>" { return NEQ; }
"<" { return LSS; }
">" { return GTR; }
"<=" { return LEQ; }
">=" { return GEQ; }
"begin" { return BEGINSYM; }
"call" { return CALLSYM; }
"const" { return CONSTSYM; }
"do" { return DOSYM; }
"end" { return ENDSYM; }
"if" { return IFSYM; }
"odd" { return ODDSYM; }
"procedure" { return PROCSYM; }
"then" { return THENSYM; }
"var" { return VARSYM; }
"while" { return WHILESYM; }
Es scheint mir, dass der Flex-Lexer Zeichenfolgen von Keywords \ Token zurückgibt. Es können jedoch Konstanten zurückgegeben werden, die bestimmten Zahlen entsprechen.
Wenn der Lexer Zahlen zurückgeben würde, wie würde er String-Literale lesen? Die Rückgabe einer Zahl ist für einzelne Stichwörter in Ordnung. Aber wie würden Sie mit einer Zeichenfolge umgehen? Müsste der Lexer die Zeichenfolge nicht in Binärzahlen konvertieren, und der Parser würde die Zahlen dann wieder in eine Zeichenfolge konvertieren. Es erscheint dem Lexer viel logischer (und einfacher), Zeichenfolgen zurückzugeben und den Parser dann beliebige Zahlenzeichenfolgenliterale in tatsächliche Zahlen umwandeln zu lassen.
Oder könnte der Lexer beides zurückgeben? Ich habe versucht, einen einfachen Lexer in c ++ zu schreiben, mit dem Sie nur einen Rückgabetyp für Ihre Funktionen haben. Das veranlasste mich, meine Frage zu stellen.
Um meine Frage in einem Absatz zusammenzufassen: Wenn Sie ein Lexer schreiben und davon ausgehen, dass es nur einen Datentyp (Zeichenfolgen oder Zahlen) zurückgeben könnte, was wäre die logischere Wahl?
quelle
Antworten:
Wenn Sie eine Sprache durch Lexing und Parsing verarbeiten, haben Sie im Allgemeinen eine Definition Ihrer lexikalischen Token, z.
und Sie haben eine Grammatik für den Parser:
Ihr Lexer nimmt den Eingabestream und erzeugt einen Stream von Tokens. Der Token-Stream wird vom Parser verbraucht, um einen Analysebaum zu erstellen. In einigen Fällen reicht es aus, nur den Typ des Tokens zu kennen (z. B. LPAREN, RBRACE, FOR), in einigen Fällen benötigen Sie jedoch den tatsächlichen Wert , der dem Token zugeordnet ist. Wenn Sie beispielsweise auf ein ID-Token stoßen, möchten Sie die tatsächlichen Zeichen, aus denen sich die ID zusammensetzt, später herausfinden, auf welche ID Sie verweisen möchten.
Normalerweise haben Sie also ungefähr Folgendes:
Wenn der Lexer ein Token zurückgibt, wissen Sie, um welchen Typ es sich handelt (den Sie zum Analysieren benötigen) und aus welcher Zeichenfolge es generiert wurde (den Sie später zum Interpretieren von Zeichenfolgen und numerischen Literalen sowie Bezeichnern benötigen, etc.). Es könnte sich so anfühlen, als würden Sie zwei Werte zurückgeben, da Sie einen sehr einfachen Aggregattyp zurückgeben, aber Sie brauchen wirklich beide Teile. Schließlich möchten Sie die folgenden Programme anders behandeln:
Diese produzieren die gleiche Folge von Token - Typen : IF, LPAREN, NUMBER, GREATER_THAN, NUMBER, RPAREN, lbrace, ID, LPAREN, STRING, RPAREN, SEMIKOLON, RBRACE. Das heißt, sie analysieren auch das Gleiche. Wenn Sie jedoch tatsächlich etwas mit dem Analysebaum tun, ist es Ihnen wichtig, dass der Wert der ersten Zahl "2" (oder "0") und der Wert der zweiten Zahl "0" (oder "2") ist ') und dass der Wert der Zeichenfolge' 2> 0 '(oder' 0> 2 ') ist.
quelle
String value
sich das füllen? Wird es mit einer Zeichenfolge oder einer Zahl gefüllt? Und wie würde ich denString
Typ definieren ?parse(inputStream).forEach(token -> print(token.string); print(' '))
(dh Sie müssen nur die Zeichenfolgenwerte der Token drucken, die durch ein Leerzeichen voneinander getrennt sind). Das geht ziemlich schnell. Und selbst wenn LPAREN nur aus "(" hervorgeht, kann dies eine konstante Zeichenfolge im Speicher sein, sodass das Einfügen eines Verweises darauf in das Token möglicherweise nicht teurer ist als das Einfügen des Nullverweises. Im Allgemeinen würde ich lieber schreiben Code, der mich nicht zu einem Sonderfall macht Code"Token" natürlich. Ein Lexer erzeugt einen Strom von Token, daher sollte er einen Strom von Token zurückgeben .
Maschinengenerierte Lexer haben den Vorteil, dass Sie sie schnell generieren können. Dies ist besonders praktisch, wenn Sie glauben, dass sich Ihre lexikalische Grammatik stark ändern wird. Sie haben den Nachteil, dass Sie bei Ihren Implementierungsentscheidungen oft nicht viel Flexibilität haben.
Das heißt, wen interessiert es, wenn es "einfacher" ist? Das Lexer zu schreiben ist normalerweise nicht der schwierige Teil!
Weder. Ein Lexer hat normalerweise eine "next" -Operation, die ein Token zurückgibt, also sollte es ein Token zurückgeben . Ein Token ist keine Zeichenfolge oder Zahl. Es ist ein Zeichen.
Das letzte Lexer, das ich geschrieben habe, war ein "Full Fidelity" -Lexer, dh, es gab ein Token zurück, das die Position aller Whitespaces und Kommentare - die wir "Trivia" nennen - im Programm sowie das Token verfolgte. In meinem Lexer wurde ein Token definiert als:
Trivia wurde definiert als:
Also wenn wir so etwas hätten
die mit Token - Arten , wie vier Token würde lex
Identifier
,Plus
,Identifier
,Semicolon
, und Breite 3, 1, 3, 1. Die erste Kennung hat führendes Haupt , bestehend ausWhitespace
mit einer Breite von 4 und HinterhauptWhitespace
mit einer Breite von 1. DasPlus
hat keine führende Haupt und abschließende Trivia bestehend aus einem Whitespace, einem Kommentar und einer Newline. Der endgültige Bezeichner besteht aus einem Kommentar, einem Leerzeichen usw.Mit diesem Schema wird jedes Zeichen in der Datei in der Ausgabe des Lexers berücksichtigt. Dies ist eine praktische Eigenschaft, die Sie beispielsweise für die Syntaxfärbung benötigen.
Wenn Sie diese Kleinigkeiten nicht benötigen, können Sie einfach zwei Dinge anfertigen: die Art und die Breite.
Möglicherweise stellen Sie fest, dass das Token und die Trivia nur ihre Breite und nicht ihre absolute Position im Quellcode enthalten. Das ist absichtlich. Ein solches Schema hat Vorteile:
Wenn Sie sich nicht für eines dieser Szenarien interessieren, kann ein Token als eine Art und ein Offset dargestellt werden, anstatt als eine Art und eine Breite.
Aber das Wichtigste dabei ist: Programmieren ist die Kunst, nützliche Abstraktionen zu machen . Sie manipulieren Token, machen also eine nützliche Abstraktion über Token, und dann können Sie selbst entscheiden, welche Implementierungsdetails dahinterstehen.
quelle
Im Allgemeinen geben Sie eine kleine Struktur mit einer Zahl zurück, die das Token (oder den Enum-Wert zur Vereinfachung der Verwendung) und einen optionalen Wert (Zeichenfolge oder möglicherweise generischer Wert / Vorlagenwert) angibt. Ein anderer Ansatz wäre, einen abgeleiteten Typ für Elemente zurückzugeben, die zusätzliche Daten enthalten müssen. Beide sind leicht unangenehm, aber gut genug, um ein praktisches Problem zu lösen.
quelle
Token *
oder einfach einToken
oder ein zurückgeben,TokenPtr
das ein gemeinsamer Zeiger derToken
Klasse ist. Ich sehe aber auch, dass einige Lexer nur einen TokenType zurückgeben und die Zeichenfolge oder den Zahlenwert in anderen globalen oder statischen Variablen speichern. Eine andere Frage ist, wie wir die Standortinformationen speichern können. Benötige ich eine Token-Struktur mit den Feldern TokenType, String und Location? Vielen Dank.struct Token {TokenType id; std::string lexeme; int line; int column;}
, oder? Für eine öffentliche Funktion von Lexer wiePeekToken()
könnte die Funktion einToken *
oder zurückgebenTokenPtr
. Ich denke es für eine Weile, wenn die Funktion nur den TokenType zurückgibt, wie versucht der Parser, die anderen Informationen über das Token zu erhalten? Daher wird ein Zeiger wie ein Datentyp für die Rückgabe von einer solchen Funktion bevorzugt. Irgendwelche Kommentare zu meiner Idee? Danke