Compilerbauhome Compilerbau: JLex Scanner und LALR(1) CUP Parser für Arithmetische Ausdrücke Prof. Dr. Uwe Schmidt FH Wedel

JLex Scanner und LALR(1) CUP Parser für Arithmetische Ausdrücke

weiter

weiter

Parser mit Erzeugung eines Programmbaums

In diesem Beispiel wird eine Sprache für Arithmetische Ausdrücke definiert und verarbeitet. Es werden Ausdrücke mit int und double Zahlen, mit den 2-stelligen Operatoren +, -, * und / mit Prioritäten, mit einstelligen Operatoren für + und - und mit Konversionsoperatoren verarbeitet.

Für diese Ausdrücke wird ein Programmbaum aufgebaut. Es werden Klassen für den Programmbaum analog zu dem Beispiel aus der Java-Vorlesung: Verarbeitung von Ausdrücken verwendet.

In diesem Beispiel ist keinerlei Fehlerbehandlung für Syntaxfehler enthalten, es ist also nur für syntaktisch korrekte Eingaben brauchbar. Für ernsthaft einsetzbare Progamme müssen noch Erweiterungen und Verbesserungen zur Fehlerbehandlung gemacht werden.


weiter

Die JLex-Quelle für die lexikalischen Einheiten: expr.lex

import java_cup.runtime.Symbol;
 
%%
 
%cup
 
%class scanner
%implements java_cup.runtime.Scanner
%function next_token
%type Symbol
 
digit=[0-9]
digits={digit}+
digits0={digit}*
 
%%
 
"+" { return
        new Symbol(sym.PLUS);
    }
 
"-" { return
        new Symbol(sym.MINUS);
    }
 
"*" { return
        new Symbol(sym.TIMES);
    }
 
"/" { return
        new Symbol(sym.DIVIDE);
    }
 
"(" { return
        new Symbol(sym.LPARENT);
    }
 
")" { return
        new Symbol(sym.RPARENT);
    }
 
"double"
    { return
          new Symbol(sym.DOUBLE);
    }
 
"int"
    { return
          new Symbol(sym.INT);
    }
 
{digits}
    { return
        new Symbol(sym.NUMBER,
                   new String(yytext()));
    }
 
({digits}"."{digits0})|({digits0}"."{digits})
    { return
        new Symbol(sym.REAL,
                   new String(yytext()));
    }
 
 
[ \t\r\n\f]
    {
      /* ignore white space. */
    }
 
.   { System.err.println("Illegal character: "
                         + yytext());
    }

weiter

Die CUP-Quelle für die LALR(1) Grammatik: expr.cup

Die Festlegung der Prioritäten der Arithmetischen Operatoren sind hier durch Grammatik-Regeln gemacht worden, bei umfangreicheren Grammatiken können Prioritäten und Assoziativitäten für Operatoren verwendet werden, so dass die Parser-Tabellen dadurch verkleinert werden.

import java_cup.runtime.*;
 
parser code {:
 
  public
  static
  void
  main(String args[])
    throws Exception {
      new parser(new scanner(System.in)).parse();
  }
 
:}
 
terminal PLUSMINUSTIMESDIVIDELPARENTRPARENTDOUBLEINT;
terminal String NUMBERREAL;
 
non terminal prog;
non terminal Expr exprtermfactor;
 
prog    ::= expr:e
            {: System.out.println( e
                                 + " = "
                                 + e.eval());
            :}
        ;
 
expr    ::= expr:l PLUS term:r
            {: RESULT =
                 new BinaryPlus(l,r);
            :}
 
        |   expr:l MINUS term:r
            {: RESULT =
                 new BinaryMinus(l,r);
            :}
 
        |   term:t
            {: RESULT = t:}
        ;
 
term    ::= term:l TIMES factor:r
            {: RESULT =
                 new Mult(l,r);
            :}
 
        |   term:l DIVIDE factor:r
            {: RESULT =
                 new Divide(l,r);
            :}
 
        |   factor:f
            {: RESULT = f:}
        ;
 
factor  ::= LPARENT expr:e RPARENT
            {: RESULT = e;
            :}
 
        |   NUMBER:n
            {: RESULT =
                 new Const(
                   new Integer(n));
            :}
 
        |   REAL:n
            {: RESULT =
                 new Const(
                   new Double(n));
            :}
 
        |   PLUS factor:f
            {: RESULT =
                 new UnaryPlus(f);
            :}
 
        |   MINUS factor:f
            {: RESULT =
                 new UnaryMinus(f);
            :}
 
        |   LPARENT DOUBLE RPARENT factor:f
            {: RESULT =
                 new IntToDouble(f);
            :}
 
        |   LPARENT INT RPARENT factor:f
            {: RESULT =
                 new DoubleToInt(f);
            :}
 
        ;

weiter

Die Klassen für den Programmbaum

Expr

/** $Id: Expr.java,v 1.2 2007-01-02 17:49:52 uwe Exp $
 * eine abstrakte Klasse fuer die Konstruktion
 * beliebiger Ausdruecke
 * und zum Auswerten dieser Ausdruecke
 * 
 */
 
//--------------------
 
public
abstract
class Expr {
 
  // die eval Funktion
  // liefert ein beliebiges Objekt als Resultat
  // damit koennen nicht nur arithmetische Ausdruecke
  // sondern Ausdruecke mit beliebigem Resultat
  // behandelt werden
 
  public
  abstract
  Object eval();
 
}
 
//--------------------

BinaryExpr

/** $Id: BinaryExpr.java,v 1.2 2007-01-02 17:49:52 uwe Exp $
 * eine abstrakte Klasse fuer binaere Ausdruecke
 * 
 */
 
//--------------------
 
abstract
class BinaryExpr extends Expr {
 
  // die Operanden
 
  protected
  Expr left;
 
  protected
  Expr right;
 
  //--------------------
 
  // der Konstruktor ist protected
  // da er nur von Subklassen aus aufgerufen wird
 
  protected
  BinaryExpr(Expr leftExpr right) {
    this.left  = left;
    this.right = right;
  }
 
}
 
//--------------------

BinaryPlus

/** $Id: BinaryPlus.java,v 1.2 2007-01-02 17:49:52 uwe Exp $
 * eine Klasse fuer das binaere Plus
 * 
 */
 
//--------------------
 
public
class BinaryPlus extends BinaryExpr {
 
  //--------------------
 
  public
  BinaryPlus(Expr leftExpr right) {
    super(leftright);
  }
 
  //--------------------
 
  public
  Object eval() {
 
    // beide Operanden auswerten
    Object value1
      = left.eval();
 
    Object value2
      = right.eval();
 
    // + fuer int
    if ( value1 instanceof Integer
         &&
         value2 instanceof Integer ) {
 
      return
        new Integer( ((Integer)value1).intValue()
                     +
                     ((Integer)value2).intValue() );
    }
 
    // + fuer double
    if ( value1 instanceof Double
         &&
         value2 instanceof Double ) {
 
      return
        new Double( ((Double)value1).doubleValue()
                    +
                    ((Double)value2).doubleValue() );
    }
 
    // nicht erlaubtes Argument
    throw
      new IllegalArgumentException("binary + : illegal operand types");
  }
 
  //--------------------
 
  public
  String toString() {
    return
      "("   + left.toString()  +
      " + " + right.toString() +
      ")";
  }
}
 
//--------------------
 

Die anderen Operatoren werden durch analoge Klassen dargestellt.

UnaryExpr

/** $Id: UnaryExpr.java,v 1.2 2007-01-02 17:49:52 uwe Exp $
 * eine abstrakte Klasse fuer unaere Ausdruecke
 * 
 */
 
//--------------------
 
abstract
class UnaryExpr extends Expr {
 
  // der Operanden-Ausdruck
 
  protected
  Expr operand;
 
  //--------------------
 
  protected
  UnaryExpr(Expr operand) {
    this.operand = operand;
  }
 
}
 
//--------------------

UnaryMinus

/** $Id: UnaryMinus.java,v 1.2 2007-01-02 17:49:52 uwe Exp $
 * eine Klasse fuer das unaere Minus
 * 
 */
 
//--------------------
 
public
class UnaryMinus extends UnaryExpr {
 
  //--------------------
 
  public
  UnaryMinus(Expr operand) {
    super(operand);
  }
 
  //--------------------
 
  // eval wertet den Operanden aus
  // und negiert den Wert
  // Operation erlaubt fuer int und double Werte
 
  public
  Object eval() {
 
    // operand auswerten
 
    Object value
      = operand.eval();
 
    // - fuer int
 
    if ( value instanceof Integer ) {
      return
        new Integer(- ((Integer)value).intValue());
    }
 
    // - fuer double
 
    if ( value instanceof Double ) {
      return
        new Double(- ((Double)value).doubleValue());
    }
 
    // nicht erlaubtes Argument
 
    throw
      new IllegalArgumentException("unary - : illegal operand type");
  }
 
  //--------------------
 
  public
  String toString() {
    return
      "-(" + operand.toString() + ")";
  }
}
 
//--------------------
 

IntToDouble

/** $Id: IntToDouble.java,v 1.1 2000/01/10 17:05:54 uwe Exp $
 * eine Klasse zum Konvertieren von int Operanden
 * in double Operanden
 * 
 */
 
//--------------------
 
public
class IntToDouble extends UnaryExpr {
 
  //--------------------
 
  public
  IntToDouble(Expr operand) {
    super(operand);
  }
 
  //--------------------
 
  // eval wertet den Operanden aus
  // und konvertiert ihn nach double
 
  public
  Object eval() {
 
    // operand auswerten
 
    Object value
      = operand.eval();
 
    // int to double Konversion
 
    if ( value instanceof Integer ) {
      return
        new Double( ((Integer)value).doubleValue() );
    }
 
    if ( value instanceof Double ) {
      return
        value;
    }
 
    // nicht erlaubtes Argument
 
    throw
      new IllegalArgumentException("int to double: illegal operand type");
  }
 
  //--------------------
 
  public
  String toString() {
    return
      "((double)" + operand.toString() + ")";
  }
}
 
//--------------------
 

Die anderen Operatoren werden durch analoge Klassen dargestellt.


weiter

Der Makefile

JCP     := $(PWD)/../../../software:$(PWD)/../../../software/CUP:.:$(CLASSPATH)
 
JAVA    := java  -classpath $(JCP)
JAVAC   := javac -classpath $(JCP)
JLEX    := java  -classpath $(JCP) JLex.Main
CUP     := java  -classpath $(JCP) java_cup.Main
 
lexsrc  := $(shell echo *.lex)
cupsrc  := $(shell echo *.cup)
javagen := scanner.java parser.java sym.java
javasrc := \
          Test.java \
          Expr.java Const.java UnaryExpr.java BinaryExpr.java \
          UnaryPlus.java UnaryMinus.java IntToDouble.java DoubleToInt.java \
          BinaryPlus.java BinaryMinus.java Mult.java Divide.java
 
classes := $(javagen:.java=.class) $(javasrc:.java=.class)
 
tests   = test0 test1
 
all             : examples
 
examples        : $(javagen) $(classes) $(tests)
 
scanner.java    : expr.lex sym.java
        $(JLEX) $<
        mv $<.java scanner.java
 
parser.java sym.java    : expr.cup
        $(CUP) $<
 
test0   : $(classes)
        echo "1" | $(JAVA) parser
 
test1   : $(classes)
        echo "(3 + - - 8 / 2) * (8 - 2) * (int)((double)12 / 10.0)" | $(JAVA) parser
 
%.java  : %.lex
        $(JLEX) $<
 
%.class : %.java
        $(JAVAC) $<
 
.SUFFIXES       : .class .java .lex .cup
 
clean   :
        rm -f $(javagen) *.class *~ .*~

weiter

Das Protokoll von: make all

java  ... java_cup.Main expr.cup
 
Opening files...
 
Parsing specification from standard input...
 
Checking specification...
 
Building parse tables...
  Computing non-terminal nullability...
  Computing first sets...
  Building state machine...
  Filling in tables...
  Checking for non-reduced productions...
 
Writing parser...
 
Closing files...
------- CUP v0.10k Parser Generation Summary -------
  0 errors and 0 warnings
  12 terminals, 5 non-terminals, and 15 productions declared, 
  producing 29 unique parse states.
  0 terminals declared but not used.
  0 non-terminals declared but not used.
  0 productions never reduced.
  0 conflicts detected (0 expected).
  Code written to "parser.java", and "sym.java".
---------------------------------------------------- (v0.10k)
 
java  ... JLex.Main expr.lex
 
Processing first section -- user code.
Processing second section -- JLex declarations.
Processing third section -- lexical rules.
Creating NFA machine representation.
NFA comprised of 62 states.
Creating DFA transition table.
Working on DFA states..........................
Minimizing DFA transition table.
21 states after removal of redundant states.
Outputting lexical analyzer code.
 
mv expr.lex.java scanner.java
javac ... scanner.java
javac ... parser.java
javac ... Test.java
 
# a very simple test run
 
echo "1" | java  ... parser
 
1 = 1
 
# a bit more complex expression
 
echo "(3+--8/2)*(8-2)*(int)((double)12/10.0)" \
| java  ... parser
 
(((3 + (-(-(8)) / 2)) * (8 - 2)) * ((int)(((double)12) / 10.0))) = 42
 

Letzte Änderung: 14.02.2012
© Prof. Dr. Uwe Schmidt
Prof. Dr. Uwe Schmidt FH Wedel