![]() |
Wie am besten Parsen?
Hi!
Ich hab mir mal überlegt eine kleine Scriptsprache zu entwickeln... allerdings weiß ich grade nicht, wie ich am besten den Script-Quellcode parsen kann (also welche Technik ich verwenden soll). Ich könnte ja prinzipiell den ganzen Code mit einer Schleife durchlaufen, aber das halte ich nicht für eine gute Idee... besonders, wenn ich nicht nach Zeichen suche, sondern nach Strings (und die mit solch einer Schleife schwieriger zu finden sind). Kennt jemand zufällig einen Trick, wie man leicht den Code parsen kann? Oder hat überhaupt jemand Ideen, wie man das machen könnte? Danke schonmal im Vorraus! |
Re: Wie am besten Parsen?
Ich habs zwar mal gemacht, aber im erklären bin ich nicht so sehr großartig ;-)
Also, wenn du eine Sprache haben willst, musst du verschiedene syntaktische Elemente festlegen, darunter auch Begrenzer für Statements usw. in Pascal gibt es jede Menge dieser Dinger: ";,()[] begin end for in do out var type" usw.. Du gehts eigentlich beim parsen nur von einem Begrenzer zum nächsten und wertest aus, was dazwischen steht, gekoppelt mit der Bedeutung des Begrenzers selbst. |
Re: Wie am besten Parsen?
Zitat:
|
Re: Wie am besten Parsen?
Du könntest dir eine Funktion schreiben, die dir zurückgibt, ob ein Zeichen eine Ziffer ist, ein + oder - oder * oder / Zeichen ist oder ein Buchstabe.
Dann durchläufst du mit ner while-Schleife (z.B. ob ein Zeichen eine Ziffer ist) den String und suchst nach Zahlen. Dann machst du das ganze mit den Rechenzeichen und schaust, wie alles gerechnet werden muss. Natürlich musst du auf Klammern aufpassen ;) |
Re: Wie am besten Parsen?
Ein sehr einfacher, aber hilfreicher Parser wäre z.B. die Klasse TParser aus der Unit Classes. Darauf könntest du deinen eigenen Parser aufbauen.
|
Re: Wie am besten Parsen?
Zitat:
|
Re: Wie am besten Parsen?
Zitat:
|
Re: Wie am besten Parsen?
Naja, ich such immer noch nach einer guten Methode, einen String nach anderen Teil-Strings zu durchsuchen.
Meine Script-Syntax soll in etwa so aussehen:
Code:
Etwa so... jetzt brauche ich aber eine vernünftige Möglichkeit, den Code von oben bis unten durchzulaufen und zu prüfen, ob eines der Schlüsselwörter zu finden ist bzw. ob eine Funktion aufgerufen wird. Mit Pos oder PosEx zu arbeiten halte ich für ungeschickt, weil ich den Quellcode ja von oben nach unten durcharbeiten will. Bei Pos sucht der ja nur nach einzelnen Schlüsselwörtern im Zeilstring... :?
int $EineIntVariable; //an Sprachen wie PHP, Perl oder C angelehnt
$EineIntVariable = 10; //Wert 10 zuweisen Message('Hier Text'); //Messagebox int $ZweiteIntVariable; // noch eine IntVariable erstellen $EineIntVariable = $ZweiteIntVariable + 1; @Dax: Wie hast du denn zum Beispiel in deiner Scriptsprache nach den Bezeichnern gesucht? Also mit welcher Funktion? Eine komplett selbstgeschriebene, oder wie genau? |
Re: Wie am besten Parsen?
Ich hab meine Funktion komplett selbst geschrieben, nur mit Pos, Schleifen und Pointern. Ziemlich unsichere Sache, die ich da hatte, und extrem spezifisch.. die hier zu posten und deinen Wünschen entsprechend umzuändern würde länger dauern, als eine komplett neue, flexiblere Funktion zu schreiben..
|
Re: Wie am besten Parsen?
Such mal nach "Tokenizer". Der kommt nämlich noch vor dem Parser. Der Tokenizer zerlegt Quelltext in seine Bestandteile (z.B. Schlüsselwörter, Stringliterale, Numerische Konstanten, Operatoren) und danach kommt der Parser, der ja auch weiß, ob bestimmte Token in einer gewissen Reihenfolge auftauchen dürfen. Nehmen wir mal:
Delphi-Quellcode:
Der Tokenizer würde hier finden: for, x, :=, 0, do, 78, to!
for x := 0 do 78 to
Aber erst der Parser kann ermitteln, daß das DO zwischen den beiden Zahlen syntaktisch inkorrekt ist. Alternativ kannst du bei Bloodshed mal nach CoPascal suchen - einem Miniinterpreter von N. Wirth, dem Erfinder von Pascal - dort sind ja alle benötigten Techniken mehr oder minder implementiert. |
Re: Wie am besten Parsen?
Moin Malo,
als erstes solltest Du Dir mal darüber Gedanken machen, was für Bestandteile die Sprache haben soll, und wie diese aufgebaut sein sollen. Beispiel: Bezeichner (für Variablen, Keywords, Datentypen): Regel : dürfen nur aus Buchstaben und Ziffern bestehen, müssen mit einem Buchstaben anfangen. Zahlen: Regel : dürfen nur aus Ziffern bestehen, müssen mit einer Ziffer ungleich 0 beginnen, Wertebereich von / bis. Strings: Regel : müssen mit ' beginnen, müssen mit ' enden. Operatoren: Regel : erlaubt sind +,-,*,/,=,; (in Deinem Beispiel noch $ als Kennzeichen, dass eine Variable folgt) Jeder dieser Bestandteile ist ein Token, dass sich aus Typ und Attribut zusammensetzt. Bei einem Bezeichner wäre das dann z.B. Typ: Bezeichner, Attribut: EineIntVariable oder das =: Typ: Operator Zuweisung, Attribut: <Keines erforderlich> Wenn Du jetzt also Deine Bestandteile und die Regeln, wie sie gebildet werden hast, kannst Du anfangen die Quelldatei "auseianderzunehmen". Hierbei musst Du dann Zeichen für Zeichen durchgehen, und bei jedem entscheiden, wie es weitergehen kann. Wieder auf Dein Beispiel bezogen: Du triffst auf ein i (das aus int). Da es sich um einen Buchstaben handelt, muss es sich um irgendeinen Bezeichner handeln. Jetzt kannst Du also von hier aus, bis zum ersten Trennzeichen durchgehen (hier ein Blank), und hast anschliessend Deinen Bezeichner int. 1. Token: IDENTIFIER:int Als nächstes triffst Du auf $ 2. Token: OperatorVariable:$ jetzt folgt wieder ein Bezeichner usw. Wenn Du auf einen Kommentar triffst, kannst Du diesen natürlich überlesen, denn er hat ja mit dem Ablauf nichts zu tun. Als Interpreter solltest Du eine ganze Zeile am Stück in Token verwandeln (gekennzeichnet bei Dir durch ;), und kannst anschliessend darangehen die Zeile auszuwerten: 1. Token ist ein Datentyp => jetzt muss ein $ folgen => jetzt muss ein Bezeichner folgen, der kein Keyword ist (hier: OK, kann in die Liste der Variablen als integer-Variable aufgenommen werden) => jetzt muss ein logisches Zeilenende folgen Jetzt bis zum "physikalischen"-Zeilenende (#13#10) wieder von Vorne, da nur noch ein Kommentar folgt geht's weiter: Ist diese aufgespalten: 1. Token ein $ => Jetzt muss eine Variable folgen. In der Liste ist diese, als kann es weitergehen, sonst Fehler => Jetzt muss eine Zuweisung folgen. => Da es sich um eine integer-Variable handelt, muss jetzt ein numerischer Ausdruck folgen (Variablen, Zahlen, Operatoren) => eine 10, also wird der Variablen in der Liste jetzt dieser Wert zugeordnet. .... Das ist jetzt nur einmal grob vereinfacht dargestellt. Als Suchbegriffe zu diesem Thema kannst Du es mal mit Compilerbau, DEA (Determinierender endlicher Automat), Zustandsautomat versuchen. Zum Thema ![]() Im Moment zur Hand habe ich diesen ![]() |
Re: Wie am besten Parsen?
Zitat:
auch, wenn ich noch nicht viel sinnvolles zu "Tokenizer" gefunden hab (es soll zwar eine tokenizer.pas geben, die ich jedoch noch nicht gefunden hab), ist das mal ein vernünftiger Ansatz. Prinzipiell könnte ich ja den ganzen Quellcode in StringListen packen (eventuell für jede Zeile eine eigene, also bis zum Semikolon immer, aber darüber kann man ja noch diskutieren). Dann prüfe ich einen Eintrag nach dem anderen, ob die in die Syntax passt. Aber das ist mal 'ne Idee... :thumb: //edit: Auf Christians Beitrag antworte ich später noch mal, hab grad nich so viel zeit... |
Re: Wie am besten Parsen?
Moin Malo,
wenn Du erst einmal weisst aus was sich die Sprache zusammensetzen soll ist ein Tokenizer noch das kleinste Übel ;-) |
Re: Wie am besten Parsen?
der tokenizer muss doch den text nur mit den begrenzungszeichen [' ',';',','] zerteilen. und am besten noch sagen, welches begrenzungszeichen es denn war.
|
Re: Wie am besten Parsen?
@DGL
und sie siehts anstatt mit
Delphi-Quellcode:
hiermit aus?
x := 1;
Delphi-Quellcode:
Da brauchst du als Trennzeichen '', was dann aber jedes Zeichen separiert.
x:=1;
Gut, in einer "Anfangssprache" kann man halt erzwingen, damit man Leerzeichen setzen muss air |
Re: Wie am besten Parsen?
Zitat:
|
Re: Wie am besten Parsen?
Zitat:
Zitat:
Ob man bei Strings nun ' oder " oder was ganz anderes verwendet, ist eher nebensächlich. Das, wo ich mir bisher die wenigsten Gedanken drüber gemacht hab, sind die Operatoren, die ja die wichtigsten sind... ;) Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
@Airblader: Verkompliziere das bitte nicht unnötig. Ich will dann halt immer Leerzeilen als Begrenzung haben, basta :mrgreen: Gehört meiner Meinung auch zum guten Programmierstil... alles so eng zu schreiben ist eklig :? |
Re: Wie am besten Parsen?
Moin malo,
Zitat:
Wenn Du in einem C(++)-Quelltext auf eine 0 triffst, kann es sein, dass es sich um eine einfach dezimale 0 handelt, es könnte aber auch der Beginn einer hexadezimalen Zahl (0x, 0X) oder einer oktalen Zahl sein. ;-) Mal abgesehen davon, dass auch Gleitkommazahlen mit einer 0 beginnen können... Was die Tokenliste angeht: Ich habe es (vereinfacht) so gelöst: Ein Aufzählungstyp der die verschiedenen Tokentypen enthält. Ein Recordtyp der Typ und Attribut enthält. Eine Klasse, die ein dynamisches Array des Recordtypen enthält, und für die sonstige Verwaltung der Token zuständig ist. (z.B. Add, Delete, Insert...) |
Re: Wie am besten Parsen?
@malo
gehört für mich auch dazu, aber in praktisch allen sprachen ist es erlaubt/möglich. Da es ja deine Sprache ist, ist es dann aber auch deine Sache ;) air |
Re: Wie am besten Parsen?
Zitat:
Obwohl... grade eben kommt mir dazu die Idee, gezielt nach solchen Konstruktionen zu suchen (vor dem Aufteilen in StringListen!) und einfach Leerzeichen davor und dahinter setzen :mrgreen: Problem wären dann nur ähnliche Konstruktionen in Strings :? |
Re: Wie am besten Parsen?
Moin Malo,
eigentlich brauchst Du Dir keinen Gedanken darum zu machen bestimmte Konstruktion zu suchen. Als erstes solltest Du Deinen Sourcecode in einen Tokenstrom verwandeln. Ob diese dann syntaktisch richtig sind spielt zu diesem Zeitpunkt keine Rolle. Wenn Du das Beispiel von Airblader nimmst: x := 1; x:=1; Dein Tokenizer stösst auf das x Es muss sich also um einen Bezeichner handeln. Jetzt muss nur noch geprüft werden, wie lang dieser ist. Enden wird er an einem Trennzeichen, wobei es nun keine Rolle spielt, ob dieses Trennzeichen wieder der Beginn eines anderen Tokentyps sein kann (in diesem Falle : ) oder ein Whitespace-Zeichen ist (i.d.R. Tabulator, Leerzeichen). Wurde der vollständige Bezeichner ermittelt, muss der Tokenizer nun alle Whitespace-Zeichen überspringen, bis er zu dem nächsten kommt, aus dem er ein Token machen kann, oder das den Ablauf steuert. Hierbei wird er also auf ein : stossen und muss nun prüfen ob das alles ist, oder ob noch unmittelbar ein = folgt. Es könnte ja auch das Trennzeichen für ein Case-Label sein. Folgt ein = wurde ein := erkannt, und kann in die Tokenliste aufgenommen werden. usw. |
Re: Wie am besten Parsen?
Das würde aber meine Idee mit der Stringliste (den ganzen Code reinladen und bei Leerzeichen aufteilen) so ziemlich zunichte machen. Ich müsste alles per Hand trennen (also jedes mal einzeln prüfen, ob das zum ersten Token gehört, oder ob das Zeichen ein Begrenzer oder ein neues Token sein kann, was wahrscheinlich extrem auf die Performence gehen kann, wie ich fast schon fürchte :?
Oder geht das vielleicht noch einfacher? Wenn nicht, dann werd ichs aber wahrscheinlich auf die "langsame" Methode machen... ;) |
Re: Wie am besten Parsen?
Moin Malo,
ich kann Dich beruhigen, dass geht mit Sicherheit nicht auf die Performance. Bei dem beschriebenen Verfahren braucht mein Programm ca. 1 sek. / MB Sourcecode (C-Header). [EDIT] ausserdem klingt es schwieriger als es ist. [/EDIT] |
Re: Wie am besten Parsen?
Schleifen und If Abfragen gehören dazu, kommst eh nicht drumrum.
|
Re: Wie am besten Parsen?
Zitat:
|
Re: Wie am besten Parsen?
Zitat:
|
Re: Wie am besten Parsen?
Zitat:
|
Re: Wie am besten Parsen?
Moin Malo,
das lässt sich bestens mit case lösen. (und einer while-Schleife, da man die Schleifenvariable u.U. manipulieren muss) |
Re: Wie am besten Parsen?
Zitat:
|
Re: Wie am besten Parsen?
Zitat:
for(;Abbruchbedingung;) {} ... |
Re: Wie am besten Parsen?
Moin Malo,
Zitat:
Es würde mich wundern, wenn nicht ;-) |
Re: Wie am besten Parsen?
Zitat:
|
Re: Wie am besten Parsen?
Liste der Anhänge anzeigen (Anzahl: 1)
Hi Malo,
vor kurzem haben wir im Informatikunterricht Parser behandelt, und sogar einen Parser für ein (sehr stark vereinfachtes) Pascal geschrieben. Besonders wichtig ist, zuerst eine klare Struktur zu haben (kann man schön in Syntaxdiagrammen darstellen). Wenn du willst, kann ich dir den vereinfachten Parser ja mal schicken, vielleicht hilft dir das was! Cu, Chris [EDIT] habs mal angefügt [/EDIT] |
Re: Wie am besten Parsen?
Da es hier in dem Thread ja um u.a. Tokenizer geht, muss ich gleich mal was fragen :stupid:
Bisher hab ich meinen Tokenizer soweit, dass er mir alles schön zerlegt: Klammern, Zahlen und natürlich auch Rechenzeichen. Das Problem ist nur: Das Minus ist ja sowohl Rechenzeichen als auch ein Vorzeichen. Wie mach ich meinem Tokenizer klar, ob ein Minus als Vorzeichen oder als Rechenzeichen zu sehen ist? :-? |
Re: Wie am besten Parsen?
Zitat:
Code:
ist das selbe wie
5-3=2
Code:
. Daher würde ich einfach ein Minus immer als Vorzeichen interpretieren. Dann soll der Parser nachher erkennen, dass, wenn zwei Zahlen nacheinander ohne Rechenzeichen (dafür aber einmal mit Vorzeichen) kommen, addiert werden sollen. ;)
5 + (-3) = 2
Nur meine Idee ;) |
Re: Wie am besten Parsen?
Gar nicht. Das ist Sache des Parser, der Tokenizer zerlegt nur ;)
|
Re: Wie am besten Parsen?
Zitat:
@Dax: Okay :lol: Der Tokenizer soll das '-' also immer als ein Token ansehen? |
Re: Wie am besten Parsen?
Es sei denn, du willst auch strings parsen, dann sollte der Tokenizer das - in einem string nicht erkennen. Aber generell für Matheparser: ja.
|
Re: Wie am besten Parsen?
Nein, Strings will ich nicht, ich will nur nen Matheparser ;)
Wie isses bei der Multiplikation, Division und (noch schlimmer :lol:) den Potenzen? Wie lass ich das den Parser richtig auswerten, wo jetzt ein Minuszeichen dazugehört? |
Re: Wie am besten Parsen?
Ich misch mich mal ein: Der Tokenizer zerlegt den Inputtext (eine Folge von Zeichen) in eine Folge von 'Token', damit des der Parser einfacher hat. Aus dem Wort 'begin' wird der Token BEGIN, aus dem String 'ein String mit begin' wird der Token STRING (mit einem Verweis auf den String-inhalt), aus einem FooBar wird ein IDENTIFIER (mit dem Verweis auf den Namen 'FooBar') und aus dem Zeichen '-' wird das Token MINUS. Dabei gibt es schon ein paar Fallen. Was soll der Tokenizer z.B. aus ':=' machen? Zwei Token, nämlich COLON und EQUAL, oder ein Token, 'ASSIGNMENT'. Was ist '..', DECIMALPOINT DECIMALPOINT oder DOTDOT (oder was auch immer). Hier kann man z.B. vor den Tokenizer noch einen Präprozessor knallen, der mit einem Lookaheadbuffer das nächste Zeichen prüft und ggf. Metazeichen (z.B. #255, statt :=) an den Tokenizer übermittelt, damit der eben bei jeden 'Zeichen' weiss, woran er gerade ist. Zu kompliziert? Na, ein guter Tokenizer ist ja auch nicht so mal eben geschrieben.
Ein Tokenizer und Parser sollte Jeder ernsthafte Programmierer mal implementiert haben. Meine Meinung. Eine Möglichkeit, die Punkt-Vor-Strichrechnung zu implementieren, ist die, die Grammatik erstmal zu formulieren und diese dann in Funktionen zu packen, z.B. so:
Code:
Das kann man z.B. so programmieren:
Term ::= <Ausdruck> [<StrichOp> <Term>] /* Das in [] angegeben kann, muss aber nicht angegeben werden
StrichOp ::= '+' | '-' Ausdruck ::= <SimpleTerm> [ <MulOp> <Ausdruck> ] MulOp ::= '*' | '/' SimpleTerm ::= '(' <Term> ')' | <Number> Number ::= [ <Sign> ] <Digits> <Sign> ::= [<PlusOrMinus> [<Sign>]] PlusOrMinus ::= '+' | '-'
Delphi-Quellcode:
Man sollte sich aber mit formalen Grammatiken (z.B. in Backus-Naur Form) auskennen. Das wichtige an der Grammatik ist, das man zu jeden Zeitpunkt anhand des nächsten Token entscheiden kann, wohin die Reise geht. Bei einem mathematischen Ausdruck geht das noch, aber bei einer Programmiersprache wird das schon schwieriger...
Function IsTerm : Boolean;
Begin If IsAusdruck Then Begin If IsStrichOp then If IsTerm Then Result := True else Error 'Term erwartet' end else Error 'Ausdruck erwartet' End; Function IsAusdruck : Boolean; Begin If IsSimpleTerm Then Begin If IsMulOp then If IsAusdruck Then Result := True else Error 'Ausdruck erwartet' end else Error 'SimpleTerm erwartet' End; Function IsStrichOp : Boolean; Begin If (CurrentToken = PLUS) Or (CurrentToken = MINUS) Then Begin AdvanceToNextToken; Result := True; End Else Result := False; End; ... Viel Spass! |
Alle Zeitangaben in WEZ +1. Es ist jetzt 16:48 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz