3
\$\begingroup\$

I'm writing an interpreter, and I'm designing the abstract syntax tree currently. Right now, I'm trying to figure out a way to add (or do other operations with) two Numbers, without casting to double or using a long switch statement

I've currently implemented addition with a switch statement, but I don't want to copy this for every operator. Is there a better way to do this?

AdditionNumNum.java

@Override
public ValueNumeric simplify(Context c) {
    ValueNumeric left = this.left.simplify(c);
    ValueNumeric right = this.right.simplify(c);

    NumberType type = NumberType.leastCommonType(left.getType(), right.getType());

    Number leftNum = left.get();
    Number rightNum = right.get();

    switch(type) {
    case BYTE:
        return new ValueNumeric(leftNum.byteValue() + rightNum.byteValue(), type);
    case SHORT:
        return new ValueNumeric(leftNum.shortValue() + rightNum.shortValue(), type);
    case INT:
        return new ValueNumeric(leftNum.intValue() + rightNum.intValue(), type);
    case LONG:
        return new ValueNumeric(leftNum.longValue() + rightNum.longValue(), type);
    case FLOAT:
        return new ValueNumeric(leftNum.floatValue() + rightNum.floatValue(), type);
    case DOUBLE:
        return new ValueNumeric(leftNum.doubleValue() + rightNum.doubleValue(), type);
    }

    throw new InterpreterException("Unknown type " + type);
}

ValueNumeric.java

public class ValueNumeric extends Value<Number> {
    private final Number value;
    private final NumberType type;

    public ValueNumeric(Number value) {
        this(value, NumberType.forClass(value.getClass()));
    }
    public ValueNumeric(Number value, NumberType type) {
        if (!value.getClass().equals(type.clazz)) {
            throw new IllegalArgumentException("Value (" + value
                    + ") is not of the right type for " + type + " ("
                    + value.getClass() + "/" + type.clazz + ")");
        }
        this.value = value;
        this.type = type;
    }
    @Override
    public Number get(){
        return value;
    }
    public NumberType getType() {
        return type;
    }
    public ValueNumeric castTo(NumberType newType) {
        if (newType == this.type) {
            return this;
        }
        Number newValue = type.cast(this.value);
        return new ValueNumeric(newValue, newType);
    }


    @Override
    public String toString() {
        return "ValueNumeric [value=" + value + ", type=" + type + "]";
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + ((type == null) ? 0 : type.hashCode());
        result = prime * result + ((value == null) ? 0 : value.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        ValueNumeric other = (ValueNumeric) obj;
        if (type != other.type)
            return false;
        if (value == null) {
            if (other.value != null)
                return false;
        } else if (!value.equals(other.value))
            return false;
        return true;
    }


    public enum NumberType {
        BYTE(Byte.class, n -> Byte.valueOf(n.byteValue())),
        SHORT(Short.class, n -> Short.valueOf(n.shortValue())),
        INT(Integer.class, n -> Integer.valueOf(n.intValue())),
        LONG(Long.class, n -> Long.valueOf(n.longValue())),
        FLOAT(Float.class, n -> Float.valueOf(n.floatValue())),
        DOUBLE(Double.class, n -> Double.valueOf(n.doubleValue()));

        public final Class<? extends Number> clazz;
        private Function<Number, ? extends Number> castFunc;

        private <T extends Number> NumberType(Class<T> clazz,
                Function<Number, T> castFunc) {
            this.clazz = clazz;
            this.castFunc = castFunc;
        }

        public Number cast(Number number) {
            return castFunc.apply(number);
        }

        private static Map<Class<? extends Number>, NumberType> BY_CLASS = new HashMap<>();

        static {
            for (NumberType type : values()) {
                BY_CLASS.put(type.clazz, type);
            }
        }

        /**
         * 
         */
        public static NumberType forClass(Class<? extends Number> clazz) {
            return BY_CLASS.get(clazz);
        }

        /**
         * Returns the lowest common type between these two types of numbers,
         * for automatic type conversions.
         */
        public static NumberType leastCommonType(NumberType left, NumberType right) {
            if (left.ordinal() >= right.ordinal()) {
                return left;
            } else {
                return right;
            }
        }
    }
}

I would also appreciate any general comments on the code.

\$\endgroup\$
2
\$\begingroup\$

For the concrete problem of code-duplication just to exchange a single '+' with '-', '*' or '/', the only immediate option I see is, to move the calculation to a common type which can cope with all the value ranges that your concrete types can have. In Java, the only viable option is BigDecimal.

Thus, I'd add conversions from and to BigDecimal to the enum constants in NumberType and move the calculation over to calls to BigDecimal.add, BigDecimal.multiply, etc.

This might even give you some new interesting aspects like handling numeric overflow (what happens if you add byte 200 to byte 150?)

\$\endgroup\$
2
\$\begingroup\$

The default solution for any switch is inheritance.

Add ValueNumeric add(Number,Number) to your NumberType and implement it accordingly. Do the same with the other methods.

But mtj is mostlikely right. BigDecimal as common type, might make your life easier.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.