Interpreterbau - Ein Anfang



  • 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...



  • Dummie-Off schrieb:

    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/

    Ok. Danke für den hinweis. 🙂

    Dummie-Off schrieb:

    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...

    Hm.. joar vielleicht. Ich glaube aber es soll so sein muss ich mal mein Ausbilder fragen. Der gibt mir meistens vor wie ich es machen soll. Ob eine oder mehrere. Trotzdem danke nochmal. 🙂

    P.S.: Dein Artikel ist btw ziemlich gut mag ich. 🙂 Gut zum lernen geeignet.



  • Hey ich noch mal!
    Ehm ... also irgendwie versteh ich nicht warum er bei mir bei einem String immer abbricht. Also nach dem er ein String Token gemacht hat hört er auf. Liegt das am "?

    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(isprint(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);
    };
    
    std::string input = "{ :(); classa {public:   function hello(){    console.log(""hello"");  }};class b {public:  function bye(){     console.log(""Bye"");  }};class c : public a,b{public:  function greet(){    console.log(""greeting"");  }}";
    
    Eingabe: "{ :(); classa {public:   function hello(){    console.log(hello);  }};class b {public:  function bye(){     console.log(Bye);  }};class c : public a,b{public:  function greet(){    console.log(greeting);  }}"
    
    TT_RBRACE = 0
    TT_COLON = 0
    TT_RBRACKET = 0
    TT_LBRACKET = 0
    TT_SEMI = 0
    TT_STRING = 0
    


  • Überleg' mal was die while-Schleife macht....



  • Th69 schrieb:

    Überleg' mal was die while-Schleife macht....

    while(isprint(myCh)){ 
        buf += myCh; 
        readNextChar(); 
          }
    

    Ehm...
    Solange myCh ein druckbarer char ist (frage ist ob der das nun nur für die cases oben macht oder ob für ihn alle druckbar sind?)
    myCh in wird an buf ran gehangen
    nächsten char lesen



  • Anstatt isprint musst du isalpha verwenden, denn isprint sieht auch all die anderen Zeichen als druckbar an.

    http://www.cplusplus.com/reference/clibrary/cctype/isalpha/

    Zur Not musst du auch einfach selber mal nach der Funktion im Internet suchen und dir die Dokumentation dieser ansehen.
    Oft sind auch verwandete Funktionen dort beschrieben, die dann ggf. besser geeignet sind.

    Und noch was:
    Warum benennst du "(" als RBRACKET und ")" als LBRACKET (selbiges gilt für BRACE)?

    Eigentlich wäre es genau andersrum sinnvoller, denn das R steht für Right und das L für Left.



  • Dummie schrieb:

    Anstatt isprint musst du isalpha verwenden, denn isprint sieht auch all die anderen Zeichen als druckbar an.

    Und noch was:
    Warum benennst du "(" als RBRACKET und ")" als LBRACKET (selbiges gilt für BRACE)?

    Eigentlich wäre es genau andersrum sinnvoller, denn das R steht für Right und das L für Left.

    Öhm ... ich hab mir gedacht "(" ist nach rechts geöffnet also RBRACKET. 🙂

    EDIT: Ach so und warum muss ich den constructor löschen wenn ich was an dem string = input ändere? Wenn ich wieder was ändere muss ich ihn wieder rein machen. Versteh ich nicht.

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


  • Ich hatte es eben von der Perspektive gesehen, dass sie eben links steht (sind ja immer Klammerpaare). Das finde ich auch beim Schreiben bzw. Lesen schneller verstanden. Denn bei deiner Variante muss ich mir erst vorstellen, wie denn "(" aussieht und entsprechend entscheiden, ob ich L oder R meine.

    Kannst du letztendlich aber eh so halten, wie du damit am besten arbeiten kannst.


Anmelden zum Antworten