Java 解释器中的数学运算抽象

Abstracting over Math Operations in an Interpreter in Java

我正在写一个 AST 解释器 Java 有很多方法可以检查参数类型并在它们匹配时执行操作。到目前为止,已经有五种以上的方法,它们基本上是相互复制粘贴的版本。有没有办法抽象出要检查的类型和要执行的操作?

    @Override
    public Object visitMultiplyNode(MultiplyNode multiplyNode) {
        Object lhs = multiplyNode.getLeftHandSide().accept(this);
        Object rhs = multiplyNode.getRightHandSide().accept(this);

        if (lhs instanceof Double && rhs instanceof Double) {
            return (double) lhs * (double) rhs;
        }
        if (lhs instanceof Long && rhs instanceof Long) {
            return (long) lhs * (long) rhs;
        }
        throw new TypeError("Can not multiply " + lhs.getClass() + " and " + rhs.getClass() + ".");
    }

我要检查的类型并不总是相同的,例如模数节点只接受 Longs 而加法节点也接受字符串进行连接。

    @Override
    public Object visitAddNode(AddNode addNode) {
        Object lhs = addNode.getLeftHandSide().accept(this);
        Object rhs = addNode.getRightHandSide().accept(this);

        if (lhs instanceof Double && rhs instanceof Double) {
            return (double) lhs + (double) rhs;
        }
        if (lhs instanceof Long && rhs instanceof  Long) {
            return (long) lhs + (long) rhs;
        }
        if (lhs instanceof String && rhs instanceof String) {
            return "" + lhs + lhs;
        }
        throw new TypeError("Can not add " + lhs.getClass() + " and " + rhs.getClass() + ".");
    }

    @Override
    public Object visitModulusNode(ModulusNode modulusNode) {
        Object lhs = modulusNode.getLeftHandSide().accept(this);
        Object rhs = modulusNode.getRightHandSide().accept(this);

        if (lhs instanceof Long && rhs instanceof Long) {
            return (long) lhs % (long) rhs;
        }
        throw new TypeError("Can not take modulus of " + lhs.getClass() + " and " + rhs.getClass() + ".");
    }

您可以使用 lambda:

private Object visitBinaryOperatorNode(BinaryOpNode node, BiFunction<T, T> op) {
    Object lhs = node.getLeftHandSide().accept(this);
    Object rhs = node.getRightHandSide().accept(this);

    if (lhs instanceof Long && rhs instanceof Long) {
        return op.apply((long) lhs, (long) rhs);
    }
    throw new TypeError("Can not take " + node.getOpName() + "of " + lhs.getClass() + " and " + rhs.getClass() + ".");
}

但是,由于您的某些运算符支持多种类型,因此您需要另一层抽象:

@RequiredArgsConstructor// Lombok, otherwise write the boilerplate yourself
public class BinaryOperator<T, T> {
   @NonNull private final BiFunction<T, T> op;
   @NonNull private final Class<T> clazz;

   public boolean isApplicable(Object left, Object right) {
       return clazz.isInstance(left) && clazz.isInstance(right);
   }

   public T apply(Object left, Object right) {
       return op.apply(clazz.cast(left), clazz.cast(right));
   }
}

您现在可以传递一组有效的二元运算符并测试它们是否适用,如果适用,则应用它们。

private static final List<BinaryOperator<?, ?>> VALID_ADD_OPERATORS = Arrays.asList(
    new BinaryOperator<>((x, y) -> x + y, Double.class), 
    new BinaryOperator<>((x, y) -> x + y, Long.class),
    new BinaryOperator<>((x, y) -> x + y, String.class)
);

private static final List<BinaryOperator<?, ?>> VALID_MULTIPLY_OPERATORS = Arrays.asList(
    new BinaryOperator<>((x, y) -> x * y, Double.class), 
    new BinaryOperator<>((x, y) -> x * y, Long.class)
);

@Override
public Object visitAddNode(AddNode addNode) {
    return visitBinaryOperatorNode(addNode, VALID_ADD_OPERATORS );
}

@Override
public Object visitMultiplyNode(MultiplyNode multiplyNode) { 
    return visitBinaryOperatorNode(multiplyNode, VALID_MULTIPLY_OPERATORS ); 
}

private Object visitBinaryOperatorNode(BinaryOpNode node, List<BinaryOperator<?, ?>> validOperators) {
    Object lhs = node.getLeftHandSide().accept(this);
    Object rhs = node.getRightHandSide().accept(this);

    for (BinaryOperator<?, ?> op : validOperators) {
        if (op.isApplicable(lhs, rhs)) return op.apply(lhs, rhs);
    }
    throw new TypeError("Can not take " + node.getOpName() + "of " + lhs.getClass() + " and " + rhs.getClass() + ".");
}

您可以将这些检查提取到单独的对象中,并在需要时重复使用。
例如,通过为要处理转换的每种类型定义一个枚举值。

例如

public enum ConvertType {

    DOUBLE {
        Object apply(Object lhs, Object rhs) {
            if (lhs instanceof Double && rhs instanceof Double) {
                return (double) lhs + (double) rhs;
            }
            return null;
        }
    },

    LONG {
        Object apply(Object lhs, Object rhs) {
            if (lhs instanceof Long && rhs instanceof Long) {
                return (long) lhs + (long) rhs;
            }
            return null;
        }
    },

    STRING {
        Object apply(Object lhs, Object rhs) {
            if (lhs instanceof String && rhs instanceof String) {
                return "" + lhs + lhs;
            }
            return null;
        }
    };

    public static Object apply(Object a, Object b, ConvertType... convertTypes) {
        for (ConvertType convertType : convertTypes) {
            Object result = convertType.apply(a, b);
            if (result != null) {
                return result;
            }
        }
        throw new TypeError("Can not take modulus of " + a.getClass() + " and " + b.getClass() + ".");

    }

}

要转​​换的入口点是静态方法:

public static Object apply(Object a, Object b, ConvertType... convertTypes)

由于 var-args,允许使用可变数字传递 ConvertTypes。

例如:

@Override
public Object visitMultiplyNode(MultiplyNode multiplyNode) {
    Object lhs = multiplyNode.getLeftHandSide().accept(this);
    Object rhs = multiplyNode.getRightHandSide().accept(this);        
    return ConvertType.apply(lhs , rhs, ConvertType.DOUBLE, ConvertType.LONG);
}

或:

@Override
public Object visitAddNode(AddNode addNode) {
    Object lhs = addNode.getLeftHandSide().accept(this);
    Object rhs = addNode.getRightHandSide().accept(this);
    return ConvertType.apply(lhs , rhs, ConvertType.DOUBLE, ConvertType.LONG, ConvertType.STRING);    
}