/** class Expr, our internal representation of an expression * in the F0 language. * See http://www.radford.edu/itec380/2024spring-ibarland/Homeworks/Project/ * * @author Ian Barland * @version 2023.Apr.10 */ import java.util.*; import java.util.function.*; import java.util.stream.*; /** An `Expr` represents (the parse tree of) an expression in F0. * See the internal records like `BinOp` etc, for specific variants. */ public sealed interface Expr permits Num, BinOp, Paren, IfZero { /********** Template *********/ /** * @return ... * public … funcForExpr( /* Expr this * / ) { return switch (this) { case Value v -> …; case Paren(var e ) -> … e.funcForExpr() …; case BinOp(var op, var l, var r) -> … op … l.funcForExpr() … r.funcForExpr() case IfZero( var tst, var thn, var els ) -> … tst.funcForExpr() … thn.funcForExpr() … els.funcForExpr() …; }; } */ /** Evaluate a given Expr. * @return the Value this Expr evaluates to. * (In F0, all values are numbers (doubles), but * in F3 that will change, which is why we have * pre-emptively made the return type 'Value'.) */ default public Value eval( /* Expr this */ ) { return switch (this) { case Value v -> v; // this instanceof Paren -> { Paren p = (Paren)this; (p.e).eval(); } //case Paren p -> (p.e).eval(); case Paren(var e ) -> e.eval(); case BinOp(var op, var l, var r) -> BinOp.evalBinOp(op, ((Num)(l.eval())).doubleValue(), ((Num)(r.eval())).doubleValue()); case IfZero( var tst, var thn, var els ) -> ( Math.abs(((Num)(tst.eval())).doubleValue()) < IfZero.EPSILON ? thn : els ) .eval(); }; } /** Return a String representation of this Expr. * The result will be something which can be * passed into 'parse(String)' to get the same * Expr back. That is, toString and parse are * inverses of each other. * @return a String representation of this Expr. */ default public String myToString( /* Expr this */ ) { /** We can't override `toString` here: it'd be mutliple-inheritance. * It comes from the fact that we had to make `Expr` and *interface* (and not an abstract class), * which in turn came from the fact that `record`s can't extend other classes * (because of internal-implementation, they already extend a private class `Record`). * Sigh. * Workaround: name this `myToString`, and have *each* subclass override toString * to just call Expr.myToString. */ return switch (this) { case Num(var d) -> Num.formatter.format(d); // make ints look nicer: round to 9 places, then omit trailing 0's case Paren(var e ) -> Paren.START_TOKEN + e.toString() + Paren.STOP_TOKEN; case BinOp(var op, var l, var r) -> BinOp.START_TOKEN + " " + l.toString() + " " + op + " " + r.toString() + BinOp.END_TOKEN; case IfZero( var tst, var thn, var els ) -> IfZero.START_TOKEN + " " + tst.toString() + " " + IfZero.CONT_TOKEN + " " + thn.toString() + " " + IfZero.CONT_TOKEN3 + " " + els.toString(); }; } /** Return (our internal representation of) the first expression in s. * * @param s The source code for exactly one Expr. Must by syntactically correct. * @return (our internal representation of) the expression s. */ public static Expr parse(String s) { return Expr.parse(new java.util.Scanner(s)); } public static final String PUNCTUATION = "@=^{}><;!~#:?|\\(\\)\\[\\]/\\\\"; //as a regex-class // Note: Beware cautious '-' or '.' or '+' as punctuation, // if it introduces ambiguity with numbers. /** Return (our internal representation of) the first expression in s. * Side-effect: consume *exactly* the chars of that expr from the front of `s`. * @param s A scanner starting with the source code for an Expr. * @pre The Expr source must be syntactically correct. * @return (our internal representation of) the expression at front of `s`. */ public static Expr parse(Scanner s) { return parse(s,PUNCTUATION); } /** Return (our internal representation of) the first expression in s. * Side-effect: consume *exactly* the chars of that expr from the front of `s`. * @param s A scanner starting with the source code for an Expr. * @param punctuation A string of the chars to be interpreted as puncutation (*and* not part larger tokens). * @pre The Expr source must be syntactically correct. * @return (our internal representation of) the expression at front of `s`. * @see {@link Expr#parse(Scanner)} */ public static Expr parse(java.util.Scanner s, String punctuation) { if (UtilIan.hasNextDoubleSplittingBy(s,punctuation)) { return Num.parse(s,punctuation); } else if (UtilIan.hasNextSplittingBy(s, Paren.START_TOKEN, punctuation) ) { return Paren.parse(s,punctuation); } else if (UtilIan.hasNextSplittingBy(s, BinOp.START_TOKEN,punctuation) ) { return BinOp.parse(s,punctuation); } else if (UtilIan.hasNextSplittingBy(s, IfZero.START_TOKEN, punctuation) ) { return IfZero.parse(s,punctuation); } else { /* Unknown syntax! */ String tokens = ""; while (s.hasNext()) { tokens += UtilIan.nextSplittingBy(s,punctuation) + " "; } throw new java.util.InputMismatchException( "Expr.parse: something has gone awry! Encountered \"" + tokens + "\""); } } /** * Abstract class Value - * The type of all Expressions which self-evaluate: * Numbers, and (later, in F3) functions. * * See http://www.radford.edu/itec380/2024spring-ibarland/Homeworks/Project/ * * (This architecture works as long as every Value is a type of * Expression, which is a nice feature to have in a language; * it means that any result can be pasted/substituted into * larger expressions, which promotes a substitution model * of evaluation.) * * @author Ian Barland * @version 2024-Apr-04 */ sealed public interface Value extends Expr {} /** Our internal representation of a number in the F0 language. * See http://www.radford.edu/itec380/2024spring-ibarland/Project * * @author Ian Barland * @version 2023.Apr.10 */ public record Num(double x) implements Value { /** “@Override” {@link Expr#parse} */ public static Num parse( Scanner s, String punctuation ) { return new Num( UtilIan.nextDoubleSplittingBy(s,punctuation) ); } private static java.text.DecimalFormat formatter = new java.text.DecimalFormat("#.#########"); /** Return the Java double this Value represents * (for use by other primitives in our language.) * @return the Java double this Value represents. */ double doubleValue() { return this.x; } @Override /** Compare two Numbers, but with some tolerance. */ public boolean equals( /* Num this, */ Object that) { if (this==that) { return true; } else if (that==null) { return false; } else if (this.getClass() != that.getClass()) { return false; } else { Num thatt = (Num) that; return UtilIan.equalsApprox(this.x,thatt.x); } } @Override public String toString() { return myToString(); } } /** Our internal representation of a Paren * in the F0 language. * See http://www.radford.edu/itec380/2024spring-ibarland/Homeworks/Project/ * * @author Ian Barland * @version 2023.Apr.10 */ public record Paren(Expr e) implements Expr { static final String START_TOKEN = "("; static final String STOP_TOKEN = ")"; /** “@Override” {@link Expr#parse} */ public static Paren parse( java.util.Scanner s, String punctuation ) { UtilIan.verifyToken( UtilIan.nextSplittingBy(s,punctuation), START_TOKEN); // Consume the opening ')', and continue. // NOTE: recur with `Expr.parse` -- not `parse` = `Paren.parse` which is NOT what we want! Expr theInsideExpr = Expr.parse(s,punctuation); UtilIan.verifyToken( UtilIan.nextSplittingBy(s,punctuation), STOP_TOKEN); // Consume the closing '(', and continue. return new Paren(theInsideExpr); } @Override public String toString() { return myToString(); } } /** Our internal representation of a BinOp * in the F0 language. * See http://www.radford.edu/itec380/2024spring-ibarland/Homeworks/Project/ * * @author Ian Barland * @version 2024.Apr.04 */ public record BinOp( String op, Expr left, Expr right) implements Expr { static final String START_TOKEN = "tilt"; static final String END_TOKEN = ""; public static final List>> OP_FUNCS = List.of( new Pair>( "sum", Double::sum ) , new Pair>( "wntr", (a,b) -> a-b ) , new Pair>( "spr", (a,b) -> a*b ) // I originally had `new Pair<>(...)`, omitting the full generic-type-args, // but some [only] students' compilers would complain about that! // (err msg "can't convert Object to BinaryOperator") ); public static final List OPS = OP_FUNCS.stream().map( Pair::first ).collect(Collectors.toList()); //OPS = List.of( "sum", "wntr", "spr" ); // <-- Alternately just use this, if `OP_FUNCS` not declared. /** “@Override” {@link Expr#parse} */ public static BinOp parse(java.util.Scanner s, String punctuation) { UtilIan.verifyToken( UtilIan.nextSplittingBy(s,punctuation), START_TOKEN); // Consume (&verify) opening token ']'. Expr lefty = Expr.parse(s,punctuation); // NOTE: recur with `Expr.parse` -- not `parse`=`BinOp.parse` which is NOT what we want! dbg("BinOp#parse-1: got " + lefty.toString()+ ". " ); String op = UtilIan.nextSplittingBy(s,punctuation); dbg("BinOp#parse-2: got " + op.toString() + ". "); Expr righty = Expr.parse(s,punctuation); dbg("BinOp#parse-3: got " + righty.toString() + ". "); if (!(OPS.contains(op))) throw new InputMismatchException(String.format("Unknown operator \"%s\".",op)); return new BinOp(op, lefty, righty ); } /** Evaluate E's `op` w/ `l` and `r` */ static Value evalBinOp( String op, double l, double r ) { Pair> opFunc = OP_FUNCS.stream() // didn't declare OP_FUNCS? See comment below. .filter(pr -> pr.first().equals(op) ) .findFirst() .orElseThrow( () -> new InputMismatchException(String.format("Unknown op `%s`; must be one of %s.", op, OPS)) ); return new Num( opFunc.second().apply(l,r) ); /* If OP_FUNCS wasn't defined, delete the above two statments, and instead use the following: return switch(op) { case "frodo -> new Num(l+r); case "gollum" -> new Num(l-r); case "samwise" -> new Num(l*r); default -> throw new RuntimeException(String.format("Unknown op `%s`; must be one of %s.", op, OPS)); }; */ } static void dbg( String fmt, Object... args ) { //System.out.printf( fmt, args ); } @Override public String toString() { return myToString(); } } /** Our internal representation of a IfZero expr * in the F0 language. * See http://www.radford.edu/itec380/2024spring-ibarland/Project * * @author Ian Barland * @version 2023.Apr.10 */ public record IfZero(Expr test, Expr thenAns, Expr elseAns) implements Expr { static final String START_TOKEN = "if"; static final String CONT_TOKEN = "equinox"; static final String CONT_TOKEN3 = ":"; @Override public String toString() { return myToString(); } /** “@Override” {@link Expr#parse} */ public static IfZero parse( java.util.Scanner s, String punctuation ) { UtilIan.verifyToken( UtilIan.nextSplittingBy(s,punctuation), START_TOKEN ); // consume&verify the "if" // NOTE: recur with `Expr.parse` -- not `parse` = `IfZero.parse` which is NOT what we want! Expr theTest = Expr.parse(s,punctuation); UtilIan.verifyToken( UtilIan.nextSplittingBy(s,punctuation), CONT_TOKEN ); // consume&verify the "equinox" Expr theThenAnswer = Expr.parse(s,punctuation); UtilIan.verifyToken( UtilIan.nextSplittingBy(s,punctuation), CONT_TOKEN3 ); // consume&verify the ":" Expr theElseAnswer = Expr.parse(s,punctuation); return new IfZero(theTest,theThenAnswer,theElseAnswer); } } static final double EPSILON = 1e-10; }