1
0
mirror of https://github.com/DanilaFe/abacus synced 2024-12-22 07:20:09 -08:00

Merge pull request #21 from DanilaFe/context

Implement a Context system which allows concurrent creation of variables.
This commit is contained in:
Danila Fedorin 2017-09-15 22:51:56 -07:00 committed by GitHub
commit 46f78bb2ed
21 changed files with 506 additions and 360 deletions

View File

@ -0,0 +1,25 @@
package org.nwapw.abacus.function;
/**
* An exception thrown primarily from Tree Value operators and functions,
* which have to deal with the result of a Reducer as well as the results
* of Applicable.
*/
public class EvaluationException extends RuntimeException {
/**
* Creates a new EvaluationException with the default string.
*/
public EvaluationException() {
super("Error evaluating expression.");
}
/**
* Creates a new EvaluationError with the given message string.
* @param message the message string.
*/
public EvaluationException(String message) {
super(message);
}
}

View File

@ -2,7 +2,6 @@ package org.nwapw.abacus.plugin;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.variables.VariableDatabase;
/**
* A plugin class that can be externally implemented and loaded via the
@ -220,12 +219,4 @@ public abstract class Plugin {
*/
public abstract void onDisable();
/**
* Get the variable database.
* @return the variable database.
*/
public final VariableDatabase getVariableDatabase(){
return manager.getVariableDatabase();
}
}

View File

@ -3,7 +3,6 @@ package org.nwapw.abacus.plugin;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.variables.VariableDatabase;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
@ -422,12 +421,5 @@ public class PluginManager {
public Set<Class<?>> getLoadedPluginClasses() {
return loadedPluginClasses;
}
/**
* Gets the variable database.
* @return the database.
*/
public VariableDatabase getVariableDatabase(){
return abacus.getVariableDatabase();
}
}

View File

@ -1,12 +1,10 @@
package org.nwapw.abacus.plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.nwapw.abacus.context.MutableEvaluationContext;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber;
import org.nwapw.abacus.tree.Reducer;
import org.nwapw.abacus.tree.TreeNode;
import org.nwapw.abacus.tree.VariableNode;
@ -24,15 +22,16 @@ public class StandardPlugin extends Plugin {
*/
public final TreeValueOperator opSet = new TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(NumberImplementation implementation, TreeNode[] params) {
public boolean matchesParams(MutableEvaluationContext context, TreeNode[] params) {
return params.length == 2 && params[0] instanceof VariableNode;
}
@Override
public NumberInterface applyWithReducerInternal(NumberImplementation implementation, Reducer<? extends NumberInterface> reducer, TreeNode[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, TreeNode[] params) {
String assignTo = ((VariableNode) params[0]).getVariable();
NumberInterface value = params[1].reduce(reducer);
getVariableDatabase().getVariables().put(assignTo, value);
NumberInterface value = params[1].reduce(context.getReducer());
if(value == null) throw new EvaluationException();
context.setVariable(assignTo, value);
return value;
}
};
@ -41,16 +40,17 @@ public class StandardPlugin extends Plugin {
*/
public final TreeValueOperator opDefine = new TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(NumberImplementation implementation, TreeNode[] params) {
public boolean matchesParams(MutableEvaluationContext context, TreeNode[] params) {
return params.length == 2 && params[0] instanceof VariableNode;
}
@Nullable
@Override
public NumberInterface applyWithReducerInternal(NumberImplementation implementation, Reducer<? extends NumberInterface> reducer, TreeNode[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, TreeNode[] params) {
String assignTo = ((VariableNode) params[0]).getVariable();
getVariableDatabase().getDefinitions().put(assignTo, params[1]);
return params[1].reduce(reducer);
context.setDefinition(assignTo, params[1]);
NumberInterface value = params[1].reduce(context.getReducer());
if(value == null) throw new EvaluationException();
return value;
}
};
/**
@ -58,12 +58,12 @@ public class StandardPlugin extends Plugin {
*/
public static final NumberOperator OP_ADD = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].add(params[1]);
}
};
@ -72,12 +72,12 @@ public class StandardPlugin extends Plugin {
*/
public static final NumberOperator OP_SUBTRACT = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].subtract(params[1]);
}
@ -87,12 +87,12 @@ public class StandardPlugin extends Plugin {
*/
public static final NumberOperator OP_NEGATE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0) {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].negate();
}
};
@ -101,12 +101,12 @@ public class StandardPlugin extends Plugin {
*/
public static final NumberOperator OP_MULTIPLY = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].multiply(params[1]);
}
};
@ -135,7 +135,8 @@ public class StandardPlugin extends Plugin {
@Override
public NumberInterface instanceForPi() {
NumberInterface C = FUNCTION_SQRT.apply(this, new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
MutableEvaluationContext dummyContext = new MutableEvaluationContext(null, this, null);
NumberInterface C = FUNCTION_SQRT.apply(dummyContext, new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
NumberInterface M = PreciseNumber.ONE;
NumberInterface L = new PreciseNumber("13591409");
NumberInterface X = M;
@ -165,12 +166,12 @@ public class StandardPlugin extends Plugin {
*/
public static final NumberOperator OP_DIVIDE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 2 && params[1].compareTo(implementation.instanceForString(Integer.toString(0))) != 0;
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2 && params[1].compareTo(context.getInheritedNumberImplementation().instanceForString(Integer.toString(0))) != 0;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].divide(params[1]);
}
};
@ -180,14 +181,15 @@ public class StandardPlugin extends Plugin {
public static final NumberOperator OP_FACTORIAL = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0) {
//private HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> storedList = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>();
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1
&& params[0].fractionalPart().compareTo(implementation.instanceForString("0")) == 0
&& params[0].fractionalPart().compareTo(context.getInheritedNumberImplementation().instanceForString("0")) == 0
&& params[0].signum() >= 0;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
if (params[0].signum() == 0) {
return implementation.instanceForString("1");
}
@ -211,13 +213,14 @@ public class StandardPlugin extends Plugin {
*/
public static final NumberOperator OP_NPR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2 && params[0].fractionalPart().signum() == 0
&& params[1].fractionalPart().signum() == 0;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
if (params[0].compareTo(params[1]) < 0 ||
params[0].signum() < 0 ||
(params[0].signum() == 0 && params[1].signum() != 0)) return implementation.instanceForString("0");
@ -241,14 +244,14 @@ public class StandardPlugin extends Plugin {
*/
public static final NumberOperator OP_NCR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2 && params[0].fractionalPart().signum() == 0
&& params[1].fractionalPart().signum() == 0;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return OP_NPR.apply(implementation, params).divide(OP_FACTORIAL.apply(implementation, params[1]));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return OP_NPR.apply(context, params).divide(OP_FACTORIAL.apply(context, params[1]));
}
};
/**
@ -256,13 +259,13 @@ public class StandardPlugin extends Plugin {
*/
public static final NumberFunction FUNCTION_ABS = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return params[0].multiply(implementation.instanceForString(Integer.toString(params[0].signum())));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].multiply(context.getInheritedNumberImplementation().instanceForString(Integer.toString(params[0].signum())));
}
};
/**
@ -270,16 +273,17 @@ public class StandardPlugin extends Plugin {
*/
public static final NumberFunction FUNCTION_LN = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1 && params[0].compareTo(implementation.instanceForString("0")) > 0;
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1 && params[0].compareTo(context.getInheritedNumberImplementation().instanceForString("0")) > 0;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
NumberInterface param = params[0];
NumberInterface one = implementation.instanceForString("1");
int powersOf2 = 0;
while (FUNCTION_ABS.apply(implementation, param.subtract(one)).compareTo(implementation.instanceForString(".1")) >= 0) {
while (FUNCTION_ABS.apply(context, param.subtract(one)).compareTo(implementation.instanceForString(".1")) >= 0) {
if (param.subtract(one).signum() == 1) {
param = param.divide(implementation.instanceForString("2"));
powersOf2++;
@ -296,7 +300,7 @@ public class StandardPlugin extends Plugin {
}
}
}
return getLog2(implementation, param).multiply(implementation.instanceForString(Integer.toString(powersOf2))).add(getLogPartialSum(implementation, param));
return getLog2(context.getInheritedNumberImplementation(), param).multiply(implementation.instanceForString(Integer.toString(powersOf2))).add(getLogPartialSum(context, param));
}
/**
@ -305,13 +309,13 @@ public class StandardPlugin extends Plugin {
* @param x value at which the series is evaluated. 0 < x < 2. (x=2 is convergent but impractical.)
* @return the partial sum.
*/
private NumberInterface getLogPartialSum(NumberImplementation implementation, NumberInterface x) {
private NumberInterface getLogPartialSum(MutableEvaluationContext context, NumberInterface x) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
NumberInterface maxError = x.getMaxError();
x = x.subtract(implementation.instanceForString("1")); //Terms used are for log(x+1).
NumberInterface currentNumerator = x, currentTerm = x, sum = x;
int n = 1;
while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(maxError) > 0) {
while (FUNCTION_ABS.apply(context, currentTerm).compareTo(maxError) > 0) {
n++;
currentNumerator = currentNumerator.multiply(x).negate();
currentTerm = currentNumerator.divide(implementation.instanceForString(Integer.toString(n)));
@ -350,13 +354,13 @@ public class StandardPlugin extends Plugin {
*/
public static final NumberFunction FUNCTION_RAND_INT = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return implementation.instanceForString(Long.toString(Math.round(Math.random() * params[0].floor().intValue())));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return context.getInheritedNumberImplementation().instanceForString(Long.toString(Math.round(Math.random() * params[0].floor().intValue())));
}
};
/**
@ -364,8 +368,8 @@ public class StandardPlugin extends Plugin {
*/
public static final NumberOperator OP_CARET = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2) {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
NumberInterface zero = implementation.instanceForString("0");
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
NumberInterface zero = context.getInheritedNumberImplementation().instanceForString("0");
return params.length == 2
&& !(params[0].compareTo(zero) == 0
&& params[1].compareTo(zero) == 0)
@ -373,7 +377,8 @@ public class StandardPlugin extends Plugin {
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
NumberInterface zero = implementation.instanceForString("0");
if (params[0].compareTo(zero) == 0)
return zero;
@ -381,12 +386,12 @@ public class StandardPlugin extends Plugin {
return implementation.instanceForString("1");
//Detect integer bases:
if (params[0].fractionalPart().compareTo(implementation.instanceForString("0")) == 0
&& FUNCTION_ABS.apply(implementation, params[1]).compareTo(implementation.instanceForString(Integer.toString(Integer.MAX_VALUE))) < 0
&& FUNCTION_ABS.apply(implementation, params[1]).compareTo(implementation.instanceForString("1")) >= 0) {
&& FUNCTION_ABS.apply(context, params[1]).compareTo(implementation.instanceForString(Integer.toString(Integer.MAX_VALUE))) < 0
&& FUNCTION_ABS.apply(context, params[1]).compareTo(implementation.instanceForString("1")) >= 0) {
NumberInterface[] newParams = {params[0], params[1].fractionalPart()};
return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(implementation, newParams));
return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(context, newParams));
}
return FUNCTION_EXP.apply(implementation, FUNCTION_LN.apply(implementation, FUNCTION_ABS.apply(implementation, params[0])).multiply(params[1]));
return FUNCTION_EXP.apply(context, FUNCTION_LN.apply(context, FUNCTION_ABS.apply(context, params[0])).multiply(params[1]));
}
};
/**
@ -394,13 +399,13 @@ public class StandardPlugin extends Plugin {
*/
public static final NumberFunction FUNCTION_SQRT = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return OP_CARET.apply(implementation, params[0], implementation.instanceForString(".5"));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return OP_CARET.apply(context, params[0], context.getInheritedNumberImplementation().instanceForString(".5"));
}
};
private static final HashMap<NumberImplementation, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
@ -409,17 +414,18 @@ public class StandardPlugin extends Plugin {
*/
public static final NumberFunction FUNCTION_EXP = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
NumberInterface maxError = params[0].getMaxError();
int n = 0;
if (params[0].signum() < 0) {
NumberInterface[] negatedParams = {params[0].negate()};
return implementation.instanceForString("1").divide(applyInternal(implementation, negatedParams));
return implementation.instanceForString("1").divide(applyInternal(context, negatedParams));
} else {
//We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!.
//right and left refer to lhs and rhs in the above inequality.
@ -446,15 +452,16 @@ public class StandardPlugin extends Plugin {
*/
public final NumberFunction functionSin = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
NumberInterface pi = piFor(params[0].getClass());
NumberInterface twoPi = pi.multiply(implementation.instanceForString("2"));
NumberInterface theta = getSmallAngle(implementation, params[0], pi);
NumberInterface theta = getSmallAngle(context, params[0], pi);
//System.out.println(theta);
if (theta.compareTo(pi.multiply(implementation.instanceForString("1.5"))) >= 0) {
theta = theta.subtract(twoPi);
@ -462,7 +469,7 @@ public class StandardPlugin extends Plugin {
theta = pi.subtract(theta);
}
//System.out.println(theta);
return sinTaylor(implementation, theta);
return sinTaylor(context, theta);
}
};
/**
@ -470,13 +477,13 @@ public class StandardPlugin extends Plugin {
*/
public final NumberFunction functionCos = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return functionSin.apply(implementation, piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return functionSin.apply(context, piFor(params[0].getClass()).divide(context.getInheritedNumberImplementation().instanceForString("2"))
.subtract(params[0]));
}
};
@ -485,13 +492,13 @@ public class StandardPlugin extends Plugin {
*/
public final NumberFunction functionTan = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return functionSin.apply(implementation, params[0]).divide(functionCos.apply(implementation, params[0]));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return functionSin.apply(context, params[0]).divide(functionCos.apply(context, params[0]));
}
};
/**
@ -499,13 +506,13 @@ public class StandardPlugin extends Plugin {
*/
public final NumberFunction functionSec = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return implementation.instanceForString("1").divide(functionCos.apply(implementation, params[0]));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return context.getInheritedNumberImplementation().instanceForString("1").divide(functionCos.apply(context, params[0]));
}
};
/**
@ -513,13 +520,13 @@ public class StandardPlugin extends Plugin {
*/
public final NumberFunction functionCsc = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return implementation.instanceForString("1").divide(functionSin.apply(implementation, params[0]));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return context.getInheritedNumberImplementation().instanceForString("1").divide(functionSin.apply(context, params[0]));
}
};
/**
@ -527,13 +534,13 @@ public class StandardPlugin extends Plugin {
*/
public final NumberFunction functionCot = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return functionCos.apply(implementation, params[0]).divide(functionSin.apply(implementation, params[0]));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return functionCos.apply(context, params[0]).divide(functionSin.apply(context, params[0]));
}
};
@ -542,23 +549,24 @@ public class StandardPlugin extends Plugin {
*/
public final NumberFunction functionArcsin = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1
&& FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) <= 0;
&& FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) <= 0;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
if (FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString(".8")) >= 0) {
NumberInterface[] newParams = {FUNCTION_SQRT.apply(implementation, implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
if (FUNCTION_ABS.apply(context, params[0]).compareTo(implementation.instanceForString(".8")) >= 0) {
NumberInterface[] newParams = {FUNCTION_SQRT.apply(context, implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
.subtract(applyInternal(implementation, newParams)).multiply(implementation.instanceForString(Integer.toString(params[0].signum())));
.subtract(applyInternal(context, newParams)).multiply(implementation.instanceForString(Integer.toString(params[0].signum())));
}
NumberInterface currentTerm = params[0], sum = currentTerm,
multiplier = currentTerm.multiply(currentTerm), summandBound = sum.getMaxError().multiply(implementation.instanceForString("1").subtract(multiplier)),
power = currentTerm, coefficient = implementation.instanceForString("1");
int exponent = 1;
while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(summandBound) > 0) {
while (FUNCTION_ABS.apply(context, currentTerm).compareTo(summandBound) > 0) {
exponent += 2;
power = power.multiply(multiplier);
coefficient = coefficient.multiply(implementation.instanceForString(Integer.toString(exponent - 2)))
@ -575,14 +583,14 @@ public class StandardPlugin extends Plugin {
*/
public final NumberFunction functionArccos = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) <= 0;
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) <= 0;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
.subtract(functionArcsin.apply(implementation, params));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return piFor(params[0].getClass()).divide(context.getInheritedNumberImplementation().instanceForString("2"))
.subtract(functionArcsin.apply(context, params));
}
};
@ -591,14 +599,14 @@ public class StandardPlugin extends Plugin {
*/
public final NumberFunction functionArccsc = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) >= 0;
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) >= 0;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
NumberInterface[] reciprocalParamArr = {implementation.instanceForString("1").divide(params[0])};
return functionArcsin.apply(implementation, reciprocalParamArr);
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberInterface[] reciprocalParamArr = {context.getInheritedNumberImplementation().instanceForString("1").divide(params[0])};
return functionArcsin.apply(context, reciprocalParamArr);
}
};
@ -607,14 +615,14 @@ public class StandardPlugin extends Plugin {
*/
public final NumberFunction functionArcsec = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) >= 0;
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) >= 0;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
NumberInterface[] reciprocalParamArr = {implementation.instanceForString("1").divide(params[0])};
return functionArccos.apply(implementation, reciprocalParamArr);
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberInterface[] reciprocalParamArr = {context.getInheritedNumberImplementation().instanceForString("1").divide(params[0])};
return functionArccos.apply(context, reciprocalParamArr);
}
};
@ -623,20 +631,21 @@ public class StandardPlugin extends Plugin {
*/
public final NumberFunction functionArctan = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
if (params[0].signum() == -1) {
NumberInterface[] negatedParams = {params[0].negate()};
return applyInternal(implementation, negatedParams).negate();
return applyInternal(context, negatedParams).negate();
}
if (params[0].compareTo(implementation.instanceForString("1")) > 0) {
NumberInterface[] reciprocalParams = {implementation.instanceForString("1").divide(params[0])};
return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
.subtract(applyInternal(implementation, reciprocalParams));
.subtract(applyInternal(context, reciprocalParams));
}
if (params[0].compareTo(implementation.instanceForString("1")) == 0) {
return piFor(params[0].getClass()).divide(implementation.instanceForString("4"));
@ -644,12 +653,12 @@ public class StandardPlugin extends Plugin {
if (params[0].compareTo(implementation.instanceForString(".9")) >= 0) {
NumberInterface[] newParams = {params[0].multiply(implementation.instanceForString("2"))
.divide(implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
return applyInternal(implementation, newParams).divide(implementation.instanceForString("2"));
return applyInternal(context, newParams).divide(implementation.instanceForString("2"));
}
NumberInterface currentPower = params[0], currentTerm = currentPower, sum = currentTerm,
maxError = params[0].getMaxError(), multiplier = currentPower.multiply(currentPower).negate();
int n = 1;
while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(maxError) > 0) {
while (FUNCTION_ABS.apply(context, currentTerm).compareTo(maxError) > 0) {
n += 2;
currentPower = currentPower.multiply(multiplier);
currentTerm = currentPower.divide(implementation.instanceForString(Integer.toString(n)));
@ -664,14 +673,14 @@ public class StandardPlugin extends Plugin {
*/
public final NumberFunction functionArccot = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
.subtract(functionArctan.apply(implementation, params));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return piFor(params[0].getClass()).divide(context.getInheritedNumberImplementation().instanceForString("2"))
.subtract(functionArctan.apply(context, params));
}
};
@ -709,16 +718,16 @@ public class StandardPlugin extends Plugin {
* @param x where the series is evaluated.
* @return the value of the series
*/
private static NumberInterface sinTaylor(NumberImplementation implementation, NumberInterface x) {
private static NumberInterface sinTaylor(MutableEvaluationContext context, NumberInterface x) {
NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x;
NumberInterface maxError = x.getMaxError();
int n = 1;
do {
n += 2;
power = power.multiply(multiplier);
currentTerm = power.divide(factorial(implementation, n));
currentTerm = power.divide(factorial(context.getInheritedNumberImplementation(), n));
sum = sum.add(currentTerm);
} while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(maxError) > 0);
} while (FUNCTION_ABS.apply(context, currentTerm).compareTo(maxError) > 0);
return sum;
}
@ -728,10 +737,10 @@ public class StandardPlugin extends Plugin {
* @param phi an angle (in radians).
* @return theta in [0, 2pi) that differs from phi by a multiple of 2pi.
*/
private static NumberInterface getSmallAngle(NumberImplementation implementation, NumberInterface phi, NumberInterface pi) {
NumberInterface twoPi = pi.multiply(implementation.instanceForString("2"));
NumberInterface theta = FUNCTION_ABS.apply(implementation, phi).subtract(twoPi
.multiply(FUNCTION_ABS.apply(implementation, phi).divide(twoPi).floor())); //Now theta is in [0, 2pi).
private static NumberInterface getSmallAngle(MutableEvaluationContext context, NumberInterface phi, NumberInterface pi) {
NumberInterface twoPi = pi.multiply(context.getInheritedNumberImplementation().instanceForString("2"));
NumberInterface theta = FUNCTION_ABS.apply(context, phi).subtract(twoPi
.multiply(FUNCTION_ABS.apply(context, phi).divide(twoPi).floor())); //Now theta is in [0, 2pi).
if (phi.signum() < 0) {
theta = twoPi.subtract(theta);
}

View File

@ -1,93 +0,0 @@
package org.nwapw.abacus.tree;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.NumberFunction;
import org.nwapw.abacus.function.NumberOperator;
import org.nwapw.abacus.function.TreeValueFunction;
import org.nwapw.abacus.function.TreeValueOperator;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PromotionManager;
import org.nwapw.abacus.number.PromotionResult;
import org.nwapw.abacus.variables.VariableDatabase;
/**
* A reducer implementation that turns a tree into a single number.
* This is not always guaranteed to work.
*/
public class NumberReducer implements Reducer<NumberInterface> {
/**
* The plugin manager from which to draw the functions.
*/
private Abacus abacus;
/**
* Creates a new number reducer.
*
* @param abacus the calculator instance.
*/
public NumberReducer(Abacus abacus) {
this.abacus = abacus;
}
@Override
public NumberInterface reduceNode(TreeNode node, Object... children) {
PromotionManager manager = abacus.getPromotionManager();
if (node instanceof NumberNode) {
return abacus.getNumberImplementation().instanceForString(((NumberNode) node).getNumber());
} else if (node instanceof VariableNode) {
VariableDatabase database = abacus.getVariableDatabase();
String name = ((VariableNode) node).getVariable();
NumberInterface variable = database.getVariables().get(name);
if(variable != null) return variable;
TreeNode definition = database.getDefinitions().get(name);
if(definition != null) return definition.reduce(this);
return abacus.getNumberImplementation().instanceForString("0");
} else if (node instanceof NumberBinaryNode) {
NumberInterface left = (NumberInterface) children[0];
NumberInterface right = (NumberInterface) children[1];
NumberOperator operator = abacus.getPluginManager().operatorFor(((BinaryNode) node).getOperation());
PromotionResult result = manager.promote(left, right);
if (result == null) return null;
return operator.apply(result.getPromotedTo(), result.getItems());
} else if (node instanceof NumberUnaryNode) {
NumberInterface child = (NumberInterface) children[0];
NumberOperator operator = abacus.getPluginManager().operatorFor(((UnaryNode) node).getOperation());
return operator.apply(abacus.getPluginManager().interfaceImplementationFor(child.getClass()), child);
} else if (node instanceof FunctionNode) {
NumberInterface[] convertedChildren = new NumberInterface[children.length];
for (int i = 0; i < convertedChildren.length; i++) {
convertedChildren[i] = (NumberInterface) children[i];
}
NumberFunction function = abacus.getPluginManager().functionFor(((FunctionNode) node).getCallTo());
if (function == null) return null;
PromotionResult result = manager.promote(convertedChildren);
if (result == null) return null;
return function.apply(result.getPromotedTo(), result.getItems());
} else if (node instanceof TreeValueFunctionNode) {
CallNode callNode = (CallNode) node;
TreeNode[] realChildren = new TreeNode[callNode.getChildren().size()];
for (int i = 0; i < realChildren.length; i++) {
realChildren[i] = callNode.getChildren().get(i);
}
TreeValueFunction function =
abacus.getPluginManager().treeValueFunctionFor(callNode.getCallTo());
if (function == null) return null;
return function.applyWithReducer(abacus.getNumberImplementation(), this, realChildren);
} else if (node instanceof TreeValueBinaryNode) {
BinaryNode binaryNode = (BinaryNode) node;
TreeValueOperator operator = abacus.getPluginManager()
.treeValueOperatorFor(binaryNode.getOperation());
if (operator == null) return null;
return operator.applyWithReducer(abacus.getNumberImplementation(), this, binaryNode.getLeft(), binaryNode.getRight());
} else if (node instanceof TreeValueUnaryNode) {
UnaryNode unaryNode = (UnaryNode) node;
TreeValueOperator operator = abacus.getPluginManager()
.treeValueOperatorFor(unaryNode.getOperation());
if (operator == null) return null;
return operator.applyWithReducer(abacus.getNumberImplementation(), this, unaryNode.getApplyTo());
}
return null;
}
}

View File

@ -1,17 +1,17 @@
package org.nwapw.abacus
import org.nwapw.abacus.config.Configuration
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.context.EvaluationContext
import org.nwapw.abacus.number.PromotionManager
import org.nwapw.abacus.parsing.LexerTokenizer
import org.nwapw.abacus.parsing.ShuntingYardParser
import org.nwapw.abacus.parsing.TreeBuilder
import org.nwapw.abacus.plugin.NumberImplementation
import org.nwapw.abacus.plugin.PluginManager
import org.nwapw.abacus.plugin.StandardPlugin
import org.nwapw.abacus.tree.EvaluationResult
import org.nwapw.abacus.tree.NumberReducer
import org.nwapw.abacus.tree.TreeNode
import org.nwapw.abacus.variables.VariableDatabase
/**
* Core class to handle all mathematics.
@ -36,10 +36,6 @@ class Abacus(val configuration: Configuration) {
* The plugin manager used to handle loading and unloading plugins.
*/
val pluginManager = PluginManager(this)
/**
* The reducer used to turn trees into a single number.
*/
val numberReducer = NumberReducer(this)
/**
* The tree builder that handles the conversion of strings into trees.
*/
@ -48,28 +44,42 @@ class Abacus(val configuration: Configuration) {
* The promotion manager used to convert between number implementations.
*/
val promotionManager = PromotionManager(this)
/**
* The database of variable definitions.
* The hidden, mutable implementation of the context.
*/
val variableDatabase = VariableDatabase(this)
private val mutableContext = MutableEvaluationContext(numberImplementation = StandardPlugin.IMPLEMENTATION_NAIVE)
/**
* The number implementation used by default.
* The base context from which calculations are started.
*/
val numberImplementation: NumberImplementation
get() {
val selectedImplementation =
pluginManager.numberImplementationFor(configuration.numberImplementation)
if (selectedImplementation != null) return selectedImplementation
return StandardPlugin.IMPLEMENTATION_NAIVE
}
val context: EvaluationContext
get() = mutableContext
init {
pluginManager.addListener(tokenizer)
pluginManager.addListener(parser)
pluginManager.addListener(promotionManager)
pluginManager.addListener(variableDatabase)
}
/**
* Reloads the Abacus core.
*/
fun reload(){
pluginManager.reload()
with(mutableContext) {
numberImplementation = pluginManager.numberImplementationFor(configuration.numberImplementation)
clearVariables()
clearDefinitions()
}
}
/**
* Merges the current context with the provided one, updating
* variables and the like.
* @param context the context to apply.
*/
fun applyToContext(context: EvaluationContext){
mutableContext.apply(context)
}
/**
* Parses a string into a tree structure using the main
* tree builder.
@ -79,12 +89,26 @@ class Abacus(val configuration: Configuration) {
*/
fun parseString(input: String): TreeNode? = treeBuilder.fromString(input)
/**
* Evaluates the given tree using the main
* number reducer.
* Evaluates the given tree.
*
* @param tree the tree to reduce, must not be null.
* @return the resulting number, or null of the reduction failed.
* @return the evaluation result.
*/
fun evaluateTree(tree: TreeNode): NumberInterface? = tree.reduce(numberReducer)
fun evaluateTree(tree: TreeNode): EvaluationResult {
return evaluateTreeWithContext(tree, context.mutableSubInstance())
}
/**
* Evaluates the given tree using a different context than
* the default one.
*
* @param tree the tree to reduce, must not be null.
* @param context the context to use for the evaluation.
* @return the evaluation result.
*/
fun evaluateTreeWithContext(tree: TreeNode, context: MutableEvaluationContext): EvaluationResult {
val newReducer = NumberReducer(this, context)
val evaluationValue = tree.reduce(newReducer)
return EvaluationResult(evaluationValue, newReducer.context)
}
}

View File

@ -0,0 +1,26 @@
package org.nwapw.abacus.context
import kotlin.reflect.KProperty
/**
* A delegate to accumulate a collection of elements in a [EvaluationContext] hierarchy.
*
* ChainAccumulateDelegate is similar to the [ChainSearchDelegate], however, it operates only on collections.
* Instead of returning the most recent collection, it merges them into a [Set].
*
* @param T the type of element in the collection.
* @property valueGetter the getter used to access the collection from the context.
*/
class ChainAccumulateDelegate<out T>(private val valueGetter: EvaluationContext.() -> Collection<T>) {
operator fun getValue(selfRef: Any, property: KProperty<*>): Set<T> {
val set = mutableSetOf<T>()
var currentRef: EvaluationContext = selfRef as? EvaluationContext ?: return set
while(true) {
set.addAll(currentRef.valueGetter())
currentRef = currentRef.parent ?: break
}
return set
}
}

View File

@ -0,0 +1,29 @@
package org.nwapw.abacus.context
import kotlin.reflect.KProperty
/**
* A delegate to search a hierarchy made up of [EvaluationContext].
*
* ChainSearchDelegate is a variable delegate written specifically for use in [EvaluationContext], because
* of its hierarchical structure. Variables not found in the current context are searched
* for in its parent, which continues recursively until the context being examined has no parent.
* This class assists that logic, which is commonly re-used with different variable types, by calling
* [valueGetter] on the current context, then its parent, etc.
*
* @param V the type of the property to search recursively.
* @property valueGetter the getter lambda to access the value from the context.
*/
class ChainSearchDelegate<out V>(private val valueGetter: EvaluationContext.() -> V?) {
operator fun getValue(selfRef: Any, property: KProperty<*>): V? {
var currentRef = selfRef as? EvaluationContext ?: return null
var returnedValue = currentRef.valueGetter()
while (returnedValue == null) {
currentRef = currentRef.parent ?: break
returnedValue = currentRef.valueGetter()
}
return returnedValue
}
}

View File

@ -0,0 +1,84 @@
package org.nwapw.abacus.context
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.plugin.NumberImplementation
import org.nwapw.abacus.tree.Reducer
import org.nwapw.abacus.tree.TreeNode
/**
* A context for the reduction of a [org.nwapw.abacus.tree.TreeNode] into a number.
*
* The reduction context is used to carry important state information captured at the beginning
* of the reduction of an expression, such as the variables and the implementation in use.
*
* @property parent the parent of this context.
* @property numberImplementation the implementation for numbers of this context.
* @property reducer the reducer used by this context.
*/
open class EvaluationContext(val parent: EvaluationContext? = null,
open val numberImplementation: NumberImplementation? = null,
open val reducer: Reducer<NumberInterface>? = null) {
/**
* The map of variables in this context.
*/
protected val variableMap = mutableMapOf<String, NumberInterface>()
/**
* The map of definitions in this context.
*/
protected val definitionMap = mutableMapOf<String, TreeNode>()
/**
* The set of all variable names defined in this context.
*/
val variables: Set<String>
get() = variableMap.keys
/**
* The set of all definition names defined in this context.
*/
val definitions: Set<String>
get() = definitionMap.keys
/**
* The implementation inherited from this context's parent.
*/
val inheritedNumberImplementation: NumberImplementation?
by ChainSearchDelegate { numberImplementation}
/**
* The reducer inherited from this context's parent.
*/
val inheritedReducer: Reducer<NumberInterface>?
by ChainSearchDelegate { reducer }
/**
* The set of all variables in this context and its parents.
*/
val inheritedVariables: Set<String> by ChainAccumulateDelegate { variables }
/**
* The set of all definition in this context and its parents.
*/
val inheritedDefinitions: Set<String> by ChainAccumulateDelegate { definitions }
/**
* Create a new child instance of this context that is mutable.
* @return the new child instance.
*/
fun mutableSubInstance(): MutableEvaluationContext = MutableEvaluationContext(this)
/**
* Gets a variable stored in this context.
*/
fun getVariable(name: String): NumberInterface? {
return variableMap[name] ?: parent?.getVariable(name)
}
/**
* Gets the definition stored in this context.
*/
fun getDefinition(name: String): TreeNode? {
return definitionMap[name] ?: parent?.getDefinition(name)
}
}

View File

@ -0,0 +1,69 @@
package org.nwapw.abacus.context
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.plugin.NumberImplementation
import org.nwapw.abacus.tree.Reducer
import org.nwapw.abacus.tree.TreeNode
/**
* A reduction context that is mutable.
* @param parent the parent of this context.
* @param numberImplementation the number implementation used in this context.
* @param reducer the reducer used in this context
*/
class MutableEvaluationContext(parent: EvaluationContext? = null,
numberImplementation: NumberImplementation? = null,
reducer: Reducer<NumberInterface>? = null) :
EvaluationContext(parent, numberImplementation, reducer) {
override var numberImplementation: NumberImplementation? = super.numberImplementation
override var reducer: Reducer<NumberInterface>? = super.reducer
/**
* Writes data stored in the [other] context over data stored in this one.
* @param other the context from which to copy data.
*/
fun apply(other: EvaluationContext) {
if(other.numberImplementation != null) numberImplementation = other.numberImplementation
if(other.reducer != null) reducer = other.reducer
for(name in other.variables) {
setVariable(name, other.getVariable(name) ?: continue)
}
for(name in other.definitions) {
setDefinition(name, other.getDefinition(name) ?: continue)
}
}
/**
* Sets a variable to a certain [value].
* @param name the name of the variable.
* @param value the value of the variable.
*/
fun setVariable(name: String, value: NumberInterface) {
variableMap[name] = value
}
/**
* Set a definition to a certain [value].
* @param name the name of the definition.
* @param value the value of the definition.
*/
fun setDefinition(name: String, value: TreeNode) {
definitionMap[name] = value
}
/**
* Clears the variables defined in this context.
*/
fun clearVariables(){
variableMap.clear()
}
/**
* Clears the definitions defined in this context.
*/
fun clearDefinitions(){
definitionMap.clear()
}
}

View File

@ -1,6 +1,6 @@
package org.nwapw.abacus.function
import org.nwapw.abacus.function.applicable.ReducerApplicable
import org.nwapw.abacus.function.applicable.Applicable
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.tree.TreeNode
@ -10,4 +10,4 @@ import org.nwapw.abacus.tree.TreeNode
* A function that operates on parse tree nodes instead of on already simplified numbers.
* Despite this, it returns a number, not a tree.
*/
abstract class TreeValueFunction : ReducerApplicable<TreeNode, NumberInterface, NumberInterface>
abstract class TreeValueFunction : Applicable<TreeNode, NumberInterface>

View File

@ -1,6 +1,6 @@
package org.nwapw.abacus.function
import org.nwapw.abacus.function.applicable.ReducerApplicable
import org.nwapw.abacus.function.applicable.Applicable
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.tree.TreeNode
@ -15,4 +15,4 @@ import org.nwapw.abacus.tree.TreeNode
abstract class TreeValueOperator(associativity: OperatorAssociativity, type: OperatorType,
precedence: Int) :
Operator(associativity, type, precedence),
ReducerApplicable<TreeNode, NumberInterface, NumberInterface>
Applicable<TreeNode, NumberInterface>

View File

@ -1,7 +1,7 @@
package org.nwapw.abacus.function.applicable
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.function.DomainException
import org.nwapw.abacus.plugin.NumberImplementation
/**
* A class that can be applied to arguments.
@ -18,7 +18,7 @@ interface Applicable<in T : Any, out O : Any> {
* @param params the parameter array to verify for compatibility.
* @return whether the array can be used with applyInternal.
*/
fun matchesParams(implementation: NumberImplementation, params: Array<out T>): Boolean
fun matchesParams(context: MutableEvaluationContext, params: Array<out T>): Boolean
/**
* Applies the applicable object to the given parameters,
@ -26,7 +26,7 @@ interface Applicable<in T : Any, out O : Any> {
* @param params the parameters to apply to.
* @return the result of the application.
*/
fun applyInternal(implementation: NumberImplementation, params: Array<out T>): O
fun applyInternal(context: MutableEvaluationContext, params: Array<out T>): O
/**
* If the parameters can be used with this applicable, returns
@ -35,9 +35,9 @@ interface Applicable<in T : Any, out O : Any> {
* @param params the parameters to apply to.
* @return the result of the operation, or null if parameters do not match.
*/
fun apply(implementation: NumberImplementation, vararg params: T): O {
if (!matchesParams(implementation, params)) throw DomainException()
return applyInternal(implementation, params)
fun apply(context: MutableEvaluationContext, vararg params: T): O {
if (!matchesParams(context, params)) throw DomainException()
return applyInternal(context, params)
}
}

View File

@ -1,45 +0,0 @@
package org.nwapw.abacus.function.applicable
import org.nwapw.abacus.function.DomainException
import org.nwapw.abacus.plugin.NumberImplementation
import org.nwapw.abacus.tree.Reducer
/**
* Applicable that requires a reducer.
*
* ReducerApplicable slightly more specific Applicable that requires a reducer
* to be passed to it along with the parameters.
* @param <T> the type of the input arguments.
* @param <O> the return type of the application.
* @param <R> the required type of the reducer.
*/
interface ReducerApplicable<in T : Any, out O : Any, in R : Any> {
/**
* Checks if this applicable can be applied to the
* given parameters.
* @param params the parameters to check.
*/
fun matchesParams(implementation: NumberImplementation, params: Array<out T>): Boolean
/**
* Applies this applicable to the given arguments, and reducer.
* @param reducer the reducer to use in the application.
* @param params the arguments to apply to.
* @return the result of the application.
*/
fun applyWithReducerInternal(implementation: NumberImplementation, reducer: Reducer<R>, params: Array<out T>): O
/**
* Applies this applicable to the given arguments, and reducer,
* if the arguments and reducer are compatible with this applicable.
* @param reducer the reducer to use in the application.
* @param params the arguments to apply to.
* @return the result of the application, or null if the arguments are incompatible.
*/
fun applyWithReducer(implementation: NumberImplementation, reducer: Reducer<R>, vararg params: T): O {
if (!matchesParams(implementation, params)) throw DomainException()
return applyWithReducerInternal(implementation, reducer, params)
}
}

View File

@ -0,0 +1,6 @@
package org.nwapw.abacus.tree
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.number.NumberInterface
data class EvaluationResult(val value: NumberInterface?, val resultingContext: MutableEvaluationContext)

View File

@ -0,0 +1,63 @@
package org.nwapw.abacus.tree
import org.nwapw.abacus.Abacus
import org.nwapw.abacus.context.EvaluationContext
import org.nwapw.abacus.number.NumberInterface
class NumberReducer(val abacus: Abacus, context: EvaluationContext) : Reducer<NumberInterface> {
val context = context.mutableSubInstance()
init {
this.context.reducer = this
}
override fun reduceNode(treeNode: TreeNode, vararg children: Any): NumberInterface? {
val promotionManager = abacus.promotionManager
return when(treeNode){
is NumberNode -> {
context.inheritedNumberImplementation?.instanceForString(treeNode.number)
}
is VariableNode -> {
val variable = context.getVariable(treeNode.variable)
if(variable != null) return variable
val definition = context.getDefinition(treeNode.variable)
if(definition != null) return definition.reduce(this)
null
}
is NumberUnaryNode -> {
val child = children[0] as NumberInterface
context.numberImplementation = abacus.pluginManager.interfaceImplementationFor(child.javaClass)
abacus.pluginManager.operatorFor(treeNode.operation)
.apply(context, child)
}
is NumberBinaryNode -> {
val left = children[0] as NumberInterface
val right = children[1] as NumberInterface
val promotionResult = promotionManager.promote(left, right) ?: return null
context.numberImplementation = promotionResult.promotedTo
abacus.pluginManager.operatorFor(treeNode.operation).apply(context, *promotionResult.items)
}
is FunctionNode -> {
val promotionResult = promotionManager
.promote(*children.map { it as NumberInterface }.toTypedArray()) ?: return null
context.numberImplementation = promotionResult.promotedTo
abacus.pluginManager.functionFor(treeNode.callTo).apply(context, *promotionResult.items)
}
is TreeValueUnaryNode -> {
abacus.pluginManager.treeValueOperatorFor(treeNode.operation)
.apply(context, treeNode.applyTo ?: return null)
}
is TreeValueBinaryNode -> {
abacus.pluginManager.treeValueOperatorFor(treeNode.operation)
.apply(context, treeNode.left ?: return null, treeNode.right ?: return null)
}
is TreeValueFunctionNode -> {
abacus.pluginManager.treeValueFunctionFor(treeNode.callTo)
.apply(context, *treeNode.children.toTypedArray())
}
else -> null
}
}
}

View File

@ -1,37 +0,0 @@
package org.nwapw.abacus.variables
import org.nwapw.abacus.Abacus
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.plugin.PluginListener
import org.nwapw.abacus.plugin.PluginManager
import org.nwapw.abacus.tree.TreeNode
/**
* A database for variables and definition.
*
* The variable database is used to keep track of
* variables and definitions throughout the calculator.
*
* @property abacus the Abacus instance.
*/
class VariableDatabase(val abacus: Abacus): PluginListener {
/**
* The variables that are stored in the database.
*/
val variables = mutableMapOf<String, NumberInterface>()
/**
* The definitions that are stored in the database.
*/
val definitions = mutableMapOf<String, TreeNode>()
override fun onLoad(manager: PluginManager?) {
}
override fun onUnload(manager: PluginManager?) {
variables.clear()
definitions.clear()
}
}

View File

@ -17,14 +17,14 @@ public class CalculationTests {
@BeforeClass
public static void prepareTests() {
abacus.getPluginManager().addInstantiated(new StandardPlugin(abacus.getPluginManager()));
abacus.getPluginManager().load();
abacus.reload();
}
private void testOutput(String input, String parseOutput, String output) {
TreeNode parsedTree = abacus.parseString(input);
Assert.assertNotNull(parsedTree);
Assert.assertEquals(parsedTree.toString(), parseOutput);
NumberInterface result = abacus.evaluateTree(parsedTree);
NumberInterface result = abacus.evaluateTree(parsedTree).getValue();
Assert.assertNotNull(result);
Assert.assertTrue(result.toString().startsWith(output));
}

View File

@ -5,11 +5,11 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.context.MutableEvaluationContext;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.lexing.pattern.Match;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.parsing.LexerTokenizer;
import org.nwapw.abacus.plugin.NumberImplementation;
import org.nwapw.abacus.plugin.Plugin;
import org.nwapw.abacus.tree.TokenType;
@ -21,12 +21,12 @@ public class TokenizerTests {
private static LexerTokenizer lexerTokenizer = new LexerTokenizer();
private static NumberFunction subtractFunction = new NumberFunction() {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].subtract(params[1]);
}
};
@ -37,26 +37,26 @@ public class TokenizerTests {
0) {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return true;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return subtractFunction.apply(implementation, params);
@Override
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return subtractFunction.apply(context, params);
}
});
registerOperator("-", new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX,
0) {
@Override
public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return true;
}
@Override
public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
return subtractFunction.apply(implementation, params);
@Override
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return subtractFunction.apply(context, params);
}
});
registerFunction("subtract", subtractFunction);
@ -80,7 +80,7 @@ public class TokenizerTests {
public static void prepareTests() {
abacus.getPluginManager().addListener(lexerTokenizer);
abacus.getPluginManager().addInstantiated(testPlugin);
abacus.getPluginManager().load();
abacus.reload();
}
@Test

View File

@ -20,6 +20,7 @@ import org.nwapw.abacus.plugin.ClassFinder;
import org.nwapw.abacus.plugin.PluginListener;
import org.nwapw.abacus.plugin.PluginManager;
import org.nwapw.abacus.plugin.StandardPlugin;
import org.nwapw.abacus.tree.EvaluationResult;
import org.nwapw.abacus.tree.TreeNode;
import java.io.File;
@ -144,12 +145,14 @@ public class AbacusController implements PluginListener {
if (constructedTree == null) {
return ERR_SYNTAX;
}
NumberInterface evaluatedNumber = abacus.evaluateTree(constructedTree);
EvaluationResult result = abacus.evaluateTree(constructedTree);
NumberInterface evaluatedNumber = result.getValue();
if (evaluatedNumber == null) {
return ERR_EVAL;
}
String resultingString = evaluatedNumber.toString();
historyData.add(new HistoryModel(inputField.getText(), constructedTree.toString(), resultingString));
abacus.applyToContext(result.getResultingContext());
return resultingString;
} catch (ComputationInterruptedException exception) {
return ERR_STOP;
@ -320,13 +323,13 @@ public class AbacusController implements PluginListener {
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
abacusPluginManager.reload();
abacus.reload();
}
@FXML
public void performReload() {
alertIfApplyNeeded(true);
abacus.getPluginManager().reload();
abacus.reload();
}
@FXML

View File

@ -54,7 +54,7 @@
<TextField fx:id="computationLimitField" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<FlowPane GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="3" hgap="10"
vgap="10">
<Button text="Apply" onAction="#performSave"/>
<Button text="Save" onAction="#performSave"/>
<Button text="Reload Plugins" onAction="#performReload"/>
<Button text="Apply and Reload" onAction="#performSaveAndReload"/>
<Button text="Scan Plugins" onAction="#performScan"/>