Parser - Wie Code intern speichern



  • Mein aktuelles Projekt ist ein Compiler der eine eigene kleine Sprache
    nach C++ übersetzen soll. Allerdings ist die Sprache einerseits schon
    zu komplex um sie in einem Zug direkt übersetzen zu können (also mit
    einem One-Pass-Compiler) und andererseits möchte ich schon die Gelegenheit
    nutzen etwas tiefer in den Compilerbau einzusteigen.

    Soweit so gut, das Frontend ist eigentlich fertig, das heißt der Input der
    Dateien und das Scannen der Tokens funktioniert tadellos.

    Die Tokens sind der Einfachheit halber wie folgt gespeichert:

    struct Token {
      TokenID ID;
      string Value;
    };
    //...
    vector<Token> tokens;
    

    Wobei TokenID ein simples enum für allerlei Typen ist (Klammer auf, Semicolon, Identifier...)

    Nun zu meiner eigentlichen Frage, ich möchte aus diesem Tokenstream eine
    Art interne Repräsentation der Codelogik aufbauen. Ich habe allerdings
    keine Ahnung wie ich das anfangen soll, nicht einmal wie diese Struktur
    aussehen soll. Wie würdet ihr das machen, und wie soll das Parsen von statten gehen?

    Danke schon mal fürs lesen, ich freu mich auf eure Antworten...

    PS: Wenn ihr weitere Details braucht -> Bitte einfach fragen



  • Selber kenne ich mich in dem Bereich nicht gut aus, aber vielleicht hilft dir der Artikel über Compilerbau etwas weiter.



  • Unter Linux gibt es 2 Tools, die Hand in Hand arbeiten: Flex und Bison bzw. Lex und Yacc. Auf den zugehoerigen Seiten findest du auch ausfuehrliche Tutorials, wie ein kleiner Taschenrechner zu implementieren ist. Aufbauend auf diesen Erkenntnissen kannst du dann eine eigene Sprache entwerfen.

    Auch die Paper bezueglich Lisp/Scheme etc. sind vielleicht hilfreich.



  • Nexus schrieb:

    Selber kenne ich mich in dem Bereich nicht gut aus, aber vielleicht hilft dir der Artikel über Compilerbau etwas weiter.

    Siehe Punkt 12 in dem Artikel - allerdings hilft mir der Begriff "Parserbaum"
    schon etwas weiter. Gleich mal googlen...

    @knivil
    Ich möchte bewusst kein Tool dafür verwenden, sondern alles per Hand schreiben.



  • Die Tools erzeugen c-Sourcedateien, die keinerlei externe Abhaengigkeiten aufweisen. Und du wirst noch genug selbst machen muessen. Natuerlich kannst du dich auch damit aufhalten, einen Matcher fuer regulaere Ausdruecke zu schreiben (flex) und einen Parser fuer Ableitungsbaeume (bison) zu entwickeln. Vielleicht solltest du dir trotzdem flex/bison ansehen, damit du begreifst, was im Hintergrund so abgeht.



  • Abstrake Syntax- und Semantikbäume sind noch eventuelle Stichpunkte die dir weiterhelfen können.



  • Also, ich hab jetzt mal ne Weile nach diesen Begriffen gegoogelt und glaube
    dass ich jetzt eine grobe Vorstellung von dem habe was noch auf mich zukommt.
    Ich denke ich werd jetzt einfach mal ein bisschen rumprobieren, vielleicht
    kommt ja was dabei raus. Wenn ich noch spezielle Fragen hab, meld ich mich wieder.



  • milan1612 schrieb:

    Also, ich hab jetzt mal ne Weile nach diesen Begriffen gegoogelt und glaube
    dass ich jetzt eine grobe Vorstellung von dem habe was noch auf mich zukommt.
    Ich denke ich werd jetzt einfach mal ein bisschen rumprobieren, vielleicht
    kommt ja was dabei raus. Wenn ich noch spezielle Fragen hab, meld ich mich wieder.

    Entwirf das doch zuerst einmal mit Hilfe von lex/yacc und anschließend von Hand. Nicht gleich alles auf einmal versuchen! 🙂



  • milan1612 schrieb:

    Nun zu meiner eigentlichen Frage, ich möchte aus diesem Tokenstream eine
    Art interne Repräsentation der Codelogik aufbauen. Ich habe allerdings
    keine Ahnung wie ich das anfangen soll, nicht einmal wie diese Struktur
    aussehen soll.

    Häufig kann man Code in zwei Dinge aufteilen: Die Struktur (Funktionen, Klassen, ...) und Ausdrücke (sowas wie i*2+7 ). Eine Datei besteht dann z.B. aus einer Reihe Funktionen (mit jeweils Rückgabetyp, Name, Parameter-Namen und -Typen und eventuellen Modifizierern wie z.B. "static"), Klassen (mit Name, Membervariablen, Memberfunktionen, evtl Operatoren, Konstruktoren usw) und anderen "Strukturen".
    An "Code" hast du meist den Code in Funktionen, welcher sich in Codezeilen wie return-Anweisungen, Schleifen, if-Verzweigungen, Variablen-Deklarationen etc und Ausdrücken ausdrücken lässt (z.B. eine Codezeile Variablendeklaration mit Typ=int, Name=foo, Init-Ausdruck=5+7 oder eine while-Schleife mit dem Ausdruck b>7 als Bedingung). Dann gibt's noch "Code" außerhalb von Funktionen, z.B. bei dem Initialisierungs-Code von globalen Variablen-Deklarationen.

    Ausdrücke werden dabei meist mit "Abstract Syntax Tree"s repräsentiert, der Rest mit Property-Sammel-Klassen wie deine Token-Struktur.

    So könnte das in etwa aussehen:

    File
      -> Function[] funcs
      -> VarDecl[]  vars
    
    Function
      -> string     name
      -> Type       return_type
      -> Param[]    params
      -> CodeLine[] codelines
    
    Param
      -> Type       type
      -> string     name
    
    VarDecl
      -> Type      type
      -> string    name
      -> Expression* init_code // Optional falls Initialisierung vorhanden
    
    CodeLine : CodeLineReturn | CodeLineWhile | CodeLineIf | CodeLineVarDecl | ...
    
    CodeLineReturn
      -> Expression* expr  // Optional, falls ein "leeres" return möglich ist
    
    CodeLineVarDecl
      -> VarDecl[] vars
    
    CodeLineWhile
      -> Expression condition
      -> CodeLine[] code_block
    ...
    
    Expression : ExprBinaryOperator | ExprUnaryOperator | ExprStatement | ...
    
    ExprBinaryOperator
      -> Operator op
      -> Expression left
      -> Expression right
    
    ExprStatement
      -> string text
    
    ...
    

Anmelden zum Antworten