1
0
mirror of https://github.com/DanilaFe/abacus synced 2026-01-26 08:35:20 +00:00

Compare commits

..

36 Commits

Author SHA1 Message Date
ea4588be44 Add more descriptive message to context exceptions. 2017-09-23 15:43:07 -07:00
31996219ad Switch the Lexer and TreeBuilder to using exceptions. 2017-09-23 15:31:35 -07:00
a3bfc34c1c Throw parse exceptions instead of returning null. 2017-09-22 16:35:08 -07:00
8dc7acd4b3 Add a separate class of exceptions for NumberReducer. 2017-09-22 11:58:19 -07:00
76fcd8ec1c Remove unused elvis. 2017-09-21 23:17:45 -07:00
fbdf2c7e52 Eliminate warnings related to null returns that have been removed. 2017-09-21 23:09:13 -07:00
3057f66e66 Throw the exception instead of returning null. 2017-09-21 23:05:48 -07:00
f385a48aa2 Merge pull request #34 from DanilaFe/number-range
Add a NumberRange utility.
2017-09-20 15:42:05 -07:00
fd3f56aa8f Write some tests for the Ranges. 2017-09-20 15:23:17 -07:00
e364f4e94b Have the NumberInterface provide the Kotlin rangeTo method. 2017-09-20 13:22:18 -07:00
4c94abb18b Create the NumberRangeBuilder utility class. 2017-09-20 13:19:55 -07:00
ba63dd7874 Implement a range that works for NumberInterfaces. 2017-09-20 13:04:20 -07:00
566598b702 Merge pull request #33 from DanilaFe/promotion-fix
Fix a bug that made some same-priority implementations not convert.
2017-09-20 12:56:12 -07:00
eb91a5b875 Fix a bug that made some same-priority implementations not convert. 2017-09-20 12:47:30 -07:00
fcd4694203 Merge pull request #32 from DanilaFe/promotion-exception
Add an exception thrown when promotion fails.
2017-09-20 12:16:00 -07:00
566831246c Add an exception thrown when promotion fails. 2017-09-20 12:06:06 -07:00
ad8a0a9b2a Merge pull request #29 from DanilaFe/fixes
Revert "Remove unnecessary nullability from parseString."
2017-09-16 03:05:20 -07:00
e430e738cf Merge branch 'master' into fixes 2017-09-16 03:03:52 -07:00
f6e326e0f1 Revert "Remove unnecessary nullability from parseString."
88e3bb7109
2017-09-16 03:02:35 -07:00
07581557c7 Merge pull request #27 from DanilaFe/fixes
Fix a number of small issues not worthy of their own branches.
2017-09-16 01:26:40 -07:00
14ac9c67f4 Implement the comparable interface. 2017-09-16 00:18:43 -07:00
0ff071e212 Add a more complete .gitignore 2017-09-16 00:17:03 -07:00
88e3bb7109 Remove unnecessary nullability from parseString. 2017-09-16 00:16:48 -07:00
540e5d6099 Load default implementation if one is not found. 2017-09-16 00:16:32 -07:00
c9e93d87a2 Merge pull request #23 from DanilaFe/thread-safety
Make some parts of the code more thread safe.
2017-09-15 22:59:42 -07:00
337edd68fa Merge branch 'master' into thread-safety 2017-09-15 22:58:33 -07:00
08967fbb8f Merge pull request #22 from DanilaFe/less-null
Remove some null-heavy parts of the code.
2017-09-15 22:56:54 -07:00
46f78bb2ed Merge pull request #21 from DanilaFe/context
Implement a Context system which allows concurrent creation of variables.
2017-09-15 22:51:56 -07:00
5b4773dee1 Do not use null in exceptions and add messages to exceptions. 2017-09-11 19:32:57 -07:00
be94394a5c Catch one exception. 2017-09-11 19:32:57 -07:00
45de25cd50 Move exceptions to their own package and subclass one class. 2017-09-11 19:32:57 -07:00
52ab357fe1 Remove nullability from reduction. 2017-09-11 19:32:57 -07:00
1575d3e574 Remove nullability from tree nodes. 2017-09-11 19:32:57 -07:00
87529da15f Precompute all the transitions ahead of time. 2017-09-11 19:32:51 -07:00
7cd117dac1 Add synchronization to the Standard plugin. 2017-09-11 19:32:51 -07:00
8975bfdb99 Precompute Pi, and do not store documentation on access. 2017-09-11 19:32:51 -07:00
42 changed files with 428 additions and 168 deletions

4
.gitignore vendored
View File

@@ -24,7 +24,9 @@ hs_err_pid*
# Custom Stuff # Custom Stuff
# Gradle # Gradle
.gradle/* .gradle/*
build/* **/build/*
**/out/**
**/.DS_Store
# IntelliJ # IntelliJ
.idea/* .idea/*

View File

@@ -0,0 +1,9 @@
package org.nwapw.abacus.exception;
public class AbacusException extends RuntimeException {
public AbacusException(String baseMessage, String description){
super(baseMessage + ((description.equals("")) ? "." : (": " + description)));
}
}

View File

@@ -1,16 +1,16 @@
package org.nwapw.abacus.number; package org.nwapw.abacus.exception;
/** /**
* Exception thrown when the computation is interrupted by * Exception thrown when the computation is interrupted by
* the user. * the user.
*/ */
public class ComputationInterruptedException extends RuntimeException { public class ComputationInterruptedException extends AbacusException {
/** /**
* Creates a new exception of this type. * Creates a new exception of this type.
*/ */
public ComputationInterruptedException() { public ComputationInterruptedException() {
super("Computation interrupted by user."); super("Computation interrupted", "");
} }
} }

View File

@@ -0,0 +1,24 @@
package org.nwapw.abacus.exception;
/**
* Exception thrown by the Context in cases where lookup fails
* where it should not.
*/
public class ContextException extends AbacusException {
/**
* Creates a new ContextException without an extra message.
*/
public ContextException() {
this("");
}
/**
* Creates a ContextException with the given message.
* @param message the message to use.
*/
public ContextException(String message){
super("Context exception", message);
}
}

View File

@@ -1,24 +1,24 @@
package org.nwapw.abacus.function; package org.nwapw.abacus.exception;
/** /**
* Exception thrown if the function parameters do not match * Exception thrown if the function parameters do not match
* requirements. * requirements.
*/ */
public class DomainException extends RuntimeException { public class DomainException extends AbacusException {
/** /**
* Creates a new DomainException. * Creates a new DomainException.
* @param reason the reason for which the exception is thrown. * @param reason the reason for which the exception is thrown.
*/ */
public DomainException(String reason) { public DomainException(String reason) {
super(reason); super("Domain error", reason);
} }
/** /**
* Creates a new DomainException with a default message. * Creates a new DomainException with a default message.
*/ */
public DomainException(){ public DomainException(){
this("Domain Error"); this("");
} }
} }

View File

@@ -0,0 +1,25 @@
package org.nwapw.abacus.exception;
/**
* Exception thrown by the NumberReducer if something goes wrong when
* transforming a parse tree into a single value.
*/
public class NumberReducerException extends AbacusException {
/**
* Creates a new NumberReducerException with
* no additional message.
*/
public NumberReducerException() {
this("");
}
/**
* Creates a new NumberReducerException with the given message.
* @param message the message.
*/
public NumberReducerException(String message) {
super("Error evaluating expression", message);
}
}

View File

@@ -0,0 +1,23 @@
package org.nwapw.abacus.exception;
/**
* Exception thrown by parsers.
*/
public class ParseException extends AbacusException {
/**
* Creates a new ParseException with no additional message.
*/
public ParseException(){
this("");
}
/**
* Creates a new ParseException with the given additional message.
* @param message the message.
*/
public ParseException(String message){
super("Failed to parse string", message);
}
}

View File

@@ -0,0 +1,24 @@
package org.nwapw.abacus.exception;
/**
* Exception thrown when a promotion fails.
*/
public class PromotionException extends AbacusException {
/**
* Creates a new PromotionException with the default message
* and no additional information.
*/
public PromotionException() {
this("");
}
/**
* Creates a new PromotionException with the given additional message.
* @param message the additional message to include with the error.
*/
public PromotionException(String message) {
super("Failed to promote number instances", message);
}
}

View File

@@ -0,0 +1,23 @@
package org.nwapw.abacus.exception;
/**
* An exception thrown from TreeReducers.
*/
public class ReductionException extends AbacusException {
/**
* Creates a new EvaluationException with the default string.
*/
public ReductionException() {
this("");
}
/**
* Creates a new EvaluationError with the given message string.
* @param message the message string.
*/
public ReductionException(String message) {
super("Evaluation error", message);
}
}

View File

@@ -0,0 +1,23 @@
package org.nwapw.abacus.exception;
/**
* Exception thrown by Lexers when they are unable to tokenize the input string.
*/
public class TokenizeException extends AbacusException {
/**
* Create a new tokenize exception with no additional data.
*/
public TokenizeException() {
this("");
}
/**
* Create a new tokenize exception with the given message.
* @param message the message to use.
*/
public TokenizeException(String message){
super("Failed to tokenize string", message);
}
}

View File

@@ -1,25 +0,0 @@
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

@@ -1,9 +1,11 @@
package org.nwapw.abacus.number; package org.nwapw.abacus.number;
import org.nwapw.abacus.exception.ComputationInterruptedException;
/** /**
* An interface used to represent a number. * An interface used to represent a number.
*/ */
public abstract class NumberInterface { public abstract class NumberInterface implements Comparable<NumberInterface> {
/** /**
* Check if the thread was interrupted and * Check if the thread was interrupted and
@@ -156,14 +158,6 @@ public abstract class NumberInterface {
return intPowInternal(exponent); return intPowInternal(exponent);
} }
/**
* Compares this number to another.
*
* @param number the number to compare to.
* @return same as Integer.compare();
*/
public abstract int compareTo(NumberInterface number);
/** /**
* Same as Math.signum(). * Same as Math.signum().
* *
@@ -244,4 +238,16 @@ public abstract class NumberInterface {
*/ */
public abstract NumberInterface getMaxError(); public abstract NumberInterface getMaxError();
/**
* Returns a NumberRangeBuilder object, which is used to create a range.
* The reason that this returns a builder and not an actual range is that
* the NumberRange needs to promote values passed to it, which
* requires an abacus instance.
* @param other the value at the bottom of the range.
* @return the resulting range builder.
*/
public NumberRangeBuilder rangeTo(NumberInterface other){
return new NumberRangeBuilder(this, other);
}
} }

View File

@@ -1,5 +1,6 @@
package org.nwapw.abacus.parsing; package org.nwapw.abacus.parsing;
import org.nwapw.abacus.exception.TokenizeException;
import org.nwapw.abacus.lexing.Lexer; import org.nwapw.abacus.lexing.Lexer;
import org.nwapw.abacus.lexing.pattern.Match; import org.nwapw.abacus.lexing.pattern.Match;
import org.nwapw.abacus.lexing.pattern.Pattern; import org.nwapw.abacus.lexing.pattern.Pattern;
@@ -42,7 +43,9 @@ public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListen
@Override @Override
public List<Match<TokenType>> tokenizeString(String string) { public List<Match<TokenType>> tokenizeString(String string) {
return lexer.lexAll(string, 0, TOKEN_SORTER); List<Match<TokenType>> tokens = lexer.lexAll(string, 0, TOKEN_SORTER);
if(tokens == null) throw new TokenizeException();
return tokens;
} }
@Override @Override

View File

@@ -1,5 +1,6 @@
package org.nwapw.abacus.parsing; package org.nwapw.abacus.parsing;
import org.nwapw.abacus.exception.ParseException;
import org.nwapw.abacus.function.Operator; import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.function.OperatorAssociativity; import org.nwapw.abacus.function.OperatorAssociativity;
import org.nwapw.abacus.function.OperatorType; import org.nwapw.abacus.function.OperatorType;
@@ -99,7 +100,7 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
while (!tokenStack.empty() && tokenStack.peek().getType() != TokenType.OPEN_PARENTH) { while (!tokenStack.empty() && tokenStack.peek().getType() != TokenType.OPEN_PARENTH) {
output.add(tokenStack.pop()); output.add(tokenStack.pop());
} }
if (tokenStack.empty()) return null; if (tokenStack.empty()) throw new ParseException("mismatched parentheses");
if (matchType == TokenType.CLOSE_PARENTH) { if (matchType == TokenType.CLOSE_PARENTH) {
tokenStack.pop(); tokenStack.pop();
} }
@@ -111,7 +112,7 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
if (!(newMatchType == TokenType.OP || if (!(newMatchType == TokenType.OP ||
newMatchType == TokenType.TREE_VALUE_OP || newMatchType == TokenType.TREE_VALUE_OP ||
newMatchType == TokenType.FUNCTION || newMatchType == TokenType.FUNCTION ||
newMatchType == TokenType.TREE_VALUE_FUNCTION)) return null; newMatchType == TokenType.TREE_VALUE_FUNCTION)) throw new ParseException("mismatched parentheses");
output.add(tokenStack.pop()); output.add(tokenStack.pop());
} }
return output; return output;
@@ -124,7 +125,7 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
* @return the construct tree expression. * @return the construct tree expression.
*/ */
public TreeNode constructRecursive(List<Match<TokenType>> matches) { public TreeNode constructRecursive(List<Match<TokenType>> matches) {
if (matches.size() == 0) return null; if (matches.size() == 0) throw new ParseException("no tokens left in input");
Match<TokenType> match = matches.remove(0); Match<TokenType> match = matches.remove(0);
TokenType matchType = match.getType(); TokenType matchType = match.getType();
if (matchType == TokenType.OP || matchType == TokenType.TREE_VALUE_OP) { if (matchType == TokenType.OP || matchType == TokenType.TREE_VALUE_OP) {
@@ -133,7 +134,6 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
if (type == OperatorType.BINARY_INFIX) { if (type == OperatorType.BINARY_INFIX) {
TreeNode right = constructRecursive(matches); TreeNode right = constructRecursive(matches);
TreeNode left = constructRecursive(matches); TreeNode left = constructRecursive(matches);
if (left == null || right == null) return null;
if (matchType == TokenType.OP) { if (matchType == TokenType.OP) {
return new NumberBinaryNode(operator, left, right); return new NumberBinaryNode(operator, left, right);
} else { } else {
@@ -141,7 +141,6 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
} }
} else { } else {
TreeNode applyTo = constructRecursive(matches); TreeNode applyTo = constructRecursive(matches);
if (applyTo == null) return null;
if (matchType == TokenType.OP) { if (matchType == TokenType.OP) {
return new NumberUnaryNode(operator, applyTo); return new NumberUnaryNode(operator, applyTo);
} else { } else {
@@ -154,31 +153,32 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
return new VariableNode(match.getContent()); return new VariableNode(match.getContent());
} else if (matchType == TokenType.FUNCTION || matchType == TokenType.TREE_VALUE_FUNCTION) { } else if (matchType == TokenType.FUNCTION || matchType == TokenType.TREE_VALUE_FUNCTION) {
String functionName = match.getContent(); String functionName = match.getContent();
CallNode node; List<TreeNode> children = new ArrayList<>();
if (matchType == TokenType.FUNCTION) {
node = new FunctionNode(functionName);
} else {
node = new TreeValueFunctionNode(functionName);
}
while (!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END) { while (!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END) {
TreeNode argument = constructRecursive(matches); TreeNode argument = constructRecursive(matches);
if (argument == null) return null; children.add(0, argument);
node.getChildren().add(0, argument);
} }
if (matches.isEmpty()) return null; if (matches.isEmpty()) throw new ParseException("incorrectly formatted function call");
matches.remove(0); matches.remove(0);
CallNode node;
if (matchType == TokenType.FUNCTION) {
node = new FunctionNode(functionName, children);
} else {
node = new TreeValueFunctionNode(functionName, children);
}
return node; return node;
} }
return null; throw new ParseException("unrecognized token");
} }
@Override @Override
public TreeNode constructTree(List<Match<TokenType>> tokens) { public TreeNode constructTree(List<Match<TokenType>> tokens) {
if (tokens.isEmpty()) throw new ParseException("no input tokens");
tokens = intoPostfix(new ArrayList<>(tokens)); tokens = intoPostfix(new ArrayList<>(tokens));
if (tokens == null) return null;
Collections.reverse(tokens); Collections.reverse(tokens);
TreeNode constructedTree = constructRecursive(tokens); TreeNode constructedTree = constructRecursive(tokens);
return tokens.size() == 0 ? constructedTree : null; if(tokens.size() == 0) return constructedTree;
throw new ParseException("could not parse all input");
} }
@Override @Override

View File

@@ -42,9 +42,7 @@ public class TreeBuilder<T> {
* @return the resulting tree. * @return the resulting tree.
*/ */
public TreeNode fromString(String input) { public TreeNode fromString(String input) {
List<T> tokens = tokenizer.tokenizeString(input); return parser.constructTree(tokenizer.tokenizeString(input));
if (tokens == null) return null;
return parser.constructTree(tokens);
} }
} }

View File

@@ -141,6 +141,7 @@ public class PluginManager {
registeredNumberImplementations.put(name, implementation); registeredNumberImplementations.put(name, implementation);
interfaceImplementationNames.put(implementation.getImplementation(), name); interfaceImplementationNames.put(implementation.getImplementation(), name);
interfaceImplementations.put(implementation.getImplementation(), implementation); interfaceImplementations.put(implementation.getImplementation(), implementation);
cachedPi.put(implementation.getImplementation(), implementation.instanceForPi());
} }
/** /**
@@ -218,10 +219,6 @@ public class PluginManager {
break; break;
} }
} }
if (toReturn == null) {
toReturn = new Documentation(name, "", "", "", type);
registerDocumentation(toReturn);
}
return toReturn; return toReturn;
} }
@@ -252,14 +249,7 @@ public class PluginManager {
* @return pi * @return pi
*/ */
public NumberInterface piFor(Class<? extends NumberInterface> forClass) { public NumberInterface piFor(Class<? extends NumberInterface> forClass) {
if (cachedPi.containsKey(forClass)) return cachedPi.get(forClass); return cachedPi.get(forClass);
NumberImplementation implementation = interfaceImplementationFor(forClass);
NumberInterface generatedPi = null;
if (implementation != null) {
generatedPi = implementation.instanceForPi();
}
cachedPi.put(forClass, generatedPi);
return generatedPi;
} }
/** /**

View File

@@ -29,8 +29,7 @@ public class StandardPlugin extends Plugin {
@Override @Override
public NumberInterface applyInternal(MutableEvaluationContext context, TreeNode[] params) { public NumberInterface applyInternal(MutableEvaluationContext context, TreeNode[] params) {
String assignTo = ((VariableNode) params[0]).getVariable(); String assignTo = ((VariableNode) params[0]).getVariable();
NumberInterface value = params[1].reduce(context.getReducer()); NumberInterface value = params[1].reduce(context.getInheritedReducer());
if(value == null) throw new EvaluationException();
context.setVariable(assignTo, value); context.setVariable(assignTo, value);
return value; return value;
} }
@@ -48,9 +47,7 @@ public class StandardPlugin extends Plugin {
public NumberInterface applyInternal(MutableEvaluationContext context, TreeNode[] params) { public NumberInterface applyInternal(MutableEvaluationContext context, TreeNode[] params) {
String assignTo = ((VariableNode) params[0]).getVariable(); String assignTo = ((VariableNode) params[0]).getVariable();
context.setDefinition(assignTo, params[1]); context.setDefinition(assignTo, params[1]);
NumberInterface value = params[1].reduce(context.getReducer()); return params[1].reduce(context.getInheritedReducer());
if(value == null) throw new EvaluationException();
return value;
} }
}; };
/** /**
@@ -335,7 +332,7 @@ public class StandardPlugin extends Plugin {
//We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n) //We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n)
//In the following, a=1/3^n, b=1/4^n, c = 1/n. //In the following, a=1/3^n, b=1/4^n, c = 1/n.
//a is also an error bound. //a is also an error bound.
NumberInterface a = implementation.instanceForString("1"), b = a, c = a; NumberInterface a = implementation.instanceForString("1"), b = a, c;
NumberInterface sum = implementation.instanceForString("0"); NumberInterface sum = implementation.instanceForString("0");
NumberInterface one = implementation.instanceForString("1"); NumberInterface one = implementation.instanceForString("1");
int n = 0; int n = 0;
@@ -696,8 +693,7 @@ public class StandardPlugin extends Plugin {
* @param n non-negative integer. * @param n non-negative integer.
* @return a number of numClass with value n factorial. * @return a number of numClass with value n factorial.
*/ */
public static NumberInterface factorial(NumberImplementation implementation, int n) { synchronized public static NumberInterface factorial(NumberImplementation implementation, int n) {
if (!FACTORIAL_LISTS.containsKey(implementation)) { if (!FACTORIAL_LISTS.containsKey(implementation)) {
FACTORIAL_LISTS.put(implementation, new ArrayList<>()); FACTORIAL_LISTS.put(implementation, new ArrayList<>());
FACTORIAL_LISTS.get(implementation).add(implementation.instanceForString("1")); FACTORIAL_LISTS.get(implementation).add(implementation.instanceForString("1"));
@@ -719,7 +715,7 @@ public class StandardPlugin extends Plugin {
* @return the value of the series * @return the value of the series
*/ */
private static NumberInterface sinTaylor(MutableEvaluationContext context, NumberInterface x) { private static NumberInterface sinTaylor(MutableEvaluationContext context, NumberInterface x) {
NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x; NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm, sum = x;
NumberInterface maxError = x.getMaxError(); NumberInterface maxError = x.getMaxError();
int n = 1; int n = 1;
do { do {

View File

@@ -68,6 +68,7 @@ class Abacus(val configuration: Configuration) {
pluginManager.reload() pluginManager.reload()
with(mutableContext) { with(mutableContext) {
numberImplementation = pluginManager.numberImplementationFor(configuration.numberImplementation) numberImplementation = pluginManager.numberImplementationFor(configuration.numberImplementation)
?: StandardPlugin.IMPLEMENTATION_NAIVE
clearVariables() clearVariables()
clearDefinitions() clearDefinitions()
} }
@@ -87,7 +88,7 @@ class Abacus(val configuration: Configuration) {
* @param input the input string to parse * @param input the input string to parse
* @return the resulting tree, null if the tree builder or the produced tree are null. * @return the resulting tree, null if the tree builder or the produced tree are null.
*/ */
fun parseString(input: String): TreeNode? = treeBuilder.fromString(input) fun parseString(input: String): TreeNode = treeBuilder.fromString(input)
/** /**
* Evaluates the given tree. * Evaluates the given tree.
* *

View File

@@ -1,5 +1,6 @@
package org.nwapw.abacus.context package org.nwapw.abacus.context
import org.nwapw.abacus.exception.ContextException
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
/** /**
@@ -16,14 +17,14 @@ import kotlin.reflect.KProperty
*/ */
class ChainSearchDelegate<out V>(private val valueGetter: EvaluationContext.() -> V?) { class ChainSearchDelegate<out V>(private val valueGetter: EvaluationContext.() -> V?) {
operator fun getValue(selfRef: Any, property: KProperty<*>): V? { operator fun getValue(selfRef: Any, property: KProperty<*>): V {
var currentRef = selfRef as? EvaluationContext ?: return null var currentRef = selfRef as EvaluationContext
var returnedValue = currentRef.valueGetter() var returnedValue = currentRef.valueGetter()
while (returnedValue == null) { while (returnedValue == null) {
currentRef = currentRef.parent ?: break currentRef = currentRef.parent ?: break
returnedValue = currentRef.valueGetter() returnedValue = currentRef.valueGetter()
} }
return returnedValue return returnedValue ?: throw ContextException("context chain does not contain value")
} }
} }

View File

@@ -43,13 +43,13 @@ open class EvaluationContext(val parent: EvaluationContext? = null,
/** /**
* The implementation inherited from this context's parent. * The implementation inherited from this context's parent.
*/ */
val inheritedNumberImplementation: NumberImplementation? val inheritedNumberImplementation: NumberImplementation
by ChainSearchDelegate { numberImplementation} by ChainSearchDelegate { numberImplementation }
/** /**
* The reducer inherited from this context's parent. * The reducer inherited from this context's parent.
*/ */
val inheritedReducer: Reducer<NumberInterface>? val inheritedReducer: Reducer<NumberInterface>
by ChainSearchDelegate { reducer } by ChainSearchDelegate { reducer }
/** /**

View File

@@ -1,7 +1,7 @@
package org.nwapw.abacus.function.applicable package org.nwapw.abacus.function.applicable
import org.nwapw.abacus.context.MutableEvaluationContext import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.function.DomainException import org.nwapw.abacus.exception.DomainException
/** /**
* A class that can be applied to arguments. * A class that can be applied to arguments.
@@ -36,7 +36,8 @@ interface Applicable<in T : Any, out O : Any> {
* @return the result of the operation, or null if parameters do not match. * @return the result of the operation, or null if parameters do not match.
*/ */
fun apply(context: MutableEvaluationContext, vararg params: T): O { fun apply(context: MutableEvaluationContext, vararg params: T): O {
if (!matchesParams(context, params)) throw DomainException() if (!matchesParams(context, params))
throw DomainException("parameters do not match function requirements.")
return applyInternal(context, params) return applyInternal(context, params)
} }

View File

@@ -0,0 +1,28 @@
package org.nwapw.abacus.number
import org.nwapw.abacus.Abacus
/**
* A closed range designed specifically for [NumberInterface]
*
* Besides providing the usual functionality of a [ClosedRange], this range
* also handles promotion - that is, it's safe to use it with numbers of different
* implementations, even as starting and ending points.
*
* @property abacus the abacus instance used for promotion.
* @property start the starting point of the range.
* @property endInclusive the ending point of the range.
*/
class NumberRange(val abacus: Abacus,
override val start: NumberInterface,
override val endInclusive: NumberInterface): ClosedRange<NumberInterface> {
override operator fun contains(value: NumberInterface): Boolean {
val promotionResult = abacus.promotionManager.promote(start, endInclusive, value)
val newStart = promotionResult.items[0]
val newEnd = promotionResult.items[1]
val newValue = promotionResult.items[2]
return newValue >= newStart && newValue <= newEnd
}
}

View File

@@ -0,0 +1,24 @@
package org.nwapw.abacus.number
import org.nwapw.abacus.Abacus
/**
* A utility class for creating [NumberRange] instances.
*
* Unlike a regular [ClosedRange], a NumberRange must have a third parameter,
* which is the [Abacus] instance that is used for promotion. However, the ".." operator
* is infix, and can only take two parameters. The solution is, instead of returning instances
* of NumberRange directly, to return a builder, which then provides a [with] infix function
* to attach it to an instance of Abacus.
* @property start the beginning of the range.
* @property endInclusive the end of the range.
*/
class NumberRangeBuilder(private val start: NumberInterface, private val endInclusive: NumberInterface) {
/**
* Generate a [NumberRange] with the given instance of [abacus].
* @return a new range with the given instance of Abacus.
*/
infix fun with(abacus: Abacus) = NumberRange(abacus, start, endInclusive)
}

View File

@@ -1,6 +1,7 @@
package org.nwapw.abacus.number package org.nwapw.abacus.number
import org.nwapw.abacus.Abacus import org.nwapw.abacus.Abacus
import org.nwapw.abacus.exception.PromotionException
import org.nwapw.abacus.plugin.NumberImplementation import org.nwapw.abacus.plugin.NumberImplementation
import org.nwapw.abacus.plugin.PluginListener import org.nwapw.abacus.plugin.PluginListener
import org.nwapw.abacus.plugin.PluginManager import org.nwapw.abacus.plugin.PluginManager
@@ -37,21 +38,6 @@ class PromotionManager(val abacus: Abacus) : PluginListener {
return null return null
} }
/**
* If a path between the given implementations has already been computed, uses
* the already calculated path. Otherwise, calls [computePathBetween] to compute a new
* path.
*
* @param from the implementation to start from.
* @param to the implementation to get to.
* @return the resulting promotion path, or null if it is not found
*/
fun getPathBetween(from: NumberImplementation, to: NumberImplementation): PromotionPath? {
return computePaths.computeIfAbsent(from to to, {
computePathBetween(it.first, it.second)
})
}
/** /**
* Promote all the numbers in the list to the same number implementation, to ensure * Promote all the numbers in the list to the same number implementation, to ensure
* they can be used with each other. Finds the highest priority implementation * they can be used with each other. Finds the highest priority implementation
@@ -60,22 +46,30 @@ class PromotionManager(val abacus: Abacus) : PluginListener {
* @param numbers the numbers to promote. * @param numbers the numbers to promote.
* @return the resulting promotion result. * @return the resulting promotion result.
*/ */
fun promote(vararg numbers: NumberInterface): PromotionResult? { fun promote(vararg numbers: NumberInterface): PromotionResult {
val pluginManager = abacus.pluginManager val pluginManager = abacus.pluginManager
val implementations = numbers.map { pluginManager.interfaceImplementationFor(it.javaClass) } val implementations = numbers.map { pluginManager.interfaceImplementationFor(it.javaClass) }
val highestPriority = implementations.sortedBy { it.priority }.last() val highestPriority = implementations.sortedBy { it.priority }.last()
return PromotionResult(items = numbers.map { return PromotionResult(items = numbers.map {
if(it.javaClass == highestPriority.implementation) it if(it.javaClass == highestPriority.implementation) it
else getPathBetween(pluginManager.interfaceImplementationFor(it.javaClass), highestPriority) else computePaths[pluginManager.interfaceImplementationFor(it.javaClass) to highestPriority]
?.promote(it) ?: return null ?.promote(it) ?: throw PromotionException()
}.toTypedArray(), promotedTo = highestPriority) }.toTypedArray(), promotedTo = highestPriority)
} }
override fun onLoad(manager: PluginManager?) { override fun onLoad(manager: PluginManager) {
val implementations = manager.allNumberImplementations.map { manager.numberImplementationFor(it) }
for(first in implementations) {
for(second in implementations) {
val promoteFrom = if(second.priority > first.priority) first else second
val promoteTo = if(second.priority > first.priority) second else first
val path = computePathBetween(promoteFrom, promoteTo)
computePaths[promoteFrom to promoteTo] = path
}
}
} }
override fun onUnload(manager: PluginManager?) { override fun onUnload(manager: PluginManager) {
computePaths.clear() computePaths.clear()
} }

View File

@@ -11,10 +11,10 @@ package org.nwapw.abacus.tree
* @param left the left node. * @param left the left node.
* @param right the right node. * @param right the right node.
*/ */
abstract class BinaryNode(val operation: String, val left: TreeNode? = null, val right: TreeNode?) : TreeNode() { abstract class BinaryNode(val operation: String, val left: TreeNode, val right: TreeNode) : TreeNode() {
override fun toString(): String { override fun toString(): String {
return "(" + (left?.toString() ?: "null") + operation + (right?.toString() ?: "null") + ")" return "(" + left.toString() + operation + right.toString() + ")"
} }
} }

View File

@@ -7,13 +7,9 @@ package org.nwapw.abacus.tree
* to extend this functionality. * to extend this functionality.
* *
* @param callTo the name of the things being called. * @param callTo the name of the things being called.
* @param children the children of this node.
*/ */
abstract class CallNode(val callTo: String) : TreeNode() { abstract class CallNode(val callTo: String, val children: List<TreeNode>) : TreeNode() {
/**
* The list of children this node has.
*/
val children: MutableList<TreeNode> = mutableListOf()
override fun toString(): String { override fun toString(): String {
val buffer = StringBuffer() val buffer = StringBuffer()

View File

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

View File

@@ -8,10 +8,10 @@ package org.nwapw.abacus.tree
* *
* @param function the function string. * @param function the function string.
*/ */
class FunctionNode(function: String) : CallNode(function) { class FunctionNode(function: String, children: List<TreeNode>) : CallNode(function, children) {
override fun <T : Any> reduce(reducer: Reducer<T>): T? { override fun <T : Any> reduce(reducer: Reducer<T>): T {
val children = Array<Any>(children.size, { children[it].reduce(reducer) ?: return null; }) val children = Array<Any>(children.size, { children[it].reduce(reducer) })
return reducer.reduceNode(this, *children) return reducer.reduceNode(this, *children)
} }

View File

@@ -10,12 +10,12 @@ package org.nwapw.abacus.tree
* @param left the left child of this node. * @param left the left child of this node.
* @param right the right child of this node. * @param right the right child of this node.
*/ */
class NumberBinaryNode(operation: String, left: TreeNode?, right: TreeNode?) class NumberBinaryNode(operation: String, left: TreeNode, right: TreeNode)
: BinaryNode(operation, left, right) { : BinaryNode(operation, left, right) {
override fun <T : Any> reduce(reducer: Reducer<T>): T? { override fun <T : Any> reduce(reducer: Reducer<T>): T {
val left = left?.reduce(reducer) ?: return null val left = left.reduce(reducer)
val right = right?.reduce(reducer) ?: return null val right = right.reduce(reducer)
return reducer.reduceNode(this, left, right) return reducer.reduceNode(this, left, right)
} }

View File

@@ -10,7 +10,7 @@ package org.nwapw.abacus.tree
*/ */
class NumberNode(val number: String) : TreeNode() { class NumberNode(val number: String) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? { override fun <T : Any> reduce(reducer: Reducer<T>): T {
return reducer.reduceNode(this) return reducer.reduceNode(this)
} }

View File

@@ -2,6 +2,8 @@ package org.nwapw.abacus.tree
import org.nwapw.abacus.Abacus import org.nwapw.abacus.Abacus
import org.nwapw.abacus.context.EvaluationContext import org.nwapw.abacus.context.EvaluationContext
import org.nwapw.abacus.exception.NumberReducerException
import org.nwapw.abacus.exception.ReductionException
import org.nwapw.abacus.number.NumberInterface import org.nwapw.abacus.number.NumberInterface
class NumberReducer(val abacus: Abacus, context: EvaluationContext) : Reducer<NumberInterface> { class NumberReducer(val abacus: Abacus, context: EvaluationContext) : Reducer<NumberInterface> {
@@ -12,18 +14,18 @@ class NumberReducer(val abacus: Abacus, context: EvaluationContext) : Reducer<Nu
this.context.reducer = this this.context.reducer = this
} }
override fun reduceNode(treeNode: TreeNode, vararg children: Any): NumberInterface? { override fun reduceNode(treeNode: TreeNode, vararg children: Any): NumberInterface {
val promotionManager = abacus.promotionManager val promotionManager = abacus.promotionManager
return when(treeNode){ return when(treeNode){
is NumberNode -> { is NumberNode -> {
context.inheritedNumberImplementation?.instanceForString(treeNode.number) context.inheritedNumberImplementation.instanceForString(treeNode.number)
} }
is VariableNode -> { is VariableNode -> {
val variable = context.getVariable(treeNode.variable) val variable = context.getVariable(treeNode.variable)
if(variable != null) return variable if(variable != null) return variable
val definition = context.getDefinition(treeNode.variable) val definition = context.getDefinition(treeNode.variable)
if(definition != null) return definition.reduce(this) if(definition != null) return definition.reduce(this)
null throw NumberReducerException("variable is not defined.")
} }
is NumberUnaryNode -> { is NumberUnaryNode -> {
val child = children[0] as NumberInterface val child = children[0] as NumberInterface
@@ -34,29 +36,29 @@ class NumberReducer(val abacus: Abacus, context: EvaluationContext) : Reducer<Nu
is NumberBinaryNode -> { is NumberBinaryNode -> {
val left = children[0] as NumberInterface val left = children[0] as NumberInterface
val right = children[1] as NumberInterface val right = children[1] as NumberInterface
val promotionResult = promotionManager.promote(left, right) ?: return null val promotionResult = promotionManager.promote(left, right)
context.numberImplementation = promotionResult.promotedTo context.numberImplementation = promotionResult.promotedTo
abacus.pluginManager.operatorFor(treeNode.operation).apply(context, *promotionResult.items) abacus.pluginManager.operatorFor(treeNode.operation).apply(context, *promotionResult.items)
} }
is FunctionNode -> { is FunctionNode -> {
val promotionResult = promotionManager val promotionResult = promotionManager
.promote(*children.map { it as NumberInterface }.toTypedArray()) ?: return null .promote(*children.map { it as NumberInterface }.toTypedArray())
context.numberImplementation = promotionResult.promotedTo context.numberImplementation = promotionResult.promotedTo
abacus.pluginManager.functionFor(treeNode.callTo).apply(context, *promotionResult.items) abacus.pluginManager.functionFor(treeNode.callTo).apply(context, *promotionResult.items)
} }
is TreeValueUnaryNode -> { is TreeValueUnaryNode -> {
abacus.pluginManager.treeValueOperatorFor(treeNode.operation) abacus.pluginManager.treeValueOperatorFor(treeNode.operation)
.apply(context, treeNode.applyTo ?: return null) .apply(context, treeNode.applyTo)
} }
is TreeValueBinaryNode -> { is TreeValueBinaryNode -> {
abacus.pluginManager.treeValueOperatorFor(treeNode.operation) abacus.pluginManager.treeValueOperatorFor(treeNode.operation)
.apply(context, treeNode.left ?: return null, treeNode.right ?: return null) .apply(context, treeNode.left, treeNode.right)
} }
is TreeValueFunctionNode -> { is TreeValueFunctionNode -> {
abacus.pluginManager.treeValueFunctionFor(treeNode.callTo) abacus.pluginManager.treeValueFunctionFor(treeNode.callTo)
.apply(context, *treeNode.children.toTypedArray()) .apply(context, *treeNode.children.toTypedArray())
} }
else -> null else -> throw ReductionException("unrecognized tree node.")
} }
} }

View File

@@ -8,11 +8,11 @@ package org.nwapw.abacus.tree
* @param operation the operation this node performs. * @param operation the operation this node performs.
* @param child the child this node should be applied to. * @param child the child this node should be applied to.
*/ */
class NumberUnaryNode(operation: String, child: TreeNode?) class NumberUnaryNode(operation: String, child: TreeNode)
: UnaryNode(operation, child) { : UnaryNode(operation, child) {
override fun <T : Any> reduce(reducer: Reducer<T>): T? { override fun <T : Any> reduce(reducer: Reducer<T>): T {
val child = applyTo?.reduce(reducer) ?: return null val child = applyTo.reduce(reducer)
return reducer.reduceNode(this, child) return reducer.reduceNode(this, child)
} }

View File

@@ -14,6 +14,6 @@ interface Reducer<out T> {
* @param treeNode the tree node to reduce. * @param treeNode the tree node to reduce.
* @param children the list of children, of type T. * @param children the list of children, of type T.
*/ */
fun reduceNode(treeNode: TreeNode, vararg children: Any): T? fun reduceNode(treeNode: TreeNode, vararg children: Any): T
} }

View File

@@ -5,6 +5,6 @@ package org.nwapw.abacus.tree
*/ */
abstract class TreeNode { abstract class TreeNode {
abstract fun <T : Any> reduce(reducer: Reducer<T>): T? abstract fun <T : Any> reduce(reducer: Reducer<T>): T
} }

View File

@@ -11,10 +11,10 @@ package org.nwapw.abacus.tree
* @param left the left child of this node. * @param left the left child of this node.
* @param right the right child of this node. * @param right the right child of this node.
*/ */
class TreeValueBinaryNode(operation: String, left: TreeNode?, right: TreeNode?) class TreeValueBinaryNode(operation: String, left: TreeNode, right: TreeNode)
: BinaryNode(operation, left, right) { : BinaryNode(operation, left, right) {
override fun <T : Any> reduce(reducer: Reducer<T>): T? { override fun <T : Any> reduce(reducer: Reducer<T>): T {
return reducer.reduceNode(this) return reducer.reduceNode(this)
} }

View File

@@ -7,9 +7,9 @@ package org.nwapw.abacus.tree
* is mostly to help the reducer. Besides that, this class also does not * is mostly to help the reducer. Besides that, this class also does not
* even attempt to reduce its children. * even attempt to reduce its children.
*/ */
class TreeValueFunctionNode(name: String) : CallNode(name) { class TreeValueFunctionNode(name: String, children: List<TreeNode>) : CallNode(name, children) {
override fun <T : Any> reduce(reducer: Reducer<T>): T? { override fun <T : Any> reduce(reducer: Reducer<T>): T {
return reducer.reduceNode(this) return reducer.reduceNode(this)
} }

View File

@@ -9,11 +9,11 @@ package org.nwapw.abacus.tree
* @param operation the operation this node performs. * @param operation the operation this node performs.
* @param child the node the operation should be applied to. * @param child the node the operation should be applied to.
*/ */
class TreeValueUnaryNode(operation: String, child: TreeNode?) class TreeValueUnaryNode(operation: String, child: TreeNode)
: UnaryNode(operation, child) { : UnaryNode(operation, child) {
override fun <T : Any> reduce(reducer: Reducer<T>): T? { override fun <T : Any> reduce(reducer: Reducer<T>): T {
return reducer.reduceNode(this); return reducer.reduceNode(this)
} }
} }

View File

@@ -9,10 +9,10 @@ package org.nwapw.abacus.tree
* @param operation the operation applied to the given node. * @param operation the operation applied to the given node.
* @param applyTo the node to which the operation will be applied. * @param applyTo the node to which the operation will be applied.
*/ */
abstract class UnaryNode(val operation: String, val applyTo: TreeNode? = null) : TreeNode() { abstract class UnaryNode(val operation: String, val applyTo: TreeNode) : TreeNode() {
override fun toString(): String { override fun toString(): String {
return "(" + (applyTo?.toString() ?: "null") + ")" + operation return "(" + applyTo.toString() + ")" + operation
} }
} }

View File

@@ -10,7 +10,7 @@ package org.nwapw.abacus.tree
*/ */
class VariableNode(val variable: String) : TreeNode() { class VariableNode(val variable: String) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? { override fun <T : Any> reduce(reducer: Reducer<T>): T {
return reducer.reduceNode(this) return reducer.reduceNode(this)
} }

View File

@@ -5,7 +5,7 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.nwapw.abacus.Abacus; import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration; import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.function.DomainException; import org.nwapw.abacus.exception.DomainException;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.plugin.StandardPlugin; import org.nwapw.abacus.plugin.StandardPlugin;
import org.nwapw.abacus.tree.TreeNode; import org.nwapw.abacus.tree.TreeNode;
@@ -22,7 +22,6 @@ public class CalculationTests {
private void testOutput(String input, String parseOutput, String output) { private void testOutput(String input, String parseOutput, String output) {
TreeNode parsedTree = abacus.parseString(input); TreeNode parsedTree = abacus.parseString(input);
Assert.assertNotNull(parsedTree);
Assert.assertEquals(parsedTree.toString(), parseOutput); Assert.assertEquals(parsedTree.toString(), parseOutput);
NumberInterface result = abacus.evaluateTree(parsedTree).getValue(); NumberInterface result = abacus.evaluateTree(parsedTree).getValue();
Assert.assertNotNull(result); Assert.assertNotNull(result);
@@ -31,7 +30,6 @@ public class CalculationTests {
private void testDomainException(String input, String parseOutput) { private void testDomainException(String input, String parseOutput) {
TreeNode parsedTree = abacus.parseString(input); TreeNode parsedTree = abacus.parseString(input);
Assert.assertNotNull(parsedTree);
Assert.assertEquals(parsedTree.toString(), parseOutput); Assert.assertEquals(parsedTree.toString(), parseOutput);
try { try {
abacus.evaluateTree(parsedTree); abacus.evaluateTree(parsedTree);

View File

@@ -0,0 +1,97 @@
package org.nwapw.abacus.tests;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.NumberRange;
import org.nwapw.abacus.number.PreciseNumber;
import org.nwapw.abacus.plugin.StandardPlugin;
import java.util.function.Function;
public class RangeTests {
private static Abacus abacus = new Abacus(new Configuration( "precise", new String[]{}));
private static Function<NumberInterface, NumberInterface> naivePromotion = i -> new NaiveNumber((i.toString()));
private static Function<NumberInterface, NumberInterface> precisePromotion = i -> new PreciseNumber((i.toString()));
@BeforeClass
public static void prepareTests() {
abacus.getPluginManager().addInstantiated(new StandardPlugin(abacus.getPluginManager()));
abacus.reload();
}
public static NumberRange naiveRange(String bottom, String top) {
return new NaiveNumber(bottom).rangeTo(new NaiveNumber(top)).with(abacus);
}
@Test
public void testNaiveRange(){
NumberRange range = naiveRange("0", "10");
Assert.assertTrue(range.getStart().toString().startsWith("0"));
Assert.assertTrue(range.getEndInclusive().toString().startsWith("10"));
}
@Test
public void testNaiveRangeBelow() {
NumberRange range = naiveRange("0", "10");
Assert.assertFalse(range.contains(new NaiveNumber("-10")));
}
@Test
public void testNaiveRangeAbove() {
NumberRange range = naiveRange("0", "10");
Assert.assertFalse(range.contains(new NaiveNumber("20")));
}
@Test
public void testNaiveRangeJustWithinBottom() {
NumberRange range = naiveRange("0", "10");
Assert.assertTrue(range.contains(new NaiveNumber("0")));
}
@Test
public void testNaiveRangeJustWithinTop() {
NumberRange range = naiveRange("0", "10");
Assert.assertTrue(range.contains(new NaiveNumber("10")));
}
@Test
public void testNaiveRangeWithin() {
NumberRange range = naiveRange("0", "10");
Assert.assertTrue(range.contains(new NaiveNumber("5")));
}
public static void addTestPromotionPaths() {
StandardPlugin.IMPLEMENTATION_NAIVE.getPromotionPaths().put("precise", precisePromotion);
StandardPlugin.IMPLEMENTATION_PRECISE.getPromotionPaths().put("naive", naivePromotion);
abacus.reload();
}
public static void removeTestPromotionPaths() {
StandardPlugin.IMPLEMENTATION_NAIVE.getPromotionPaths().remove("precise");
StandardPlugin.IMPLEMENTATION_NAIVE.getPromotionPaths().remove("naive");
abacus.reload();
}
@Test
public void testPromotionWithin() {
addTestPromotionPaths();
NumberRange range = naiveRange("0", "10");
Assert.assertTrue(range.contains(new PreciseNumber("5")));
removeTestPromotionPaths();
}
@Test
public void testPromotionOutside(){
addTestPromotionPaths();
NumberRange range = naiveRange("0","10");
Assert.assertFalse(range.contains(new PreciseNumber("20")));
removeTestPromotionPaths();
}
}

View File

@@ -12,9 +12,9 @@ import javafx.util.Callback;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import org.nwapw.abacus.Abacus; import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration; import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.exception.AbacusException;
import org.nwapw.abacus.function.Documentation; import org.nwapw.abacus.function.Documentation;
import org.nwapw.abacus.function.DocumentationType; import org.nwapw.abacus.function.DocumentationType;
import org.nwapw.abacus.function.DomainException;
import org.nwapw.abacus.number.*; import org.nwapw.abacus.number.*;
import org.nwapw.abacus.plugin.ClassFinder; import org.nwapw.abacus.plugin.ClassFinder;
import org.nwapw.abacus.plugin.PluginListener; import org.nwapw.abacus.plugin.PluginListener;
@@ -142,21 +142,13 @@ public class AbacusController implements PluginListener {
private String attemptCalculation() { private String attemptCalculation() {
try { try {
TreeNode constructedTree = abacus.parseString(inputField.getText()); TreeNode constructedTree = abacus.parseString(inputField.getText());
if (constructedTree == null) {
return ERR_SYNTAX;
}
EvaluationResult result = abacus.evaluateTree(constructedTree); EvaluationResult result = abacus.evaluateTree(constructedTree);
NumberInterface evaluatedNumber = result.getValue(); NumberInterface evaluatedNumber = result.getValue();
if (evaluatedNumber == null) {
return ERR_EVAL;
}
String resultingString = evaluatedNumber.toString(); String resultingString = evaluatedNumber.toString();
historyData.add(new HistoryModel(inputField.getText(), constructedTree.toString(), resultingString)); historyData.add(new HistoryModel(inputField.getText(), constructedTree.toString(), resultingString));
abacus.applyToContext(result.getResultingContext()); abacus.applyToContext(result.getResultingContext());
return resultingString; return resultingString;
} catch (ComputationInterruptedException exception) { } catch (AbacusException exception) {
return ERR_STOP;
} catch (DomainException exception) {
return exception.getMessage(); return exception.getMessage();
} catch (RuntimeException exception) { } catch (RuntimeException exception) {
exception.printStackTrace(); exception.printStackTrace();
@@ -363,7 +355,12 @@ public class AbacusController implements PluginListener {
enabledPlugins.add(plugin); enabledPlugins.add(plugin);
} }
PluginManager pluginManager = abacus.getPluginManager(); PluginManager pluginManager = abacus.getPluginManager();
functionList.addAll(manager.getAllFunctions().stream().map(name -> pluginManager.documentationFor(name, DocumentationType.FUNCTION)) functionList.addAll(manager.getAllFunctions().stream().map(name -> {
Documentation documentationInstance = pluginManager.documentationFor(name, DocumentationType.FUNCTION);
if(documentationInstance == null)
documentationInstance = new Documentation(name, "", "", "", DocumentationType.FUNCTION);
return documentationInstance;
})
.collect(Collectors.toCollection(ArrayList::new))); .collect(Collectors.toCollection(ArrayList::new)));
functionList.addAll(manager.getAllTreeValueFunctions().stream().map(name -> pluginManager.documentationFor(name, DocumentationType.TREE_VALUE_FUNCTION)) functionList.addAll(manager.getAllTreeValueFunctions().stream().map(name -> pluginManager.documentationFor(name, DocumentationType.TREE_VALUE_FUNCTION))
.collect(Collectors.toCollection(ArrayList::new))); .collect(Collectors.toCollection(ArrayList::new)));