added a bunch of comments to all the out-facing parts of the Arithmetic system.

This commit is contained in:
Talia-12 2023-06-01 17:44:56 +10:00
parent 67fd933397
commit d7b2946f55
9 changed files with 111 additions and 6 deletions

View file

@ -4,13 +4,26 @@ import at.petrak.hexcasting.api.casting.arithmetic.operator.Operator;
import at.petrak.hexcasting.api.casting.math.HexDir;
import at.petrak.hexcasting.api.casting.math.HexPattern;
/**
* This is the interface to implement if you want to override the behaviour of an Operator pattern like ADD, SUB, etc. for some type/s of
* iotas for which that Operator pattern is not yet defined.
*/
public interface Arithmetic {
String arithName();
/**
* @return All the HexPatterns for which this Arithmetic has defined Operators.
*/
Iterable<HexPattern> opTypes();
/**
* @param pattern The HexPattern that would be drawn by the caster.
* @return The Operator that this Arithmetic has defined for that pattern.
*/
Operator getOperator(HexPattern pattern);
// Below are some common Operator patterns that you can make use of in your Arithmetic:
HexPattern ADD = HexPattern.fromAngles("waaw", HexDir.NORTH_EAST);
HexPattern SUB = HexPattern.fromAngles("wddw", HexDir.NORTH_WEST);
HexPattern MUL = HexPattern.fromAngles("waqaw", HexDir.SOUTH_EAST);

View file

@ -9,11 +9,23 @@ 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!");
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);
}
@ -21,6 +33,11 @@ public class ArithmeticEngine {
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) {
@ -43,11 +60,19 @@ public class ArithmeticEngine {
return operators.keySet();
}
public Iterable<Iota> run(HexPattern operator, Stack<Iota> iotas, int startingLength) throws Mishap {
var candidates = operators.get(operator);
/**
* 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 " + operator + " is not an operator."); //
HashCons hash = new HashCons.Pattern(operator);
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()) {
@ -62,7 +87,7 @@ public class ArithmeticEngine {
return op.apply(args);
}
public Operator resolveCandidates(List<Iota> args, HashCons hash, OpCandidates candidates) {
private Operator resolveCandidates(List<Iota> args, HashCons hash, OpCandidates candidates) {
return cache.computeIfAbsent(hash, $ -> {
for (var op : candidates.operators()) {
if (op.accepts.test(args)) {

View file

@ -5,18 +5,42 @@ import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import org.jetbrains.annotations.NotNull;
/**
* Represents an Operator, similar to Action except that it also has a defined set of IotaTypes that it accepts, and
* there can be multiple Operators 'assigned' to the same pattern in different Arithmetics as long as they don't have
* overlapping matched types. (Overlapping matched types is not checked for, but will have undefined behaviour).
*/
public abstract class Operator {
/**
* The number of arguments from the stack that this Operator requires; all Operators with the same pattern must have
* the same arity.
*/
public final int arity;
/**
* A function that should return true if the passed list of Iotas satisfies this Operator's type constraints, and false otherwise.
*/
public final IotaMultiPredicate accepts;
/**
* @param arity The number of arguments from the stack that this Operator requires; all Operators with the same pattern must have arity.
* @param accepts A function that should return true if the passed list of Iotas satisfies this Operator's type constraints, and false otherwise.
*/
public Operator(int arity, IotaMultiPredicate accepts) {
this.arity = arity;
this.accepts = accepts;
}
/**
* The method called when this Operator is actually acting on the stack, for real.
* @param iotas An iterable of iotas with {@link Operator#arity} elements that satisfied {@link Operator#accepts}.
* @return the iotas that this operator will return to the stack (with the first element of the returned iterable being placed deepest into the stack, and the last element on top of the stack).
*/
public abstract @NotNull Iterable<Iota> apply(@NotNull Iterable<Iota> iotas);
/**
* A helper method to take an iota that you know is of iotaType and returning it as an iota of that type.
*/
@SuppressWarnings("unchecked")
public static <T extends Iota> T downcast(Iota iota, IotaType<T> iotaType) {
if (iota.getType() != iotaType)

View file

@ -8,6 +8,9 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.function.BinaryOperator;
/**
* A helper class for defining {@link Operator}s of two iotas.
*/
public class OperatorBinary extends Operator {
public BinaryOperator<Iota> inner;

View file

@ -8,6 +8,9 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.function.UnaryOperator;
/**
* A helper class for defining {@link Operator}s of one iota.
*/
public class OperatorUnary extends Operator {
public UnaryOperator<Iota> inner;

View file

@ -2,19 +2,35 @@ package at.petrak.hexcasting.api.casting.arithmetic.predicates;
import at.petrak.hexcasting.api.casting.iota.Iota;
/**
* Used to determine whether a given set of iotas on the stack are acceptable types for
* the operator that is storing this IotaMultiPredicate.
*/
@FunctionalInterface
public interface IotaMultiPredicate {
boolean test(Iterable<Iota> iotas);
/**
* The resulting IotaMultiPredicate only returns true if all iotas passed into test match the type dictated by child.
*/
static IotaMultiPredicate all(IotaPredicate child) {
return new All(child);
}
/**
* The resulting IotaMultiPredicate returns true if two iotas are passed, the first matching first, and the second matching second.
*/
static IotaMultiPredicate pair(IotaPredicate first, IotaPredicate second) {
return new Pair(first, second);
}
/**
* The resulting IotaMultiPredicate returns true if three iotas are passed, the first matching first, the second matching second, and the third matching third.
*/
static IotaMultiPredicate triple(IotaPredicate first, IotaPredicate second, IotaPredicate third) {
return new Triple(first, second, third);
}
/**
* The resulting IotaMultiPredicate returns true if at least one iota passed matches needs, and the rest match fallback.
*/
static IotaMultiPredicate any(IotaPredicate needs, IotaPredicate fallback) {
return new Any(needs, fallback);
}

View file

@ -3,14 +3,24 @@ package at.petrak.hexcasting.api.casting.arithmetic.predicates;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.api.casting.iota.IotaType;
/**
* Used to determine whether a given iota is an acceptable type for the operator that is storing this. It must be strictly a function
* of the passed Iota's IotaType, or the caching done by ArithmeticEngine will be invalid.
*/
@FunctionalInterface
public interface IotaPredicate {
boolean test(Iota iota);
/**
* The resulting IotaPredicate returns true if the given iota matches either the left or right predicates.
*/
static IotaPredicate or(IotaPredicate left, IotaPredicate right) {
return new Or(left, right);
}
/**
* The resulting IotaPredicate returns true if the given iota's type is type.
*/
static IotaPredicate ofType(IotaType<?> type) {
return new OfType(type);
}
@ -29,5 +39,8 @@ public interface IotaPredicate {
}
}
/**
* This IotaPredicate returns true for all iotas.
*/
IotaPredicate TRUE = iota -> true;
}

View file

@ -13,6 +13,10 @@ import at.petrak.hexcasting.common.lib.hex.HexEvalSounds
import java.util.*
import java.util.function.Consumer
/**
* Represents an Operator with the give pattern as its identifier, a special type of Action that calls a different function depending on the type of its arguments.
* This exists so that addons can easily define their own overloads to patterns like addition, subtraction, etc.
*/
data class OperationAction(val pattern: HexPattern) : Action {
override fun operate(env: CastingEnvironment, image: CastingImage, continuation: SpellContinuation): OperationResult {
val stackList = image.stack

View file

@ -40,6 +40,9 @@ public enum DoubleArithmetic implements Arithmetic {
MOD
);
/**
* An example of an IotaMultiPredicate, which returns true only if all arguments to the Operator are DoubleIotas.
*/
public static final IotaMultiPredicate ACCEPTS = IotaMultiPredicate.all(IotaPredicate.ofType(DOUBLE));
@Override
@ -91,6 +94,7 @@ public enum DoubleArithmetic implements Arithmetic {
}
return null;
}
public static OperatorUnary make1(DoubleUnaryOperator op) {
return new OperatorUnary(ACCEPTS, i -> new DoubleIota(op.applyAsDouble(downcast(i, DOUBLE).getDouble())));
}