Startseite < Informatik < Algorithmen Datenstrukturen / Software-Engineering / Programmiersprachen < Compiler Interpreter < Setty < Setty-Präprozessor Setty-Scanner [ DRUCK , 2004 , 2005 , 2006 , 2007 , 2008 , 2009 ] Setty-Parser < Setty-Syntaxbaum > > Tinyray > / Java C/C++ POV-Ray LaTeX > / Künstliche Intelligenz > Schach Privates / Inhalt >
Setty-Parser
Ein gerade noch überschaubarer Parser zur Quellsprache Setty.
Setty-Parser Der Parser zur Sprache: Setty
Setty's Parser
Der Setty-Parser ist ein Top-Down-Parser, der ein gegebenes Setty-Programm beginnend mit dem Startsymbol S folgender Grammatik SG erkennt.

Das Produkt, welches der Setty-Paser nach einem erfolgreichen Parsevorgang zurückgibt, ist der Setty-Syntaxbaum. Nach dem Parsevorgang wird der Setty-Quellcode nicht mehr benötigt, da alle Informationen des Setty-Quellprogramms im dazugehörigen Syntaxbaum vorliegen.
Die Grammatik der Setty-Sprache
Setty-Grammatik SG = (N, T, P, S) mit

N = {ProgramSetUSetISetDSetFromTo},

T = {LPAREN1      := '(',     RPAREN1      := ')',
     LPAREN2      := '[',     RPAREN2      := ']',
     LPAREN3      := '{',     RPAREN3      := '}',
     PLUS         := '+',     MINUS        := '-',
     SEMICOLON    := ';',     QUESTIONMARK := '?',
     COMPLEMENT   := 'C',     UNION        := 'u'|'v',
     INTERSECTION := 'n',     DIFFERENCE   := '\',
     IS           := 'Is',    IN           := 'in',
     NOT          := 'not',
     NUMBER       := ['+'|'-']('0'-'9')+['.'('0'-'9')+],
     INFINITY     := 'inf'},

P = {Program ::= IS NUMBER [ NOT ] IN SetU QUESTIONMARK,
     SetU    ::= SetI ( UNION SetI )*,
     SetI    ::= SetD ( INTERSECTION SetD )*,
     SetD    ::= Set [ DIFFERENCE Set ],
     Set     ::= COMPLEMENT LPAREN1 SetU RPAREN1
               | LPAREN1 SetU RPAREN1
               | LPAREN3 [ NUMBER ( SEMICOLON NUMBER )* ] RPAREN3
               | From SEMICOLON To,
     From    ::= RPAREN2 ( NUMBER | MINUS INFINITY )
               | LPAREN2 NUMBER,
     To      ::= NUMBER ( LPAREN2 | RPAREN2 )
               | [ PLUS ] INFINITY LPAREN2} und

S = Program.
Implementierung Implementierung des Setty-Parsers
Ausnahmebehandlung
Da es sich hier um einen Top-Down-Parser handelt, welcher einen Syntaxbaum praktisch rekursiv aufbaut, kann an jeder beliebigen Stelle der Rekursion ein syntaktischer Fehler im Quellprogramm auftreten. Die einfachste Behandlung eines Fehlers in einer Rekursion dürfte wohl das Werfen einer Exception sein, da dies einen für den Programmierer unkomplizierten und unmittelbaren Ausstieg aus einer rekursiven Methode zur Folge hat. Deshalb sei zunächst die Klasse ParseException vorgestellt.

Die ParseException wird nur dann geworfen, wenn der Parser auf einen syntaktischen Fehler im Setty-Programm stoßen sollte. Deshalb wird auch nur der erste, vorkommende Fehler im Setty-Programm erkannt. Weitere Fehler werden nach und nach durch erneutes Parsen, erst nach Korrektur des jeweiligen Vorgängerfehlers, ermittelt. 1
Die ParseException
/**
 * Die Klasse ParseException ist eine Exception, welche nur von einem Parser
 * während des Parse-Vorgangs geworfen werden sollte.
 */

public class ParseException extends Exception {

  /**
   * Erzeugt eine neue ParseException mit einer Nachricht.
   * @param message Die Nachricht.
   */

  public ParseException(String message) {
    super(message);
  }
}
Der Setty-Parser
Jede Produktion der Grammatik bekommt eine Methode, welche jeweils einen Teilbaum für den Syntaxbaum produziert. Die Produktion
  • Program wird durch die Methode Program()
  • SetU wird durch die Methode SetU()
  • SetI wird durch die Methode SetI()
  • SetD wird durch die Methode SetD()
  • Set wird durch die Methode Set()
  • From wird durch die Methode From()
  • To wird durch die Methode To()
repräsentiert, wobei die Methode Program() den gesamten Syntaxbaum zurückgibt.

Diese Methoden implementieren jeweils ihre entsprechende Produktion exakt, also 1:1.
Der Setty-Parser
/**
 * Die Klasse Parser ist ein Top-Down-Parser,
 * der die Tokenfolge eines Setty-Programm gemäß der Setty-Grammatik zu einem
 * Setty-Syntaxbaum zusammenfasst.
 */

public class Parser {

  /**
   * Der Scanner, der sein Setty-Programm tokenweise lesen kann.
   */

  private Scanner scanner;

  /**
   * Veranlasst den Scanner das nächste Token zu lesen, falls es sich um ein
   * unbekanntes Token handeln sollte, wird eine ParseException geworfen.
   * @throws ParseException Quellprogramm ist fehlerhaft.
   */

  private void tryNextToken() throws ParseException {
    this.scanner.nextToken();
    if (this.scanner.getToken() == Token.UNKNOWN) {
      throw new ParseException("Unbekanntes Zeichen: '"
        + this.scanner.getTokenValue() + "'");
    }
  }

  /**
   * Erzeugt und initialisiet den Parser mit einem Setty-Scanner.
   * @param scanner Der Setty-Scanner.
   */

  public Parser(Scanner scanner) {
    this.scanner = scanner;
  }

  /**
   * Stößt den Parse-Vorgang an und gibt den Syntaxbaum zurück.
   * @throws ParseException Quellprogramm ist syntaktisch fehlerhaft.
   * @return Der Syntaxbaum.
   */

  public ProgramNode parse() throws ParseException {
    this.tryNextToken();
    return this.Program();
    // Das anschließende Token müsste EOS sein und wird hier nicht geprüft,
    // d.h. nach dem Fragezeichen dürfen noch weitere Zeichen vorkommen.
  }

  /**
   * Program ::= IS NUMBER [ NOT ] IN SetU QUESTIONMARK
   * @throws ParseException Quellprogramm ist syntaktisch fehlerhaft.
   * @return Der Syntaxbaum.
   */

  private ProgramNode Program() throws ParseException {
    // Parse IS (required)
    if (this.scanner.getToken() == Token.IS) {
      this.tryNextToken();
    } else {
      throw new ParseException("'Is' erwartet!");
    }
    // Parse NUMBER (required)
    double number = 0.0;
    if (this.scanner.getToken() == Token.NUMBER) {
      number = Double.parseDouble(this.scanner.getTokenValue());
      this.tryNextToken();
    } else {
      throw new ParseException("Zahl erwartet!");
    }
    // Parse NOT (optional)
    boolean not = false;
    if (this.scanner.getToken() == Token.NOT) {
      not = true;
      this.tryNextToken();
    }
    // Parse IN (required)
    if (this.scanner.getToken() == Token.IN) {
      this.tryNextToken();
    } else {
      throw new ParseException("'in' erwartet!");
    }
    // Parse SetU (required)
    SetNode setNode = this.SetU();
    // Parse QUESTIONMARK (required)
    if (this.scanner.getToken() == Token.QUESTIONMARK) {
      this.tryNextToken();
    } else {
      throw new ParseException("Mengenoperation oder '?' erwartet!");
    }
    // Rückgabe des gesamten Syntaxbaumes!
    return new ProgramNode(number, not, setNode);
  }

  /**
   * SetU ::= SetI ( UNION SetI )*
   * @throws ParseException Quellprogramm ist syntaktisch fehlerhaft.
   * @return Vereinigung aller Mengen SetI als Teilbaum des Syntaxbaumes.
   */

  private SetNode SetU() throws ParseException {
    // Parse SetI (required)
    SetNode setNode = this.SetI();

    // Parse UNION (optional)
    if (this.scanner.getToken() == Token.UNION) {

      UnionSetNode unionSetNode = new UnionSetNode();
      unionSetNode.addSetNode(setNode);

      do {
        this.tryNextToken();
        // Parse SetI (required)
        unionSetNode.addSetNode(this.SetI());

      } while (this.scanner.getToken() == Token.UNION);

      // Rückgabe der Vereinigung der Mengen.
      return unionSetNode;

    } else {
      // Ansonsten lediglich die Rückgabe von setNode
      return setNode;
    }
  }

  /**
   * SetI ::= SetD ( INTERSECTION SetD )*
   * @throws ParseException Quellprogramm ist syntaktisch fehlerhaft.
   * @return Schnitt aller Mengen SetD als Teilbaum des Syntaxbaumes.
   */

  private SetNode SetI() throws ParseException {
    // Parse SetD (required)
    SetNode setNode = this.SetD();

    // Parse INTERSECTION (optional)
    if (this.scanner.getToken() == Token.INTERSECTION) {

      IntersectionSetNode intersectionSetNode = new IntersectionSetNode();
      intersectionSetNode.addSetNode(setNode);

      do {
        this.tryNextToken();
        // Parse SetD (required)
        intersectionSetNode.addSetNode(this.SetD());

      } while (this.scanner.getToken() == Token.INTERSECTION);

      // Rückgabe des Durchschnitts der Mengen.
      return intersectionSetNode;

    } else {
      // Ansonsten lediglich die Rückgabe von setNode
      return setNode;
    }
  }

  /**
   * SetD ::= Set [ DIFFERENCE Set ]
   * @throws ParseException Quellprogramm ist syntaktisch fehlerhaft.
   * @return Differenz zweier Mengen Set als Teilbaum des Syntaxbaumes.
   */

  private SetNode SetD() throws ParseException {
    // Parse Set (required)
    SetNode setNode1 = this.Set();

    // Parse DIFFERENCE (optional)
  if (this.scanner.getToken() == Token.DIFFERENCE) {
      this.tryNextToken();
      // Parse Set (required)
      SetNode setNode2 = this.Set();
      // Rückgabe des Schnitts beider Mengen.
      return new DifferenceSetNode(setNode1, setNode2);

    } else {
      // Ansonsten lediglich die Rückgabe von setNode1
      return setNode1;
    }
  }

  /**
   * Set ::= COMPLEMENT LPAREN1 SetU RPAREN1
   *       | LPAREN1 SetU RPAREN1
   *       | LPAREN3 [ NUMBER ( SEMICOLON NUMBER )* ] RPAREN3
   *       | From SEMICOLON To
   * @throws ParseException Quellprogramm ist syntaktisch fehlerhaft.
   * @return Eine Menge als Teilbaum des Syntaxbaumes.
   */

  private SetNode Set() throws ParseException {
    // Parse COMPLEMENT or LPAREN1 or LPAREN3 or First(From) (required)
    if (this.scanner.getToken() == Token.COMPLEMENT) {

      this.tryNextToken();
      // Parse LPAREN1 (required)
      if (this.scanner.getToken() == Token.LPAREN1) {
        this.tryNextToken();
      } else {
        throw new ParseException("'(' erforderlich!");
      }
      // Parse SetU (required)
      SetNode setNode = this.SetU();
      // Parse RPAREN1 (required)
      if (this.scanner.getToken() == Token.RPAREN1) {
        this.tryNextToken();
      } else {
        throw new ParseException("Mengenoperation oder ')' erforderlich!");
      }
      // Rückgabe der aktuell ermittelten Menge
      return new ComplementSetNode(setNode);

    } else if (this.scanner.getToken() == Token.LPAREN1) {

      this.tryNextToken();
      // Parse SetU (required)
      SetNode setNode = this.SetU();
      // Parse RPAREN1 (required)
      if (this.scanner.getToken() == Token.RPAREN1) {
        this.tryNextToken();
      } else {
        throw new ParseException("Mengenoperation oder ')' erforderlich!");
      }
      // Rückgabe der aktuell ermittelten Menge
      return setNode;

    } else if (this.scanner.getToken() == Token.LPAREN3) {

      this.tryNextToken();
      // Parse NUMBERs (optional)
      ElementsSetNode elementsSetNode = new ElementsSetNode();
      if (this.scanner.getToken() == Token.NUMBER) {
        double element = Double.parseDouble(this.scanner.getTokenValue());
        elementsSetNode.addElement(element);
        this.tryNextToken();
      }
      while (this.scanner.getToken() == Token.SEMICOLON) {
        this.tryNextToken();
        // Parse Element (required)
        if (this.scanner.getToken() == Token.NUMBER) {
          double element = Double.parseDouble(this.scanner.getTokenValue());
          elementsSetNode.addElement(element);
          this.tryNextToken();
        } else {
          throw new ParseException("Zahl erwartet!");
        }
      }
      // Parse RPAREN3 (required)
      if (this.scanner.getToken() == Token.RPAREN3) {
        this.tryNextToken();
      } else {
        throw new ParseException("';' oder '}' erwartet!");
      }
      return elementsSetNode;

    } else { // First(From)

      // Parse From (required)
      SetNode from = this.From();
      // Parse SEMICOLON (required)
      if (this.scanner.getToken() == Token.SEMICOLON) {
        this.tryNextToken();
      } else {
        throw new ParseException("';' erwartet!");
      }
      // Parse To (required)
      SetNode to = this.To();

      if ((from == null) && (to == null)) {
        return new ComplementSetNode(new ElementsSetNode());
      } else if (from == null) {
        return to;
      } else if (to == null) {
        return from;
      } else {
        IntersectionSetNode intersectionSetNode = new IntersectionSetNode();
        intersectionSetNode.addSetNode(from);
        intersectionSetNode.addSetNode(to);
        return intersectionSetNode;
      }
    }
  }

  /**
   * From ::= RPAREN2 ( NUMBER | MINUS INFINITY )
   *        | LPAREN2 NUMBER
   * @throws ParseException Quellprogramm ist syntaktisch fehlerhaft.
   * @return Eine Menge als Teilbaum des Syntaxbaumes.
   */

  private SetNode From() throws ParseException {
    // Parse RPAREN2 or LPAREN2 (required)
    if (this.scanner.getToken() == Token.RPAREN2) {

      this.tryNextToken();
      // Parse NUMBER or MINUS (required)
      if (this.scanner.getToken() == Token.NUMBER) {
        double number = Double.parseDouble(this.scanner.getTokenValue());
        this.tryNextToken();
        return new GreaterSetNode(number);
      } else if (this.scanner.getToken() == Token.MINUS) {
        this.tryNextToken();
        // Parse INFINITY (required)
        if (this.scanner.getToken() == Token.INFINITY) {
          this.tryNextToken();
          return null;
        } else {
          throw new ParseException("Zahl oder 'inf' erwartet!");
        }
      } else {
        throw new ParseException("Zahl oder '-inf' erwartet!");
      }

    } else if (this.scanner.getToken() == Token.LPAREN2) {

      this.tryNextToken();
      // Parse NUMBER (required)
      if (this.scanner.getToken() == Token.NUMBER) {
        double number = Double.parseDouble(this.scanner.getTokenValue());
        this.tryNextToken();
        return new GreaterEqualSetNode(number);
      } else {
        throw new ParseException("Zahl erwartet!");
      }

    } else {
      throw new ParseException("Menge erwartet!");
    }
  }

  /**
   * To ::= NUMBER ( LPAREN2 | RPAREN2 )
   *      | [ PLUS ] INFINITY LPAREN2
   * @throws ParseException Quellprogramm ist syntaktisch fehlerhaft.
   * @return Eine Menge als Teilbaum des Syntaxbaumes.
   */

  private SetNode To() throws ParseException {

    // Parse NUMBER or else (required)
    if (this.scanner.getToken() == Token.NUMBER) {
      double number = Double.parseDouble(this.scanner.getTokenValue());
      this.tryNextToken();
      // Parse LPAREN2 or RPAREN2 (required)
      if (this.scanner.getToken() == Token.LPAREN2) {
        this.tryNextToken();
        return new LowerSetNode(number);
      } else if (this.scanner.getToken() == Token.RPAREN2) {
        this.tryNextToken();
        return new LowerEqualSetNode(number);
      } else {
        throw new ParseException("']' oder '[' erwartet!");
      }
    } else {

      // Parse PLUS (optional)
      if (this.scanner.getToken() == Token.PLUS) {
        this.tryNextToken();
      }
      // Parse INFINITY (required)
      if (this.scanner.getToken() == Token.INFINITY) {
        this.tryNextToken();
      } else {
        throw new ParseException("Zahl oder 'inf' erwartet!");
      }
      // Parse LPAREN2 (required)
      if (this.scanner.getToken() == Token.LPAREN2) {
        this.tryNextToken();
        return null;
      } else {
        throw new ParseException("'[' erwartet!");
      }
    }
  }
}
Verbesserung mit einer abstrakten Fabrik
Um eine bessere Erweiterbarkeit und Portierbarkeit des Parsers erreichen zu können, ist es ratsam die Instantiierung aller Syntaxteilbäume einer „abstrakten Fabrik” (DesignPattern: Erzeugungsmuster) zu überlassen.

Mit einer abstrakten Fabrik eliminiert man jegliche konkrete Erzeugung eines Syntaxteilbaumes, statt
  1. SetNode setNode = new UnionSetNode();
würde man
  1. SetNode setNode = kit.getUnionSetNode();
schreiben. So bleibt es der Fabrik (kit) überlassen, welches konkrete Produkt im Parsevorgang erzeugt wird. Der Softwareentwickler hielte sich hiermit die Option vor, für den Setty-Parser weitere Fabriken implementieren zu können, welche jeweils einen eigenen, den Anforderungen entsprechenden Syntaxbaum erzeugen.

Faustregel: Jedes „new” im Parser ist nicht so gut!

Sie finden ein Beispiel einer abstrakten Fabrik mit dem Namen ParseTreeKit unter Tinyray-Parser.