HexCasting/Common/src/main/java/at/petrak/hexcasting/api/casting/arithmetic/engine/ArithmeticEngine.java

101 lines
4.4 KiB
Java

package at.petrak.hexcasting.api.casting.arithmetic.engine;
import at.petrak.hexcasting.api.casting.arithmetic.Arithmetic;
import at.petrak.hexcasting.api.casting.arithmetic.operator.Operator;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.api.casting.math.HexPattern;
import at.petrak.hexcasting.api.casting.mishaps.Mishap;
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs;
import java.util.*;
/**
* This is the class responsible for managing the various Arithmetics that are in use, deciding based on the current
* stack which Operator should be called, etc.
*/
public class ArithmeticEngine {
/**
* Data structure for mapping the pattern that gets drawn on the stack to the list of Operators that
* are overloading that pattern.
* @param pattern The pattern that the caster will need to draw to cast one of these operators.
* @param arity The number of arguments that all of these operators must consume from the stack.
* @param operators The list of all operators that overload this pattern.
*/
private record OpCandidates(HexPattern pattern, int arity, List<Operator> operators) {
public void addOp(Operator next) {
if (next.arity != arity) {
throw new IllegalArgumentException("Operators exist of differing arity! The pattern " + pattern
+ " already had arity " + arity + " when the operator with arity " + next.arity + ", " + next + " was added.");
}
operators.add(next);
}
}
public final Arithmetic[] arithmetics;
private final Map<HexPattern, OpCandidates> operators = new HashMap<>();
/**
* A cache mapping specific sets of Pattern, IotaType, IotaType, ..., IotaType to Operators so that the Operators don't need to be
* queried for what types they accept every time they are used.
*/
private final Map<HashCons, Operator> cache = new HashMap<>();
public ArithmeticEngine(List<Arithmetic> arithmetics) {
this.arithmetics = arithmetics.toArray(new Arithmetic[0]);
for (var arith : arithmetics) {
for (var op : arith.opTypes()) {
operators.compute(op, ($, info) -> {
var operator = arith.getOperator(op);
if (info == null) {
info = new OpCandidates(op, operator.arity, new ArrayList<>());
}
info.addOp(operator);
return info;
});
}
}
}
public Iterable<HexPattern> operatorSyms() {
return operators.keySet();
}
/**
* Runs one of the contained Operators assigned to the given pattern, modifying the passed stack of iotas.
* @param pattern The pattern that was drawn, used to determine which operators are candidates.
* @param iotas The current stack.
* @param startingLength The length of the stack before the operator executes (used for errors).
* @return The iotas to be added to the stack.
* @throws Mishap mishaps if invalid input to the operators is given by the caster.
*/
public Iterable<Iota> run(HexPattern pattern, Stack<Iota> iotas, int startingLength) throws Mishap {
var candidates = operators.get(pattern);
if (candidates == null)
throw new InvalidOperatorException("the pattern " + pattern + " is not an operator."); //
HashCons hash = new HashCons.Pattern(pattern);
var args = new ArrayList<Iota>(candidates.arity());
for (var i = 0; i < candidates.arity(); i++) {
if (iotas.isEmpty()) {
throw new MishapNotEnoughArgs(candidates.arity, startingLength);
}
var iota = iotas.pop();
hash = new HashCons.Pair(iota.getType(), hash);
args.add(iota);
}
Collections.reverse(args);
var op = resolveCandidates(args, hash, candidates);
return op.apply(args);
}
private Operator resolveCandidates(List<Iota> args, HashCons hash, OpCandidates candidates) {
return cache.computeIfAbsent(hash, $ -> {
for (var op : candidates.operators()) {
if (op.accepts.test(args)) {
return op;
}
}
throw new NoOperatorCandidatesException(candidates.pattern(), args, "No implementation candidates for op " + candidates.pattern() + " on args: " + args);
});
}
}