Recommendations for solving lab 2 with Java

The stub for lab 2 uses Java 21,
which admits a more readable alternative to the visitor pattern suggested in the book.
The following example code shows how you can process an object exp of the type type Exp generated by the parser defined in the stub.

switch(exp) {
    case EInt    e -> ... ;  
    case EDouble e -> ... ;  
    case EAnd    e -> ... ;  
    case EOr    e -> ... ;  
    ...  
    default -> throw new IllegalStateException("Case for " + e + " is not yet implemented.");  
}

On the right hand side of the -> you can extract further information from e,
for example, in the case EAnd e, you can use the two operands e.exp_1 and e.exp_2.

switch, if used like above produces a Java-expression, which means that you can use it in assignments:

var typedExpression = switch(exp) { ... }

To define the neccessary datatypes, records are very useful.
You can define a record like this:

public record TypedAnd(TypedExpr e1, TypedExpr e2) {
}

Objects of this type can be constructed with new TypedAnd(e1,e2) where e1 and e2 are TypedExprs. For solving the lab, it is reasonable to define an interface TypedExpr with a method which returns the type of an expression:

public interface TypedExpr {
  Type type();  
}

Where Type is a datatype for types that needs to be defined. With this interface, the TypedAnd from above can be turned into an implementation like this:

public record TypedAnd(TypedExpr e1, TypedExpr e2) {
  Type type() {
    return new Type.Bool; // this is what it could look like if you implement basic types as an enum
  }
}