diff --git a/core/src/main/java/org/nwapw/abacus/exception/ContextException.java b/core/src/main/java/org/nwapw/abacus/exception/ContextException.java new file mode 100644 index 0000000..dd2db0e --- /dev/null +++ b/core/src/main/java/org/nwapw/abacus/exception/ContextException.java @@ -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); + } + +} diff --git a/core/src/main/java/org/nwapw/abacus/exception/NumberReducerException.java b/core/src/main/java/org/nwapw/abacus/exception/NumberReducerException.java new file mode 100644 index 0000000..694e0bf --- /dev/null +++ b/core/src/main/java/org/nwapw/abacus/exception/NumberReducerException.java @@ -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); + } + +} diff --git a/core/src/main/java/org/nwapw/abacus/exception/ParseException.java b/core/src/main/java/org/nwapw/abacus/exception/ParseException.java new file mode 100644 index 0000000..cd394a0 --- /dev/null +++ b/core/src/main/java/org/nwapw/abacus/exception/ParseException.java @@ -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); + } + +} diff --git a/core/src/main/java/org/nwapw/abacus/exception/EvaluationException.java b/core/src/main/java/org/nwapw/abacus/exception/ReductionException.java similarity index 51% rename from core/src/main/java/org/nwapw/abacus/exception/EvaluationException.java rename to core/src/main/java/org/nwapw/abacus/exception/ReductionException.java index 5eed066..d9173a6 100644 --- a/core/src/main/java/org/nwapw/abacus/exception/EvaluationException.java +++ b/core/src/main/java/org/nwapw/abacus/exception/ReductionException.java @@ -1,16 +1,14 @@ package org.nwapw.abacus.exception; /** - * 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. + * An exception thrown from TreeReducers. */ -public class EvaluationException extends AbacusException { +public class ReductionException extends AbacusException { /** * Creates a new EvaluationException with the default string. */ - public EvaluationException() { + public ReductionException() { this(""); } @@ -18,7 +16,7 @@ public class EvaluationException extends AbacusException { * Creates a new EvaluationError with the given message string. * @param message the message string. */ - public EvaluationException(String message) { + public ReductionException(String message) { super("Evaluation error", message); } diff --git a/core/src/main/java/org/nwapw/abacus/exception/TokenizeException.java b/core/src/main/java/org/nwapw/abacus/exception/TokenizeException.java new file mode 100644 index 0000000..df92ca1 --- /dev/null +++ b/core/src/main/java/org/nwapw/abacus/exception/TokenizeException.java @@ -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); + } + +} diff --git a/core/src/main/java/org/nwapw/abacus/parsing/LexerTokenizer.java b/core/src/main/java/org/nwapw/abacus/parsing/LexerTokenizer.java index be57744..1096abb 100644 --- a/core/src/main/java/org/nwapw/abacus/parsing/LexerTokenizer.java +++ b/core/src/main/java/org/nwapw/abacus/parsing/LexerTokenizer.java @@ -1,5 +1,6 @@ package org.nwapw.abacus.parsing; +import org.nwapw.abacus.exception.TokenizeException; import org.nwapw.abacus.lexing.Lexer; import org.nwapw.abacus.lexing.pattern.Match; import org.nwapw.abacus.lexing.pattern.Pattern; @@ -42,7 +43,9 @@ public class LexerTokenizer implements Tokenizer>, PluginListen @Override public List> tokenizeString(String string) { - return lexer.lexAll(string, 0, TOKEN_SORTER); + List> tokens = lexer.lexAll(string, 0, TOKEN_SORTER); + if(tokens == null) throw new TokenizeException(); + return tokens; } @Override diff --git a/core/src/main/java/org/nwapw/abacus/parsing/ShuntingYardParser.java b/core/src/main/java/org/nwapw/abacus/parsing/ShuntingYardParser.java index 611f5c9..f405aa9 100644 --- a/core/src/main/java/org/nwapw/abacus/parsing/ShuntingYardParser.java +++ b/core/src/main/java/org/nwapw/abacus/parsing/ShuntingYardParser.java @@ -1,5 +1,6 @@ package org.nwapw.abacus.parsing; +import org.nwapw.abacus.exception.ParseException; import org.nwapw.abacus.function.Operator; import org.nwapw.abacus.function.OperatorAssociativity; import org.nwapw.abacus.function.OperatorType; @@ -99,7 +100,7 @@ public class ShuntingYardParser implements Parser>, PluginListe while (!tokenStack.empty() && tokenStack.peek().getType() != TokenType.OPEN_PARENTH) { output.add(tokenStack.pop()); } - if (tokenStack.empty()) return null; + if (tokenStack.empty()) throw new ParseException("mismatched parentheses"); if (matchType == TokenType.CLOSE_PARENTH) { tokenStack.pop(); } @@ -111,7 +112,7 @@ public class ShuntingYardParser implements Parser>, PluginListe if (!(newMatchType == TokenType.OP || newMatchType == TokenType.TREE_VALUE_OP || newMatchType == TokenType.FUNCTION || - newMatchType == TokenType.TREE_VALUE_FUNCTION)) return null; + newMatchType == TokenType.TREE_VALUE_FUNCTION)) throw new ParseException("mismatched parentheses"); output.add(tokenStack.pop()); } return output; @@ -124,7 +125,7 @@ public class ShuntingYardParser implements Parser>, PluginListe * @return the construct tree expression. */ public TreeNode constructRecursive(List> matches) { - if (matches.size() == 0) return null; + if (matches.size() == 0) throw new ParseException("no tokens left in input"); Match match = matches.remove(0); TokenType matchType = match.getType(); if (matchType == TokenType.OP || matchType == TokenType.TREE_VALUE_OP) { @@ -133,7 +134,6 @@ public class ShuntingYardParser implements Parser>, PluginListe if (type == OperatorType.BINARY_INFIX) { TreeNode right = constructRecursive(matches); TreeNode left = constructRecursive(matches); - if (left == null || right == null) return null; if (matchType == TokenType.OP) { return new NumberBinaryNode(operator, left, right); } else { @@ -141,7 +141,6 @@ public class ShuntingYardParser implements Parser>, PluginListe } } else { TreeNode applyTo = constructRecursive(matches); - if (applyTo == null) return null; if (matchType == TokenType.OP) { return new NumberUnaryNode(operator, applyTo); } else { @@ -157,10 +156,9 @@ public class ShuntingYardParser implements Parser>, PluginListe List children = new ArrayList<>(); while (!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END) { TreeNode argument = constructRecursive(matches); - if (argument == null) return null; children.add(0, argument); } - if (matches.isEmpty()) return null; + if (matches.isEmpty()) throw new ParseException("incorrectly formatted function call"); matches.remove(0); CallNode node; if (matchType == TokenType.FUNCTION) { @@ -170,16 +168,17 @@ public class ShuntingYardParser implements Parser>, PluginListe } return node; } - return null; + throw new ParseException("unrecognized token"); } @Override public TreeNode constructTree(List> tokens) { + if (tokens.isEmpty()) throw new ParseException("no input tokens"); tokens = intoPostfix(new ArrayList<>(tokens)); - if (tokens == null) return null; Collections.reverse(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 diff --git a/core/src/main/java/org/nwapw/abacus/parsing/TreeBuilder.java b/core/src/main/java/org/nwapw/abacus/parsing/TreeBuilder.java index b3d597e..78c2331 100644 --- a/core/src/main/java/org/nwapw/abacus/parsing/TreeBuilder.java +++ b/core/src/main/java/org/nwapw/abacus/parsing/TreeBuilder.java @@ -42,9 +42,7 @@ public class TreeBuilder { * @return the resulting tree. */ public TreeNode fromString(String input) { - List tokens = tokenizer.tokenizeString(input); - if (tokens == null) return null; - return parser.constructTree(tokens); + return parser.constructTree(tokenizer.tokenizeString(input)); } } diff --git a/core/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java b/core/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java index 3d20019..9d95303 100755 --- a/core/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java +++ b/core/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java @@ -332,7 +332,7 @@ public class StandardPlugin extends Plugin { //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. //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 one = implementation.instanceForString("1"); int n = 0; @@ -715,7 +715,7 @@ public class StandardPlugin extends Plugin { * @return the value of the series */ 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(); int n = 1; do { diff --git a/core/src/main/kotlin/org/nwapw/abacus/Abacus.kt b/core/src/main/kotlin/org/nwapw/abacus/Abacus.kt index 4b78256..4272aa8 100644 --- a/core/src/main/kotlin/org/nwapw/abacus/Abacus.kt +++ b/core/src/main/kotlin/org/nwapw/abacus/Abacus.kt @@ -88,7 +88,7 @@ class Abacus(val configuration: Configuration) { * @param input the input string to parse * @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. * diff --git a/core/src/main/kotlin/org/nwapw/abacus/context/ChainSearchDelegate.kt b/core/src/main/kotlin/org/nwapw/abacus/context/ChainSearchDelegate.kt index b1245b9..7f4e21a 100644 --- a/core/src/main/kotlin/org/nwapw/abacus/context/ChainSearchDelegate.kt +++ b/core/src/main/kotlin/org/nwapw/abacus/context/ChainSearchDelegate.kt @@ -1,5 +1,6 @@ package org.nwapw.abacus.context +import org.nwapw.abacus.exception.ContextException import kotlin.reflect.KProperty /** @@ -16,14 +17,14 @@ import kotlin.reflect.KProperty */ class ChainSearchDelegate(private val valueGetter: EvaluationContext.() -> V?) { - operator fun getValue(selfRef: Any, property: KProperty<*>): V? { - var currentRef = selfRef as? EvaluationContext ?: return null + operator fun getValue(selfRef: Any, property: KProperty<*>): V { + var currentRef = selfRef as EvaluationContext var returnedValue = currentRef.valueGetter() while (returnedValue == null) { currentRef = currentRef.parent ?: break returnedValue = currentRef.valueGetter() } - return returnedValue + return returnedValue ?: throw ContextException("context chain does not contain value") } } \ No newline at end of file diff --git a/core/src/main/kotlin/org/nwapw/abacus/context/EvaluationContext.kt b/core/src/main/kotlin/org/nwapw/abacus/context/EvaluationContext.kt index d4fdadd..293a5ff 100644 --- a/core/src/main/kotlin/org/nwapw/abacus/context/EvaluationContext.kt +++ b/core/src/main/kotlin/org/nwapw/abacus/context/EvaluationContext.kt @@ -43,13 +43,13 @@ open class EvaluationContext(val parent: EvaluationContext? = null, /** * The implementation inherited from this context's parent. */ - val inheritedNumberImplementation: NumberImplementation? - by ChainSearchDelegate { numberImplementation} + val inheritedNumberImplementation: NumberImplementation + by ChainSearchDelegate { numberImplementation } /** * The reducer inherited from this context's parent. */ - val inheritedReducer: Reducer? + val inheritedReducer: Reducer by ChainSearchDelegate { reducer } /** diff --git a/core/src/main/kotlin/org/nwapw/abacus/tree/NumberReducer.kt b/core/src/main/kotlin/org/nwapw/abacus/tree/NumberReducer.kt index 7eff50a..1d9885a 100644 --- a/core/src/main/kotlin/org/nwapw/abacus/tree/NumberReducer.kt +++ b/core/src/main/kotlin/org/nwapw/abacus/tree/NumberReducer.kt @@ -2,7 +2,8 @@ package org.nwapw.abacus.tree import org.nwapw.abacus.Abacus import org.nwapw.abacus.context.EvaluationContext -import org.nwapw.abacus.exception.EvaluationException +import org.nwapw.abacus.exception.NumberReducerException +import org.nwapw.abacus.exception.ReductionException import org.nwapw.abacus.number.NumberInterface class NumberReducer(val abacus: Abacus, context: EvaluationContext) : Reducer { @@ -17,15 +18,14 @@ class NumberReducer(val abacus: Abacus, context: EvaluationContext) : Reducer { - context.inheritedNumberImplementation?.instanceForString(treeNode.number) - ?: throw EvaluationException("no number implementation selected.") + 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) - throw EvaluationException("variable is not defined.") + throw NumberReducerException("variable is not defined.") } is NumberUnaryNode -> { val child = children[0] as NumberInterface @@ -58,7 +58,7 @@ class NumberReducer(val abacus: Abacus, context: EvaluationContext) : Reducer throw EvaluationException("unrecognized tree node.") + else -> throw ReductionException("unrecognized tree node.") } } diff --git a/core/src/test/java/org/nwapw/abacus/tests/CalculationTests.java b/core/src/test/java/org/nwapw/abacus/tests/CalculationTests.java index 1aee10e..b1c0cd4 100755 --- a/core/src/test/java/org/nwapw/abacus/tests/CalculationTests.java +++ b/core/src/test/java/org/nwapw/abacus/tests/CalculationTests.java @@ -22,7 +22,6 @@ public class CalculationTests { 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).getValue(); Assert.assertNotNull(result); @@ -31,7 +30,6 @@ public class CalculationTests { private void testDomainException(String input, String parseOutput) { TreeNode parsedTree = abacus.parseString(input); - Assert.assertNotNull(parsedTree); Assert.assertEquals(parsedTree.toString(), parseOutput); try { abacus.evaluateTree(parsedTree); diff --git a/fx/src/main/java/org/nwapw/abacus/fx/AbacusController.java b/fx/src/main/java/org/nwapw/abacus/fx/AbacusController.java index f903618..25f8632 100644 --- a/fx/src/main/java/org/nwapw/abacus/fx/AbacusController.java +++ b/fx/src/main/java/org/nwapw/abacus/fx/AbacusController.java @@ -13,11 +13,8 @@ import javafx.util.StringConverter; import org.nwapw.abacus.Abacus; import org.nwapw.abacus.config.Configuration; import org.nwapw.abacus.exception.AbacusException; -import org.nwapw.abacus.exception.ComputationInterruptedException; import org.nwapw.abacus.function.Documentation; import org.nwapw.abacus.function.DocumentationType; -import org.nwapw.abacus.exception.DomainException; -import org.nwapw.abacus.exception.EvaluationException; import org.nwapw.abacus.number.*; import org.nwapw.abacus.plugin.ClassFinder; import org.nwapw.abacus.plugin.PluginListener; @@ -145,9 +142,6 @@ public class AbacusController implements PluginListener { private String attemptCalculation() { try { TreeNode constructedTree = abacus.parseString(inputField.getText()); - if (constructedTree == null) { - return ERR_SYNTAX; - } EvaluationResult result = abacus.evaluateTree(constructedTree); NumberInterface evaluatedNumber = result.getValue(); String resultingString = evaluatedNumber.toString();