Interpreterbau - Ein Anfang



  • leiderC++ schrieb:

    iJake1111 schrieb:

    Das ist wohl eines der Besten C Tutorials, das ich je gesehen hab, Respekt! 👍 Seit diesem Tut, sehe ich Parser schon mit ganz anderen Augen 😃

    Nochmal rechten Dank!
    Mfg iJake1111

    Leider ist es C++

    Ich habe die einfache Version in C portiert. Steht also ab jetzt auch zum Download bereit. Vielleicht interessiert sich ja jemand dafür. 🙂



  • Als Stichwort: Parser Combinator



  • OK. Ich habe mir das Tutorial zu Gemüte geführt und den Quelltext erstmal gedownloadet. Wenn ich diesen kompiliere, geht noch alles glatt. Wenn das Programm dann allerdings startet, kann ich auch ohne Probleme meinen String eingeben, nachdem ich 'Enter' gedrückt habe, kommt aber immer folgende Meldung: "Debug Assertion Failed! ....". Wenn ich dann auf ignorieren klicke, steht das Ergebnis auch da. Wie kriege ich also die Fehlermeldung weg?

    Schonmal danke im Voraus.



  • Ich hab mir jetzt den Quellcode nicht angesehen, aber mein erster Anhaltspunkt wäre, dass man nach einem "assert" Ausschau hält und herausfindet, warum das fehlschlägt.



  • Hey,

    probier mal in der Datei Scanner.cpp die Funktion Scanner::readNextChar() zu suchen und ersetz in dieser die Zeile

    if (myPos > myInput.length())
    

    durch diese Zeile (es kommt ein = hinzu):

    if (myPos >= myInput.length())
    

    Warum aber der Fehler erst jetzt auftritt ist mir nicht klar. Vermutlich hat es was mit der neu herausgekommenen Visual Studio Version oder 32 Bit bzw. 64 Bit Systemen zu tun. Mir wäre der Fehler (der unabhängig von der Eingabe immer auftritt) sonst definitiv aufgefallen.

    Vielen Dank für die Rückmeldung 🙂

    Edit:
    Falls jemand parallel zu neuren Versionen noch die alte Version installiert hat, wäre es wirklich sehr nett, wenn jemand überprüfen könnte, ob der Fehler dort auch auftritt und ob length() in den Versionen vielleicht unterschiedliche Werte zurückliefert oder ob in der Bibliothek einfach ein Assert dazugekommen ist.



  • * AUSGRABE*

    Eine frage kann man den download auch so bekommen das man das aus Linux ohne VS öffnen kann? 😛
    Einfach .txt reicht mir. 😃



  • Enno schrieb:

    * AUSGRABE*

    Eine frage kann man den download auch so bekommen das man das aus Linux ohne VS öffnen kann? 😛
    Einfach .txt reicht mir. 😃

    Der Quellcode in C bzw. C++ ist plattformunabhängig und in den Downloads natürlich ebenso enthalten.

    Also alles entpacken und in das entsprechende Verzeichnis gehen, wo die .h und .cpp Dateien liegen.

    Dann für die C++ Variante einfach: g++ -o Prog *.h *.cpp

    Und zum Starten: ./Prog

    Ansonsten bietet es sich an, dass du den Artikel einfach Schritt für Schritt bearbeitest. Es ist immer sinvoller, wenn man auch die ganzen Hintergründe versteht. 😉



  • Dummie schrieb:

    Enno schrieb:

    * AUSGRABE*

    Eine frage kann man den download auch so bekommen das man das aus Linux ohne VS öffnen kann? 😛
    Einfach .txt reicht mir. 😃

    Der Quellcode in C bzw. C++ ist plattformunabhängig und in den Downloads natürlich ebenso enthalten.

    Also alles entpacken und in das entsprechende Verzeichnis gehen, wo die .h und .cpp Dateien liegen.

    Dann für die C++ Variante einfach: g++ -o Prog *.h *.cpp

    Und zum Starten: ./Prog

    Ansonsten bietet es sich an, dass du den Artikel einfach Schritt für Schritt bearbeitest. Es ist immer sinvoller, wenn man auch die ganzen Hintergründe versteht. 😉

    Danke für deine antwort werd das gleich ausprobieren. Ich arbeite das grade durch und wollte nur mal nachgucken wo du die

    enum TokenType
    

    hingepackt hast. 🙂

    EDIT: Da kommt bei mir gleich die frage auf ob man das nicht alles in 3 Datei machen kann? muss ich das alles so auslagern?



  • Enno schrieb:

    Dummie schrieb:

    Enno schrieb:

    * AUSGRABE*

    Eine frage kann man den download auch so bekommen das man das aus Linux ohne VS öffnen kann? 😛
    Einfach .txt reicht mir. 😃

    Der Quellcode in C bzw. C++ ist plattformunabhängig und in den Downloads natürlich ebenso enthalten.

    Also alles entpacken und in das entsprechende Verzeichnis gehen, wo die .h und .cpp Dateien liegen.

    Dann für die C++ Variante einfach: g++ -o Prog *.h *.cpp

    Und zum Starten: ./Prog

    Ansonsten bietet es sich an, dass du den Artikel einfach Schritt für Schritt bearbeitest. Es ist immer sinvoller, wenn man auch die ganzen Hintergründe versteht. 😉

    Danke für deine antwort werd das gleich ausprobieren. Ich arbeite das grade durch und wollte nur mal nachgucken wo du die

    enum TokenType
    

    hingepackt hast. 🙂

    EDIT: Da kommt bei mir gleich die frage auf ob man das nicht alles in 3 Datei machen kann? muss ich das alles so auslagern?

    Wie für einen Artikel oder andere Erklärungen üblich ist der Quellcode in erster Linie darauf ausgerichtet, dass er sich gut vermitteln lässt.

    Er ist daher auf gar keinen Fall perfekt oder endgültig.

    Stattdessen dient er mehr als Anschauungsmaterial zum Ausprobieren und natürlich zur Orientierung (wofür du ihn ja z.B. auch gebraucht hast).

    Wenn du also der Meinung bist, dass es anders besser wäre, dann kann das gut sein, dass du damit richtig liegst.
    Da Software aber aus viel mehr Dingen besteht, kann das niemand "mal eben" beantworten.

    Da hilft es nur es auszuprobieren und eigene Erfahrungen zu sammeln oder sehr konkrete Fragen zu stellen.



  • Irgendwie versteh ich diesen Fehler nicht:

    Fehler: »TokenType« bezeichnet keinen Typ
    

    Eigentlich ist der doch durch

    enum
    

    bezeichnet? Warum meckert mein Compiler dann hier:

    private:
      TokenType myType;
    

    Bin bei dem Teil wo man zwischen durch mal was Testen kann. Also den Scanner.

    Dummie schrieb:

    An dieser Stelle ist der Scanner fertig. Er kann in einem Testlauf sehr einfach getestet werden:

    #include <iostream> 
    #include <string> 
    #include "Scanner.h" 
    
    int main() 
    { 
    std::string input = "10 + 5\n * \t10-\n5\t"; 
    
    std::cout << "Eingabe: \"" << input << "\"\n" << std::endl; 
    
    Scanner scanner(input); 
    
    Token tok = scanner.getNextToken(); 
    
    while (!tok.equal(TT_NIL)) 
    { 
    std::cout << tok.toString() << " = " << tok.getValue() << std::endl; 
    
    tok = scanner.getNextToken(); 
    } 
    }
    

    Bitte um Aufklärung. Danke. 🙂



  • Ist TokenType denn bekannt? Z.B. vor Token deklariert oder durch Inkludierung eingebunden?

    Der Codeausschnitt enthält nicht den Teil, der den Fehler enthält... Man kann dir sehr schlecht helfen.

    Oder hast du den Code aus der Zip genommen und dort kommt der Fehler?

    Welchen Compiler nutzt du denn?



  • Dummie-Off schrieb:

    Ist TokenType denn bekannt? Z.B. vor Token deklariert oder durch Inkludierung eingebunden?

    enum TokenTyp{                           // token definition
      TT_RBRACKET,
      TT_LBRACKET,
      TT_RBRACE,
      TT_LBRACE,
      TT_COMMA,
      TT_SEMI,
      TT_COLON,
      TT_DOT,
      TT_STRING,
      TT_NIL
    };
    
    static const char *TokenTypStr[] = {     // for error message
      "TT_RBRACKET",
      "TT_LBRACKET",
      "TT_RBRACE",
      "TT_LBRACE",
      "TT_COMMA",
      "TT_SEMI",
      "TT_COLON",
      "TT_DOT",
      "TT_STRING",
      "TT_NIL"
    };13 14 15 16 17
    
    class Token{
    private:
      TokenType myType;
      int myValue;
      std::string TokenTypeStr;
    public:
      Token(TokenType type = TT_NIL, int value = 0);
      TokenType getType() const { return myType; }
      int getvalue() const { return myValue; }
      const char *toString() const { return TokenTypeStr[myType]; }  // error message
      bool equal(TokenType) const { return myType == type; }         //comparison
    };
    

    Dummie-Off schrieb:

    Der Codeausschnitt enthält nicht den Teil, der den Fehler enthält... Man kann dir sehr schlecht helfen.

    Doch genau der Fehler kommt zu dem Code teil. Der untere von dir Zitierte Code ist nur dafür da das man weiß wo ich in deinem Artikel grade am arbeiten bin.

    Fehler:

    Fehler: »TokenType« bezeichnet keinen Typ
    

    Code:

    private:
      TokenType myType;
    

    Dummie-Off schrieb:

    Oder hast du den Code aus der Zip genommen und dort kommt der Fehler?

    Nein. Hab es selbst noch einmal geschrieben allerdings nur in 3 Datei. Hab nicht alles ausgelagert.

    Dummie-Off schrieb:

    Welchen Compiler nutzt du denn?

    Einfach unter Linux/Debian mit CMake und dann make.



  • TokenTyp vs TokenType (mit e am Ende)



  • Dummie-Off schrieb:

    TokenTyp vs TokenType (mit e am Ende)

    Oh man. Ich hasse mich selbst. xD
    Danke. 🙂



  • Hmmm ...
    Jetzt bekomme ich noch diesen Fehler hier:

    Fehler: »type« wurde in diesem Gültigkeitsbereich nicht definiert
    

    Irgendwie kommt da doch jetzt noch irgendwas nicht an.

    EDIT: Hab eine Sache bemerkt ich bin ein dummes Kind. Das bleibt.^^
    EDIT2: Hab es. 😃 Selbstdialog ftw. 🙂
    EDIT3: Tja nun gib das hier:

    oopjs.cpp:27: undefined reference to `Token::Token(TokenType, int)'
    oopjs.cpp:27: undefined reference to `Token::Token(TokenType, int)'
    oopjs.cpp:30: undefined reference to `Token::Token(TokenType, int)'
    oopjs.cpp:33: undefined reference to `Token::Token(TokenType, int)'
    oopjs.cpp:36: undefined reference to `Token::Token(TokenType, int)'
    oopjs.cpp:39: undefined reference to `Token::Token(TokenType, int)'
    oopjs.cpp:42: more undefined references to `Token::Token(TokenType, int)'
    

    Den Fehler bekomme ich auch wenn ich deine Version nehme.

    enum TokenType{                           // token definition
      TT_RBRACKET,
      TT_LBRACKET,
      TT_RBRACE,
      TT_LBRACE,
      TT_COMMA,
      TT_SEMI,
      TT_COLON,
      TT_DOT,
      TT_STRING,
      TT_NIL
    };
    
    static const char *TokenTypStr[] = {     // for error message
      "TT_RBRACKET",
      "TT_LBRACKET",
      "TT_RBRACE",
      "TT_LBRACE",
      "TT_COMMA",
      "TT_SEMI",
      "TT_COLON",
      "TT_DOT",
      "TT_STRING",
      "TT_NIL"
    };
    
    class Token{
    private:
      TokenType myType;
      int myValue;
    public:
      Token(TokenType type = TT_NIL, int value = 0); // HIER GEHT ES LOS!
      TokenType getType() const { return myType; }
      int getValue() const { return myValue; }
      const char *toString() const { return TokenTypStr[myType]; }  // error message
      bool equal(TokenType type) const { return myType == type; }         //comparison
    };
    
    class Scanner{
    public:
      Scanner(const std::string& input);
      Token getNextToken(); //HIER GEHT ES WEITER
    private:
      std::string myInput;
      unsigned int myPos;                                            //Position at input
      char myCh;                                                     //last char
    private:
      void skipSpaces();
      void readNextChar();
    };
    
    Token Scanner::getNextToken(){
      std::string buf;
      skipSpaces();                          //to skip chars which are not used
      switch(myCh){
        case '(':
          readNextChar();
          return Token(TT_RBRACKET);// HIER FINDET ER ES NICHT
        case ')':
          readNextChar();
          return Token(TT_LBRACKET);// HIER FINDET ER ES NICHT
        case '{':
          readNextChar();
          return Token(TT_RBRACE);// HIER FINDET ER ES NICHT
        case '}':
          readNextChar();
          return Token(TT_LBRACE);// HIER FINDET ER ES NICHT
        case ',':
          readNextChar();
          return Token(TT_COMMA);// HIER FINDET ER ES NICHT
        case ';':
          readNextChar();
          return Token(TT_SEMI);// HIER FINDET ER ES NICHT
        case ':':
          readNextChar();
          return Token(TT_COLON);//HIER WIRD NICHT GEMECKERT!!!!
        case '.':
          readNextChar();
          return Token(TT_DOT);//HIER WIRD NICHT GEMECKERT!!!!
                                            //read as long its a letter or "
        case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '"':
          while(isdigit(myCh)){
    	buf += myCh;
    	readNextChar();
          }
          return Token(TT_STRING);
        default:
          if(myCh != 0){
    	std::cerr << "Error: not used Char '" << myCh << "'" << std::endl;
          }
          break;
      } 
      return Token(TT_NIL);
    }
    


  • Okay, du hast den Sourcecode runtergeladen und er gibt dir also die gleiche Fehlermeldung aus? Eigentlich hatte ich alles vorher auch erfolgreich unter Linux getestet bevor ich es hochgeladen habe.

    Inkludierst du auch Token.h? Gibst du dem Compiler alle notwendigen .cpp und .h Dateien mit?

    Hast du überhaupt einen Konstruktor implementiert?

    Token::Token(TokenType type, int value) 
        : myType(type), myValue(value)
    {
     // ...
    }
    

    Was für einen Compiler/IDE nutzt du überhaupt?



  • Dummie schrieb:

    Inkludierst du auch Token.h? Gibst du dem Compiler alle notwendigen .cpp und .h Dateien mit?

    Ja alles richtig.

    Dummie schrieb:

    Hast du überhaupt einen Konstruktor implementiert?

    Token::Token(TokenType type, int value) 
        : myType(type), myValue(value)
    {
     // ...
    }
    

    Nein. das wars....

    Dummie schrieb:

    Was für einen Compiler/IDE nutzt du überhaupt?

    Ehm... ich schreibe in Kate. Und bau den kram mit CMake im build Ordner mit make. Sag dir das was? 🙂
    Danke.

    Achso hier hab es alles in 3 Dateien:
    .h:

    #ifndef OOPJS_H
    #define OOPJS_H
    
    #include <iostream> 
    #include <string> 
    
    enum TokenType{                           // token definition
      TT_RBRACKET,
      TT_LBRACKET,
      TT_RBRACE,
      TT_LBRACE,
      TT_COMMA,
      TT_SEMI,
      TT_COLON,
      TT_DOT,
      TT_STRING,
      TT_NIL
    };
    
    static const char *TokenTypStr[] = {     // for error message
      "TT_RBRACKET",
      "TT_LBRACKET",
      "TT_RBRACE",
      "TT_LBRACE",
      "TT_COMMA",
      "TT_SEMI",
      "TT_COLON",
      "TT_DOT",
      "TT_STRING",
      "TT_NIL"
    };
    
    class Token{
    private:
      TokenType myType;
      int myValue;
    public:
      Token(TokenType type = TT_NIL, int value = 0);
      TokenType getType() const { return myType; }
      int getValue() const { return myValue; }
      const char *toString() const { return TokenTypStr[myType]; }  // error message
      bool equal(TokenType type) const { return myType == type; }         //comparison
    };
    
    class Scanner{
    public:
      Scanner(const std::string& input);
      Token getNextToken();
    private:
      std::string myInput;
      unsigned int myPos;                                            //Position at input
      char myCh;                                                     //last char
    private:
      void skipSpaces();
      void readNextChar();
    };
    
    Token::Token(TokenType type, int value) : myType(type), myValue(value){
    }
    /*
    class Parser{
    public:
      Parser(const std::string& input):
      int parse();
    private:
      Scanner myScanner;
      Token myTok;
    private:
      int start();
      void accept(TokenType);
      void getNextToken();
    };
    */
    
    #endif
    

    .cpp:

    #include "oopjs.h"
    
    void Scanner::readNextChar(){
      if(myPos > myInput.length()){
        myCh = 0;
        return;
      }
      myCh = myInput[myPos++];
    }
    
    void Scanner::skipSpaces(){
      while(myCh == ' ' || myCh == '\t' || myCh == '\r' || myCh == '\n'){
        readNextChar();
      }
    }
    
    Scanner::Scanner(const std::string& input) : myInput(input), myPos(0){
      readNextChar();
    }
    
    Token Scanner::getNextToken(){
      std::string buf;
      skipSpaces();                          //to skip chars which are not used
      switch(myCh){
        case '(':
          readNextChar();
          return Token(TT_RBRACKET);
        case ')':
          readNextChar();
          return Token(TT_LBRACKET);
        case '{':
          readNextChar();
          return Token(TT_RBRACE);
        case '}':
          readNextChar();
          return Token(TT_LBRACE);
        case ',':
          readNextChar();
          return Token(TT_COMMA);
        case ';':
          readNextChar();
          return Token(TT_SEMI);
        case ':':
          readNextChar();
          return Token(TT_COLON);
        case '.':
          readNextChar();
          return Token(TT_DOT);
                                            //read as long its a letter or "
        case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '"':
          while(isdigit(myCh)){
    	buf += myCh;
    	readNextChar();
          }
          return Token(TT_STRING);
        default:
          if(myCh != 0){
    	std::cerr << "Error: not used Char '" << myCh << "'" << std::endl;
          }
          break;
      } 
      return Token(TT_NIL);
    }
    /*
    void Parser::getNextToken(){
      myTok = myScanner.getNextToken();
    }
    
    void Parser::accept(){
      if(!myTok.equl(type)){
        std::cerr << "Error: unexpected Token " << myTok.toString() << ", " << TokenTypeStr[type] << " expected" << std::edl;
      }
      getNextToken();
    }
    Parser::Parser(const std::string& input) : myScanner(input){
      getNextToken();
    }
    
    int Parser::parse(){
      int res = start();
      accept(TT_NIL);
      return res;
    }
    
    void Parser::start(){
    
    }
    */
    

    main:

    #include <iostream> 
    #include <string> 
    #include "oopjs.h" 
    
    int main(){ 
      std::string input = "class";
      std::cout << "Eingabe: \"" << input << "\"\n" << std::endl; 
      Scanner scanner(input); 
      Token tok = scanner.getNextToken(); 
      while (!tok.equal(TT_NIL)){ 
        std::cout << tok.toString() << " = " << tok.getValue() << std::endl; 
        tok = scanner.getNextToken(); 
      } 
    }
    


  • Funktioniert es denn nun korrekt?

    Ich würde für Bezeichner und Strings eigene Token einführen.

    Und für reservierte Keywords wie class, if, else... würde ich auch eigene Token einführen. Wenn du also einen Bezeichner eingelesen hast, kannst du prüfen, ob es evtl. ein Keyword ist...

    Also in etwa so:

    case 'a': case 'b': case 'c': ....... case 'z':
    // Weiter einlesen....
    
    if (value == "else")
     return Token(TT_ELSE);
    else if (value == "if")
     return Token(TT_IF);
    
    return Token(TT_IDENT, value );
    

    Edit: Und deine Codeaufteilung ist so leider nicht wirklich besser. Wenn du unbedingt nur eine Datei inkludieren willst, dann mach dir eben eine Datei oopjs.h die einfach alles inkludiert, was du immer brauchst.



  • Dummie schrieb:

    Funktioniert es denn nun korrekt?

    Nein ist eine endlos Schleife. 😞 Kommt immer TT_STRING = 0.

    Dummie schrieb:

    Ich würde für Bezeichner und Strings eigene Token einführen.

    Ja klingt gut. 🙂

    Dummie schrieb:

    Und für reservierte Keywords wie class, if, else... würde ich auch eigene Token einführen. Wenn du also einen Bezeichner eingelesen hast, kannst du prüfen, ob es evtl. ein Keyword ist...

    Also in etwa so:

    case 'a': case 'b': case 'c': ....... case 'z':
    // Weiter einlesen....
    
    if (value == "else")
     return Token(TT_ELSE);
    else if (value == "if")
     return Token(TT_IF);
    
    return Token(TT_IDENT, value );
    

    Ok vielen Dank. Sowas hatte ich vor. 🙂

    Dummie schrieb:

    Edit: Und deine Codeaufteilung ist so leider nicht wirklich besser. Wenn du unbedingt nur eine Datei inkludieren willst, dann mach dir eben eine Datei oopjs.h die einfach alles inkludiert, was du immer brauchst.

    Ehm ... hab ich das nicht? Hab doch eine oopjs.h die alles inkludiert? Oder versteh ich dich falsch?
    Danke.



  • Ja, es ist natürlich eine Endlosschleife, weil du isdigit nutzt. Das fragt ab, ob es eine Zahl ist. Das ist bei "class" natürlich immer false.

    Stattdessen musst du isprint verwenden, wenn du Buchstaben willst:
    http://www.cplusplus.com/reference/clibrary/cctype/isprint/

    Deine Codegliederung ist insofern schlecht, weil du Scanner und Parser und Token alles in eine Datei packen willst. Stattdessen bietet es sich an, wenn du diese Dinge trennst.

    Scanner.h
    Parser.h
    Token.h (evtl. sogar TokenType.h)

    Das wird sonst sehr unübersichtlich...


Anmelden zum Antworten