Benutzer-Werkzeuge

Webseiten-Werkzeuge


compilerbau:lexer:start

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen RevisionVorhergehende Überarbeitung
Nächste Überarbeitung
Vorhergehende Überarbeitung
compilerbau:lexer:start [2021/10/28 20:11] Martin Pabstcompilerbau:lexer:start [2021/12/29 11:29] (aktuell) – Externe Bearbeitung 127.0.0.1
Zeile 1: Zeile 1:
 ====== Lexer ====== ====== Lexer ======
 +
 +<WRAP center round tip 60%>
 +Im folgenden findest Du die Beschreibung des Lexers. Den [[.test:start|fertigen Lexer zusammen mit einem direkt im Browser ausführbaren Testprogramm findest Du hier]].
 +</WRAP>
 +
  
 Der Lexer liest den Programmtext Zeichen für Zeichen und fasst diese zu kleinsten syntaktischen Einheiten, sogenannten Tokens, zusammen. Unsere Beispielsprache besteht aus folgenden 8 Tokens: Der Lexer liest den Programmtext Zeichen für Zeichen und fasst diese zu kleinsten syntaktischen Einheiten, sogenannten Tokens, zusammen. Unsere Beispielsprache besteht aus folgenden 8 Tokens:
Zeile 39: Zeile 44:
 Da wir die TokenTypen der Einfachheit halber auch als Typen der Knoten des vom Parser generierten Syntaxbaums verwenden, gibt es einen zusätzlichen Typ ''negation''. Dazu später mehr. Da wir die TokenTypen der Einfachheit halber auch als Typen der Knoten des vom Parser generierten Syntaxbaums verwenden, gibt es einen zusätzlichen Typ ''negation''. Dazu später mehr.
  
 +===== Die Klasse Token =====
 +
 +Damit der Parser die Tokens nachher weiterverarbeiten kann, muss zu jedem Token sein Typ (Zahl, plus, text, ...) sowie - im Falle von Zahlen und Variablen - auch die konkrete Zahl bzw. die konkrete Variable gespeichert werden. Ein Token wird daher als Objekt der Klasse ''Token'' abgebildet:
 +
 +<code java>
 +public class Token {
 + 
 +  private TokenType tokenType; // Art des Tokens (z.B. zahl, klammerAuf usw.)
 +  private double zahl; // Wird nur belegt, wenn tokenType == zahl
 +  private String text; // Wird nur belegt, falls tokenType == bezeichner
 + 
 +  public Token(double zahl) {
 +    this.zahl = zahl;
 +    tokenType = TokenType.zahl;
 +  }
 + 
 +  public Token(String text) {
 +    this.text = text;
 +    tokenType = TokenType.text;
 +  }
 + 
 +  public Token(TokenType tokenType) {
 +    this.tokenType = tokenType;
 +  }
 + 
 +  public TokenType getTokenType() {
 +    return tokenType;
 +  }
 + 
 +  public double getZahl() {
 +    return zahl;
 +  }
 + 
 +  public String getText() {
 +    return text;
 +  }
 + 
 + /**
 + * Die toString()-Methode dient nur Debuggingzwecken
 + */
 +  @Override
 +  public String toString() {
 + 
 +    String s = "" + tokenType;
 + 
 +    switch(tokenType) {
 +        case zahl : 
 +          s += "[" + zahl + "]";
 +          break;
 +        case text : 
 +          s += "[" + text + "]";
 +          break;
 +        default : 
 +          break;
 +    }
 + 
 +    return s;
 + 
 +  }
 + 
 +}
 +</code>
 +
 +
 +===== Funktionsweise des Lexers =====
 +Die Klasse Lexer bekommt im Konstruktor den Programmtext übergeben und speichert ihn im Attribut ''text''. Darauf kann nachfolgend die Methode ''peek()'' zugreifen, um den Programmtext Zeichen für Zeichen auszulesen. 
 +
 +<code java>
 +public class Lexer {
 + 
 + /**
 + * Die Zeichenkette wird von links nach rechts abgearbeitet. Hier ist
 + * gespeichert, das wievielte Zeichen gerade angesehen wird:
 + */
 +  private int position;
 + 
 + /**
 + * Liste zur Speicherung der ermittelten Tokens
 + */
 +  private ArrayList<Token> tokenListe;
 + 
 + /**
 + * Wir speichern den Programmtext in einem Attribut der Klasse, damit er
 + * nachher nicht von Methode zu Methode weitergereicht werden muss.
 + */
 +  private String text;
 + 
 + /**
 +
 + * @param text
 + *            Programmtext, der in Tokens zerlegt werden soll
 + */
 +  public Lexer(String text) {
 + 
 +    this.text = text;
 + 
 +    position = 0; // Wir beginnen mit dem Zeichen ganz links
 + 
 +    tokenListe = new ArrayList<Token>(); // Liste zur Aufnahme der gelesenen Tokens instanzieren
 + 
 +  }
 +</code>
 +
 +Die Methode ''lex()'' liest den Programmtext nun Zeichen für Zeichen und zerlegt ihn in Tokens. Zum lexen von Zahlen oder Text (erstmal: Variablenbezeichnern; später: auch Schlüsselwörter) greift sie auf die Hilfsmethoden ''lexZahl()'' und ''lexText'' zurück.
 +
 +<code java>
 + /**
 + * Zerlegt den Programmtext, der dem Konstruktor übergeben wurde
 +
 + * @return Liste mit Tokens
 + * @throws Exception
 + *             Eine Exception wird ausgelöst, wenn der Programmtext Passagen
 + *             enthält, aus denen keine Token gebildet werden können.
 + */
 +  public ArrayList<Token> lex() {
 + 
 + /**
 + * Wiederholung, die den Programmtext von links nach rechts abarbeitet:
 + */
 +    while(position < text.length()) {
 + 
 + /**
 + * peek() liest das nächste Zeichen im Programmtext, erhöht die
 + * Variable position aber nicht. Ruft man peek() mehrmals
 + * hintereinander auf, liefert es also immer dasselbe Zeichen.
 + */
 +      char c = peek();
 + 
 +      if(istZiffer(c)) {
 +        lexZahl();
 +      } else if(istBuchstabe(c)) {
 +        lexText();
 +      } else {
 +        switch(c) {
 +            case '+'
 +              addToken(TokenType.plus);
 +              break;
 +            case '-'
 +              addToken(TokenType.minus);
 +              break;
 +            case '*'
 +              addToken(TokenType.mal);
 +              break;
 +            case '/'
 +              addToken(TokenType.geteilt);
 +              break;
 +            case '('
 +              addToken(TokenType.klammerAuf);
 +              break;
 +            case ')'
 +              addToken(TokenType.klammerZu);
 +              break;
 +            case ' ' : 
 +            case '\n'
 +            case '\r'
 + // Leerzeichen werden einfach überlesen
 +              break;
 +            default : 
 +              println("Fehler: Der Lexer kann mit dem Zeichen '" + c
 +                  + "' an Position " + position + " nichts anfangen.");
 +                  System.exit(0);
 +        }
 + 
 +        position++; // Das von peek() oben gelesene Zeichen ist
 + // verarbeitet, also jetzt das nächste holen.
 + 
 +      }
 + 
 +    }
 + 
 +    return tokenListe;
 + 
 +  }
 +
 +</code>
 +
 +===== Die Hilfsmethoden lexZahl() und lexText() =====
 +<code java>
 +   /**
 + * Die Methode lexText geht davon aus, dass das nächste zu verarbeitende
 + * Zeichen ein Buchstabe ist. Solange weitere Buchstaben oder Ziffern
 + * kommen, liest sie sie und setzt sie zu einem Variablennamen zusammen.
 + */
 +  private void lexText() {
 + 
 +    String variablenBezeichner = "";
 + 
 +    do {
 +      char c = peek();
 +      variablenBezeichner += c;
 +      position++;
 +    } while(Character.isLetter(peek()) || Character.isDigit(peek()) || peek() == '_');
 + 
 +    tokenListe.add(new Token(variablenBezeichner));
 + 
 +  }
 + 
 + /**
 + * Die Methode lexZahl liest eine Zahl
 + */
 +  private void lexZahl() {
 + 
 +    String zahlAlsString = "";
 + 
 +    do {
 +      char c = peek();
 +      zahlAlsString += c;
 +      position++;
 +    } while(Character.isDigit(peek()) || peek() == '.');
 + 
 + /**
 + * Hier machen wir es uns leicht und lassen Java den String in eine Zahl
 + * konvertieren. Die Methode parseDouble ist für sich genommen natürlich
 + * auch ein Lexer.
 + */
 +    double zahl = Double.parseDouble(zahlAlsString);
 + 
 +    tokenListe.add(new Token(zahl));
 + 
 +  }
 +</code>
 +Zum einfachen Hinzufügen eines Tokens, das weder eine Zahl noch Text darstellt, wird eine kleine Hilfsmethode definiert:
 +<code java>
 +   /**
 + * Fügt der tokenListe das übergebene Token hinzu
 +
 + * @param tokenType
 + */
 +  private void addToken(TokenType tokenType) {
 +    tokenListe.add(new Token(tokenType));
 + 
 +  }
 +</code>
 +Der Zugriff auf den Programmtext geschieht immer über die Methode peek(), damit weniger Fehler beim Programmieren passieren:
 +<code java>
 +/**
 + * peek() liest das nächste Zeichen im Programmtext, erhöht die Variable
 + * position aber nicht. Ruft man peek() mehrmals hintereinander auf, liefert
 + * es also immer dasselbe Zeichen.
 + 
 + * @return nächstes Zeichen im Programmtext
 + */
 +private char peek() {
 +   if(position < text.length()) {
 +      return text.charAt(position);
 +   } else {
 +      return 0x;
 +   }
 +}
 +</code java>
 +
 +Die restlichen Methoden erklären sich von selbst:
 +<code java>
 +/**
 + * Vorsicht: Die übergebene Liste ist nur dann gefüllt, wenn zuvor die
 + * Methode lex aufgerufen wurde.
 + 
 + * @return Liste mit den Tokens, in die der Lexer den Programmtext zerlegt
 +         hat.
 + */
 +public ArrayList<Token> getTokenListe() {
 + return tokenListe;
 +}
 +
 +/**
 + * Nur zu Debuggingzwecken
 + */
 +public String toString() {
 +
 +   String s = "";
 +
 +   for(Token token : tokenListe) {
 +      s += token.toString() + " ";
 +   }
 +
 +   if(!s.isEmpty()) {
 +      s = s.substring(0, s.length() - 1);
 +   }
 +
 +   return s;
 +
 +}
 +</code>
 +
 +[[..:parser:start|Hier geht's weiter zum Parser!]]
  
compilerbau/lexer/start.1635444677.txt.gz · Zuletzt geändert: 2021/12/29 11:29 (Externe Bearbeitung)

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki