// Arithmetical Expressions

/* Grammar for internal syntax

Exp t ::= CStr  (String s)
        | CInt  (int i)
        | Plus  (Exp t1, Exp t2)        
        | Minus (Exp t1, Exp t2)        
        | Len   (Exp t)
        | Str   (Exp t)

Values  v ::= CStr (s) | CInt (i)

*/

class StuckException extends Exception {}

abstract class Exp implements Cloneable {

    // t.toString() returns external representation of t
    public String toString () { return super.toString(); }

    // t.isIntValue() == true  iff  t = CInt(i)
    public boolean isIntValue () { return false; }

    // t.isStringValue() == true  iff  t = CStr(s)
    public boolean isStringValue () { return false; }

    // if  t --> t'  then t.step() == t'
    // if  t is a normal form, then t.step() throws StuckException
    public Exp step() throws StuckException { throw new StuckException(); }

    // if  t -->* t'  and  t' is normal, then t.steps(b) == t'
    // if  b == true  then a trace of the computation is printed
    public Exp steps(boolean verbose) {
	Exp t = this;
	if (verbose) System.out.println (t.toString());
        try {
	    for (;;) {
		t = t.step();
		if (verbose) System.out.println ("--> " + t.toString());
	    }
	} catch (StuckException e) {
	    return t;
	}
    }
}

class CStr extends Exp {
    private String s;
    CStr (final String s) { this.s = s; }
    public String value() { return s; }
    public String toString() { return "\"" + s + "\""; }
    public boolean isStringValue () { return true; }
}

class CInt extends Exp {
    private int i;
    CInt (final int i) { this.i = i; }
    public int value() { return i; } 
    public String toString() { return Integer.toString(i); }
    public boolean isIntValue () { return true; }
}

class Plus extends Exp {
    private Exp t1, t2;
    Plus (final Exp t1, final Exp t2) { this.t1 = t1; this.t2 = t2; }
    public String toString() { 
	return "(" + t1.toString() + " + " + t2.toString() + ")";
    }
    public Exp step() throws StuckException {
	if (t1.isIntValue()) {
	    if (t2.isIntValue()) 
		return new CInt (((CInt)t1).value() + ((CInt)t2).value());
	    else return new Plus(t1, t2.step()); 
	} else return new Plus(t1.step(), t2);
   }
}

class Minus extends Exp {
    private Exp t1, t2;
    Minus (final Exp t1, final Exp t2) { this.t1 = t1; this.t2 = t2; }
    public String toString() { 
	return "(" + t1.toString() + " - " + t2.toString() + ")";
    }
    public Exp step() throws StuckException {
	if (t1.isIntValue()) {
	    if (t2.isIntValue()) 
		return new CInt (((CInt)t1).value() - ((CInt)t2).value());
	    else return new Plus (t1, t2.step()); 
	} else return new Plus (t1.step(), t2);
    }
}

class Len extends Exp {
    private Exp t;
    Len (final Exp t) { this.t = t; }
    public String toString() {
	return t.toString() + ".length()" ;
    }
    public Exp step() throws StuckException {
	if (t.isStringValue())
	    return new CInt ((((CStr)t).value()).length());
	else return new Len (t.step());
    }
}

class Str extends Exp {
    private Exp t;
    Str (final Exp t) { this.t = t; }
    public String toString() {
	return "Integer.toString(" + t.toString() + ")";
    }
    public Exp step() throws StuckException {
	if (t.isIntValue())
	    return new CStr (Integer.toString(((CInt)t).value()));
	else return new Str (t.step());
    }
}

class Arith {
    static void m(Exp t) { t = new Plus (new CInt (1), new CInt (2)); }
    public static void main (String[] args) {
	Exp t = new Plus (new Len (new CStr ("hallo")), 
			  new Minus (new Plus (new CInt (4), new CInt (3)), 
				     new CInt (2)));
	t.steps(true);
	
    }
}
