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

Compare commits

...

17 Commits

Author SHA1 Message Date
e05b2ac8d5 Move the StandardPlugin into a "standard" package along with the ops. 2017-09-23 22:16:44 -07:00
b4214f5714 Rewrite the parsing interfaces in Kotlin. 2017-09-23 17:14:55 -07:00
bd02749706 Move all the operators into a separate subpackage. 2017-09-23 17:13:56 -07:00
f809183126 Move some more operators out of StandardPlugin.java to separate classes 2017-09-23 17:11:44 -07:00
579ff78a99 Move two more operators into separate classes. 2017-09-23 16:19:45 -07:00
81d0999c11 Switch the basic operators into individual classes. 2017-09-23 15:54:28 -07:00
4fd8f7badf Add operator overloading. 2017-09-23 15:54:28 -07:00
bc475a22f9 Rewrite NumberInterface in Kotlin. 2017-09-23 15:54:28 -07:00
e0ccb67ad3 Merge pull request #37 from DanilaFe/more-exceptions
Replace some more cases where null is used with Exceptions.
2017-09-23 15:53:37 -07:00
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
32 changed files with 780 additions and 562 deletions

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

@@ -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

@@ -1,16 +1,14 @@
package org.nwapw.abacus.exception; package org.nwapw.abacus.exception;
/** /**
* An exception thrown primarily from Tree Value operators and functions, * An exception thrown from TreeReducers.
* which have to deal with the result of a Reducer as well as the results
* of Applicable.
*/ */
public class EvaluationException extends AbacusException { public class ReductionException extends AbacusException {
/** /**
* Creates a new EvaluationException with the default string. * Creates a new EvaluationException with the default string.
*/ */
public EvaluationException() { public ReductionException() {
this(""); this("");
} }
@@ -18,7 +16,7 @@ public class EvaluationException extends AbacusException {
* Creates a new EvaluationError with the given message string. * Creates a new EvaluationError with the given message string.
* @param message the message string. * @param message the message string.
*/ */
public EvaluationException(String message) { public ReductionException(String message) {
super("Evaluation error", 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,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;
@@ -123,8 +124,8 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
* @param matches the list of tokens from the source string. * @param matches the list of tokens from the source string.
* @return the construct tree expression. * @return the construct tree expression.
*/ */
public TreeNode constructRecursive(List<Match<TokenType>> matches) { public TreeNode constructRecursive(List<? extends 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 {
@@ -157,10 +156,9 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
List<TreeNode> children = new ArrayList<>(); List<TreeNode> children = new ArrayList<>();
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); children.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; CallNode node;
if (matchType == TokenType.FUNCTION) { if (matchType == TokenType.FUNCTION) {
@@ -170,16 +168,17 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
} }
return node; return node;
} }
return null; throw new ParseException("unrecognized token");
} }
@Override @Override
public TreeNode constructTree(List<Match<TokenType>> tokens) { public TreeNode constructTree(List<? extends 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

@@ -1,20 +0,0 @@
package org.nwapw.abacus.parsing;
import java.util.List;
/**
* Interface that provides the ability to convert a string into a list of tokens.
*
* @param <T> the type of the tokens produced.
*/
public interface Tokenizer<T> {
/**
* Converts a string into tokens.
*
* @param string the string to convert.
* @return the list of tokens, or null on error.
*/
public List<T> tokenizeString(String string);
}

View File

@@ -1,50 +0,0 @@
package org.nwapw.abacus.parsing;
import org.nwapw.abacus.tree.TreeNode;
import java.util.List;
/**
* TreeBuilder class used to piece together a Tokenizer and
* Parser of the same kind. This is used to essentially avoid
* working with any parameters at all, and the generics
* in this class are used only to ensure the tokenizer and parser
* are of the same type.
*
* @param <T> the type of tokens created by the tokenizer and used by the parser.
*/
public class TreeBuilder<T> {
/**
* The tokenizer used to convert a string into tokens.
*/
private Tokenizer<T> tokenizer;
/**
* The parser used to parse a list of tokens into a tree.
*/
private Parser<T> parser;
/**
* Create a new Tree Builder with the given tokenizer and parser
*
* @param tokenizer the tokenizer to turn strings into tokens
* @param parser the parser to turn tokens into a tree
*/
public TreeBuilder(Tokenizer<T> tokenizer, Parser<T> parser) {
this.tokenizer = tokenizer;
this.parser = parser;
}
/**
* Parse the given string into a tree.
*
* @param input the string to parse into a tree.
* @return the resulting tree.
*/
public TreeNode fromString(String input) {
List<T> tokens = tokenizer.tokenizeString(input);
if (tokens == null) return null;
return parser.constructTree(tokens);
}
}

View File

@@ -1,12 +1,14 @@
package org.nwapw.abacus.plugin; package org.nwapw.abacus.plugin.standard;
import org.nwapw.abacus.context.MutableEvaluationContext; import org.nwapw.abacus.context.MutableEvaluationContext;
import org.nwapw.abacus.function.*; import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NaiveNumber; import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber; import org.nwapw.abacus.number.PreciseNumber;
import org.nwapw.abacus.tree.TreeNode; import org.nwapw.abacus.plugin.NumberImplementation;
import org.nwapw.abacus.tree.VariableNode; import org.nwapw.abacus.plugin.Plugin;
import org.nwapw.abacus.plugin.PluginManager;
import org.nwapw.abacus.plugin.standard.operator.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@@ -20,93 +22,27 @@ public class StandardPlugin extends Plugin {
/** /**
* The set operator. * The set operator.
*/ */
public final TreeValueOperator opSet = new TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) { public static final TreeValueOperator OP_SET = new OperatorSet();
@Override
public boolean matchesParams(MutableEvaluationContext context, TreeNode[] params) {
return params.length == 2 && params[0] instanceof VariableNode;
}
@Override
public NumberInterface applyInternal(MutableEvaluationContext context, TreeNode[] params) {
String assignTo = ((VariableNode) params[0]).getVariable();
NumberInterface value = params[1].reduce(context.getInheritedReducer());
context.setVariable(assignTo, value);
return value;
}
};
/** /**
* The define operator. * The define operator.
*/ */
public final TreeValueOperator opDefine = new TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) { public final TreeValueOperator OP_DEFINE = new OperatorDefine();
@Override
public boolean matchesParams(MutableEvaluationContext context, TreeNode[] params) {
return params.length == 2 && params[0] instanceof VariableNode;
}
@Override
public NumberInterface applyInternal(MutableEvaluationContext context, TreeNode[] params) {
String assignTo = ((VariableNode) params[0]).getVariable();
context.setDefinition(assignTo, params[1]);
return params[1].reduce(context.getInheritedReducer());
}
};
/** /**
* The addition operator, + * The addition operator, +
*/ */
public static final NumberOperator OP_ADD = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) { public static final NumberOperator OP_ADD = new OperatorAdd();
@Override
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2;
}
@Override
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].add(params[1]);
}
};
/** /**
* The subtraction operator, - * The subtraction operator, -
*/ */
public static final NumberOperator OP_SUBTRACT = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) { public static final NumberOperator OP_SUBTRACT = new OperatorSubtract();
@Override
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2;
}
@Override
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].subtract(params[1]);
}
};
/** /**
* The negation operator, - * The negation operator, -
*/ */
public static final NumberOperator OP_NEGATE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0) { public static final NumberOperator OP_NEGATE = new OperatorNegate();
@Override
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].negate();
}
};
/** /**
* The multiplication operator, * * The multiplication operator, *
*/ */
public static final NumberOperator OP_MULTIPLY = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) { public static final NumberOperator OP_MULTIPLY = new OperatorMultiply();
@Override
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2;
}
@Override
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].multiply(params[1]);
}
};
/** /**
* The implementation for double-based naive numbers. * The implementation for double-based naive numbers.
*/ */
@@ -161,96 +97,19 @@ public class StandardPlugin extends Plugin {
/** /**
* The division operator, / * The division operator, /
*/ */
public static final NumberOperator OP_DIVIDE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) { public static final NumberOperator OP_DIVIDE = new OperatorDivide();
@Override
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(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].divide(params[1]);
}
};
/** /**
* The factorial operator, ! * The factorial operator, !
*/ */
public static final NumberOperator OP_FACTORIAL = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0) { public static final NumberOperator OP_FACTORIAL = new OperatorFactorial();
//private HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> storedList = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>();
@Override
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1
&& params[0].fractionalPart().compareTo(context.getInheritedNumberImplementation().instanceForString("0")) == 0
&& params[0].signum() >= 0;
}
@Override
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
if (params[0].signum() == 0) {
return implementation.instanceForString("1");
}
NumberInterface one = implementation.instanceForString("1");
NumberInterface factorial = params[0];
NumberInterface multiplier = params[0];
//It is necessary to later prevent calls of factorial on anything but non-negative integers.
while ((multiplier = multiplier.subtract(one)).signum() == 1) {
factorial = factorial.multiply(multiplier);
}
return factorial;
/*if(!storedList.containsKey(params[0].getClass())){
storedList.put(params[0].getClass(), new ArrayList<NumberInterface>());
storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass()));
storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass()));
}*/
}
};
/** /**
* The permutation operator. * The permutation operator.
*/ */
public static final NumberOperator OP_NPR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) { public static final NumberOperator OP_NPR = new OperatorNpr();
@Override
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(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");
NumberInterface total = implementation.instanceForString("1");
NumberInterface multiplyBy = params[0];
NumberInterface remainingMultiplications = params[1];
NumberInterface halfway = params[0].divide(implementation.instanceForString("2"));
if (remainingMultiplications.compareTo(halfway) > 0) {
remainingMultiplications = params[0].subtract(remainingMultiplications);
}
while (remainingMultiplications.signum() > 0) {
total = total.multiply(multiplyBy);
remainingMultiplications = remainingMultiplications.subtract(implementation.instanceForString("1"));
multiplyBy = multiplyBy.subtract(implementation.instanceForString("1"));
}
return total;
}
};
/** /**
* The combination operator. * The combination operator.
*/ */
public static final NumberOperator OP_NCR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) { public static final NumberOperator OP_NCR = new OperatorNcr();
@Override
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(MutableEvaluationContext context, NumberInterface[] params) {
return OP_NPR.apply(context, params).divide(OP_FACTORIAL.apply(context, params[1]));
}
};
/** /**
* The absolute value function, abs(-3) = 3 * The absolute value function, abs(-3) = 3
*/ */
@@ -332,7 +191,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;
@@ -363,34 +222,7 @@ public class StandardPlugin extends Plugin {
/** /**
* The caret / pow operator, ^ * The caret / pow operator, ^
*/ */
public static final NumberOperator OP_CARET = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2) { public static final NumberOperator OP_CARET = new OperatorCaret();
@Override
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)
&& !(params[0].signum() == -1 && params[1].fractionalPart().compareTo(zero) != 0);
}
@Override
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
NumberInterface zero = implementation.instanceForString("0");
if (params[0].compareTo(zero) == 0)
return zero;
else if (params[1].compareTo(zero) == 0)
return implementation.instanceForString("1");
//Detect integer bases:
if (params[0].fractionalPart().compareTo(implementation.instanceForString("0")) == 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(context, newParams));
}
return FUNCTION_EXP.apply(context, FUNCTION_LN.apply(context, FUNCTION_ABS.apply(context, params[0])).multiply(params[1]));
}
};
/** /**
* The square root function. * The square root function.
*/ */
@@ -715,7 +547,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 {
@@ -756,8 +588,8 @@ public class StandardPlugin extends Plugin {
registerOperator("^", OP_CARET); registerOperator("^", OP_CARET);
registerOperator("!", OP_FACTORIAL); registerOperator("!", OP_FACTORIAL);
registerTreeValueOperator("=", opSet); registerTreeValueOperator("=", OP_SET);
registerTreeValueOperator(":=", opDefine); registerTreeValueOperator(":=", OP_DEFINE);
registerOperator("nPr", OP_NPR); registerOperator("nPr", OP_NPR);
registerOperator("nCr", OP_NCR); registerOperator("nCr", OP_NCR);

View File

@@ -8,7 +8,7 @@ import org.nwapw.abacus.parsing.LexerTokenizer
import org.nwapw.abacus.parsing.ShuntingYardParser import org.nwapw.abacus.parsing.ShuntingYardParser
import org.nwapw.abacus.parsing.TreeBuilder import org.nwapw.abacus.parsing.TreeBuilder
import org.nwapw.abacus.plugin.PluginManager import org.nwapw.abacus.plugin.PluginManager
import org.nwapw.abacus.plugin.StandardPlugin import org.nwapw.abacus.plugin.standard.StandardPlugin
import org.nwapw.abacus.tree.EvaluationResult import org.nwapw.abacus.tree.EvaluationResult
import org.nwapw.abacus.tree.NumberReducer import org.nwapw.abacus.tree.NumberReducer
import org.nwapw.abacus.tree.TreeNode import org.nwapw.abacus.tree.TreeNode
@@ -88,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,253 +1,268 @@
package org.nwapw.abacus.number; package org.nwapw.abacus.number
import org.nwapw.abacus.exception.ComputationInterruptedException; import org.nwapw.abacus.exception.ComputationInterruptedException
/** abstract class NumberInterface: Comparable<NumberInterface> {
* An interface used to represent a number.
*/ /**
public abstract class NumberInterface implements Comparable<NumberInterface> { * Check if the thread was interrupted and
* throw an exception to end the computation.
/** */
* Check if the thread was interrupted and private fun checkInterrupted(){
* throw an exception to end the computation. if(Thread.currentThread().isInterrupted)
*/ throw ComputationInterruptedException()
private static void checkInterrupted() { }
if (Thread.currentThread().isInterrupted())
throw new ComputationInterruptedException(); /**
} * Returns the integer representation of this number, discarding any fractional part,
* if int can hold the value.
/** *
* The maximum precision to which this number operates. * @return the integer value of this number.
* */
* @return the precision. abstract fun intValue(): Int
*/ /**
public abstract int getMaxPrecision(); * Same as Math.signum().
*
/** * @return 1 if this number is positive, -1 if this number is negative, 0 if this number is 0.
* Multiplies this number by another, returning */
* a new number instance. abstract fun signum(): Int
*
* @param multiplier the multiplier /**
* @return the result of the multiplication. * The maximum precision to which this number operates.
*/ */
protected abstract NumberInterface multiplyInternal(NumberInterface multiplier); abstract val maxPrecision: Int
/**
/** * Returns the smallest error this instance can tolerate depending
* Multiplies this number by another, returning * on its precision and value.
* a new number instance. Also, checks if the */
* thread has been interrupted, and if so, throws abstract val maxError: NumberInterface
* an exception.
* /**
* @param multiplier the multiplier * Adds this number to another, returning
* @return the result of the multiplication. * a new number instance.
*/ *
public final NumberInterface multiply(NumberInterface multiplier) { * @param summand the summand
checkInterrupted(); * @return the result of the summation.
return multiplyInternal(multiplier); */
} abstract fun addInternal(summand: NumberInterface): NumberInterface
/**
/** * Subtracts another number from this number,
* Divides this number by another, returning * a new number instance.
* a new number instance. *
* * @param subtrahend the subtrahend.
* @param divisor the divisor * @return the result of the subtraction.
* @return the result of the division. */
*/ abstract fun subtractInternal(subtrahend: NumberInterface): NumberInterface
protected abstract NumberInterface divideInternal(NumberInterface divisor); /**
* Multiplies this number by another, returning
/** * a new number instance.
* Divides this number by another, returning *
* a new number instance. Also, checks if the * @param multiplier the multiplier
* thread has been interrupted, and if so, throws * @return the result of the multiplication.
* an exception. */
* abstract fun multiplyInternal(multiplier: NumberInterface): NumberInterface
* @param divisor the divisor /**
* @return the result of the division. * Divides this number by another, returning
*/ * a new number instance.
public final NumberInterface divide(NumberInterface divisor) { *
checkInterrupted(); * @param divisor the divisor
return divideInternal(divisor); * @return the result of the division.
} */
abstract fun divideInternal(divisor: NumberInterface): NumberInterface
/** /**
* Adds this number to another, returning * Returns a new instance of this number with
* a new number instance. * the sign flipped.
* *
* @param summand the summand * @return the new instance.
* @return the result of the summation. */
*/ abstract fun negateInternal(): NumberInterface
protected abstract NumberInterface addInternal(NumberInterface summand); /**
* Raises this number to an integer power.
/** *
* Adds this number to another, returning * @param exponent the exponent to which to take the number.
* a new number instance. Also, checks if the * @return the resulting value.
* thread has been interrupted, and if so, throws */
* an exception. abstract fun intPowInternal(pow: Int): NumberInterface
* /**
* @param summand the summand * Returns the least integer greater than or equal to the number.
* @return the result of the summation. *
*/ * @return the least integer greater or equal to the number, if int can hold the value.
public final NumberInterface add(NumberInterface summand) { */
checkInterrupted(); abstract fun ceilingInternal(): NumberInterface
return addInternal(summand); /**
} * Return the greatest integer less than or equal to the number.
*
/** * @return the greatest integer smaller or equal the number.
* Subtracts another number from this number, */
* a new number instance. abstract fun floorInternal(): NumberInterface
* /**
* @param subtrahend the subtrahend. * Returns the fractional part of the number.
* @return the result of the subtraction. *
*/ * @return the fractional part of the number.
protected abstract NumberInterface subtractInternal(NumberInterface subtrahend); */
abstract fun fractionalPartInternal(): NumberInterface
/**
* Subtracts another number from this number, /**
* a new number instance. Also, checks if the * Adds this number to another, returning
* thread has been interrupted, and if so, throws * a new number instance. Also, checks if the
* an exception. * thread has been interrupted, and if so, throws
* * an exception.
* @param subtrahend the subtrahend. *
* @return the result of the subtraction. * @param summand the summand
*/ * @return the result of the summation.
public final NumberInterface subtract(NumberInterface subtrahend) { */
checkInterrupted(); fun add(summand: NumberInterface): NumberInterface {
return subtractInternal(subtrahend); checkInterrupted()
} return addInternal(summand)
}
/**
* Returns a new instance of this number with /**
* the sign flipped. * Subtracts another number from this number,
* * a new number instance. Also, checks if the
* @return the new instance. * thread has been interrupted, and if so, throws
*/ * an exception.
protected abstract NumberInterface negateInternal(); *
* @param subtrahend the subtrahend.
* @return the result of the subtraction.
/** */
* Returns a new instance of this number with fun subtract(subtrahend: NumberInterface): NumberInterface {
* the sign flipped. Also, checks if the checkInterrupted()
* thread has been interrupted, and if so, throws return subtractInternal(subtrahend)
* an exception. }
*
* @return the new instance. /**
*/ * Multiplies this number by another, returning
public final NumberInterface negate() { * a new number instance. Also, checks if the
checkInterrupted(); * thread has been interrupted, and if so, throws
return negateInternal(); * an exception.
} *
* @param multiplier the multiplier
/** * @return the result of the multiplication.
* Raises this number to an integer power. */
* fun multiply(multiplier: NumberInterface): NumberInterface {
* @param exponent the exponent to which to take the number. checkInterrupted()
* @return the resulting value. return multiplyInternal(multiplier)
*/ }
protected abstract NumberInterface intPowInternal(int exponent);
/**
/** * Divides this number by another, returning
* Raises this number to an integer power. Also, checks if the * a new number instance. Also, checks if the
* thread has been interrupted, and if so, throws * thread has been interrupted, and if so, throws
* an exception. * an exception.
* *
* @param exponent the exponent to which to take the number. * @param divisor the divisor
* @return the resulting value. * @return the result of the division.
*/ */
public final NumberInterface intPow(int exponent) { fun divide(divisor: NumberInterface): NumberInterface {
checkInterrupted(); checkInterrupted()
return intPowInternal(exponent); return divideInternal(divisor)
} }
/** /**
* Same as Math.signum(). * Returns a new instance of this number with
* * the sign flipped. Also, checks if the
* @return 1 if this number is positive, -1 if this number is negative, 0 if this number is 0. * thread has been interrupted, and if so, throws
*/ * an exception.
public abstract int signum(); *
* @return the new instance.
/** */
* Returns the least integer greater than or equal to the number. fun negate(): NumberInterface {
* checkInterrupted()
* @return the least integer greater or equal to the number, if int can hold the value. return negateInternal()
*/ }
protected abstract NumberInterface ceilingInternal();
/**
/** * Raises this number to an integer power. Also, checks if the
* Returns the least integer greater than or equal to the number. * thread has been interrupted, and if so, throws
* Also, checks if the thread has been interrupted, and if so, throws * an exception.
* an exception. *
* * @param exponent the exponent to which to take the number.
* @return the least integer bigger or equal to the number. * @return the resulting value.
*/ */
public final NumberInterface ceiling() { fun intPow(exponent: Int): NumberInterface {
checkInterrupted(); checkInterrupted()
return ceilingInternal(); return intPowInternal(exponent)
} }
/** /**
* Return the greatest integer less than or equal to the number. * Returns the least integer greater than or equal to the number.
* * Also, checks if the thread has been interrupted, and if so, throws
* @return the greatest integer smaller or equal the number. * an exception.
*/ *
protected abstract NumberInterface floorInternal(); * @return the least integer bigger or equal to the number.
*/
/** fun ceiling(): NumberInterface {
* Return the greatest integer less than or equal to the number. checkInterrupted()
* Also, checks if the thread has been interrupted, and if so, throws return ceilingInternal()
* an exception. }
*
* @return the greatest int smaller than or equal to the number. /**
*/ * Return the greatest integer less than or equal to the number.
public final NumberInterface floor() { * Also, checks if the thread has been interrupted, and if so, throws
checkInterrupted(); * an exception.
return floorInternal(); *
} * @return the greatest int smaller than or equal to the number.
*/
/** fun floor(): NumberInterface {
* Returns the fractional part of the number. checkInterrupted()
* return floorInternal()
* @return the fractional part of the number. }
*/
protected abstract NumberInterface fractionalPartInternal(); /**
* Returns the fractional part of the number, specifically x - floor(x).
/** * Also, checks if the thread has been interrupted,
* Returns the fractional part of the number, specifically x - floor(x). * and if so, throws an exception.
* Also, checks if the thread has been interrupted, *
* and if so, throws an exception. * @return the fractional part of the number.
* */
* @return the fractional part of the number. fun fractionalPart(): NumberInterface {
*/ checkInterrupted()
public final NumberInterface fractionalPart() { return fractionalPartInternal()
checkInterrupted(); }
return fractionalPartInternal();
} /**
* 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
* Returns the integer representation of this number, discarding any fractional part, * the NumberRange needs to promote values passed to it, which
* if int can hold the value. * requires an abacus instance.
* * @param other the value at the bottom of the range.
* @return the integer value of this number. * @return the resulting range builder.
*/ */
public abstract int intValue(); operator fun rangeTo(other: NumberInterface) = NumberRangeBuilder(this, other)
/** /**
* Returns the smallest error this instance can tolerate depending * Plus operator overloaded to allow "nice" looking math.
* on its precision and value. * @param other the value to add to this number.
* * @return the result of the addition.
* @return the smallest error that should be permitted in calculations. */
*/ operator fun plus(other: NumberInterface) = add(other)
public abstract NumberInterface getMaxError(); /**
* Minus operator overloaded to allow "nice" looking math.
/** * @param other the value to subtract to this number.
* Returns a NumberRangeBuilder object, which is used to create a range. * @return the result of the subtraction.
* The reason that this returns a builder and not an actual range is that */
* the NumberRange needs to promote values passed to it, which operator fun minus(other: NumberInterface) = subtract(other)
* requires an abacus instance. /**
* @param other the value at the bottom of the range. * Times operator overloaded to allow "nice" looking math.
* @return the resulting range builder. * @param other the value to multiply this number by.
*/ * @return the result of the multiplication.
public NumberRangeBuilder rangeTo(NumberInterface other){ */
return new NumberRangeBuilder(this, other); operator fun times(other: NumberInterface) = multiply(other)
} /**
* Divide operator overloaded to allow "nice" looking math.
} * @param other the value to divide this number by.
* @return the result of the division.
*/
operator fun div(other: NumberInterface) = divide(other)
/**
* The plus operator.
* @return this number.
*/
operator fun unaryPlus() = this
/**
* The minus operator.
* @return the negative of this number.
*/
operator fun unaryMinus() = negate()
}

View File

@@ -1,16 +1,17 @@
package org.nwapw.abacus.parsing; package org.nwapw.abacus.parsing
import org.nwapw.abacus.tree.TreeNode; import org.nwapw.abacus.tree.TreeNode
import java.util.List;
/** /**
* An itnerface that provides the ability to convert a list of tokens * Converter from tokens into a parse tree.
*
* An interface that provides the ability to convert a list of tokens
* into a parse tree. * into a parse tree.
* *
* @param <T> the type of tokens accepted by this parser. * @param <T> the type of tokens accepted by this parser.
*/ */
public interface Parser<T> {
interface Parser<in T> {
/** /**
* Constructs a tree out of the given tokens. * Constructs a tree out of the given tokens.
@@ -18,5 +19,6 @@ public interface Parser<T> {
* @param tokens the tokens to construct a tree from. * @param tokens the tokens to construct a tree from.
* @return the constructed tree, or null on error. * @return the constructed tree, or null on error.
*/ */
public TreeNode constructTree(List<T> tokens); fun constructTree(tokens: List<T>): TreeNode
}
}

View File

@@ -0,0 +1,21 @@
package org.nwapw.abacus.parsing
/**
* Converter from string to tokens.
*
* Interface that converts a string into a list
* of tokens of a certain type.
*
* @param <T> the type of the tokens produced.
*/
interface Tokenizer<out T> {
/**
* Converts a string into tokens.
*
* @param string the string to convert.
* @return the list of tokens, or null on error.
*/
fun tokenizeString(string: String): List<T>
}

View File

@@ -0,0 +1,26 @@
package org.nwapw.abacus.parsing
import org.nwapw.abacus.tree.TreeNode
/**
* Class to combine a [Tokenizer] and a [Parser]
*
* TreeBuilder class used to piece together a Tokenizer and
* Parser of the same kind. This is used to essentially avoid
* working with any parameters at all, and the generics
* in this class are used only to ensure the tokenizer and parser
* are of the same type.
*
* @param <T> the type of tokens created by the tokenizer and used by the parser.
*/
class TreeBuilder<T>(private val tokenizer: Tokenizer<T>, private val parser: Parser<T>) {
/**
* Parses the given [string] into a tree.
*
* @param string the string to parse into a tree.
* @return the resulting tree.
*/
fun fromString(string: String): TreeNode = parser.constructTree(tokenizer.tokenizeString(string))
}

View File

@@ -0,0 +1,21 @@
package org.nwapw.abacus.plugin.standard.operator
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.function.NumberOperator
import org.nwapw.abacus.function.OperatorAssociativity
import org.nwapw.abacus.function.OperatorType
import org.nwapw.abacus.number.NumberInterface
/**
* The addition operator.
*
* This is a standard operator that simply performs addition.
*/
class OperatorAdd: NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
override fun matchesParams(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
params.size == 2
override fun applyInternal(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
params[0] + params[1]
}

View File

@@ -0,0 +1,38 @@
package org.nwapw.abacus.plugin.standard.operator
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.function.NumberOperator
import org.nwapw.abacus.function.OperatorAssociativity
import org.nwapw.abacus.function.OperatorType
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.plugin.standard.StandardPlugin.*
/**
* The power operator.
*
* This is a standard operator that brings one number to the power of the other.
*/
class OperatorCaret: NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2) {
override fun matchesParams(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
params.size == 2
&& !(params[0].signum() == 0 && params[1].signum() == 0)
&& !(params[0].signum() == -1 && params[1].fractionalPart().signum() != 0)
override fun applyInternal(context: MutableEvaluationContext, params: Array<out NumberInterface>): NumberInterface {
val implementation = context.inheritedNumberImplementation
if (params[0].signum() == 0)
return implementation.instanceForString("0")
else if (params[1].signum() == 0)
return implementation.instanceForString("1")
//Detect integer bases:
if (params[0].fractionalPart().signum() == 0
&& FUNCTION_ABS.apply(context, params[1]) < implementation.instanceForString(Integer.toString(Integer.MAX_VALUE))
&& FUNCTION_ABS.apply(context, params[1]) >= implementation.instanceForString("1")) {
val newParams = arrayOf(params[0], params[1].fractionalPart())
return params[0].intPow(params[1].floor().intValue()) * applyInternal(context, newParams)
}
return FUNCTION_EXP.apply(context, FUNCTION_LN.apply(context, FUNCTION_ABS.apply(context, params[0])) * params[1])
}
}

View File

@@ -0,0 +1,28 @@
package org.nwapw.abacus.plugin.standard.operator
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.function.OperatorAssociativity
import org.nwapw.abacus.function.OperatorType
import org.nwapw.abacus.function.TreeValueOperator
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.tree.TreeNode
import org.nwapw.abacus.tree.VariableNode
/**
* The definition operator.
*
* This is a standard operator that creates a definition - something that doesn't capture variable values
* when it's created, but rather the variables themselves, and changes when the variables it uses change.
*/
class OperatorDefine: TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
override fun matchesParams(context: MutableEvaluationContext, params: Array<out TreeNode>) =
params.size == 2 && params[0] is VariableNode
override fun applyInternal(context: MutableEvaluationContext, params: Array<out TreeNode>): NumberInterface {
val assignTo = (params[0] as VariableNode).variable
context.setDefinition(assignTo, params[1])
return params[1].reduce(context.inheritedReducer)
}
}

View File

@@ -0,0 +1,21 @@
package org.nwapw.abacus.plugin.standard.operator
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.function.NumberOperator
import org.nwapw.abacus.function.OperatorAssociativity
import org.nwapw.abacus.function.OperatorType
import org.nwapw.abacus.number.NumberInterface
/**
* The division operator.
*
* This is a standard operator that simply performs division.
*/
class OperatorDivide: NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
override fun matchesParams(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
params.size == 2
override fun applyInternal(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
params[0] / params[1]
}

View File

@@ -0,0 +1,37 @@
package org.nwapw.abacus.plugin.standard.operator
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.function.NumberOperator
import org.nwapw.abacus.function.OperatorAssociativity
import org.nwapw.abacus.function.OperatorType
import org.nwapw.abacus.number.NumberInterface
/**
* The factorial operator.
*
* This is a standard operator that simply evaluates the factorial of a number.
*/
class OperatorFactorial: NumberOperator(OperatorAssociativity.LEFT, OperatorType.UNARY_POSTFIX, 0) {
override fun matchesParams(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
params.size == 1
&& params[0].fractionalPart().signum() == 0
&& params[0].signum() >= 0
override fun applyInternal(context: MutableEvaluationContext, params: Array<out NumberInterface>): NumberInterface {
val implementation = context.inheritedNumberImplementation
val one = implementation.instanceForString("1")
if (params[0].signum() == 0) {
return one
}
var factorial = params[0]
var multiplier = params[0] - one
//It is necessary to later prevent calls of factorial on anything but non-negative integers.
while (multiplier.signum() == 1) {
factorial *= multiplier
multiplier -= one
}
return factorial
}
}

View File

@@ -0,0 +1,21 @@
package org.nwapw.abacus.plugin.standard.operator
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.function.NumberOperator
import org.nwapw.abacus.function.OperatorAssociativity
import org.nwapw.abacus.function.OperatorType
import org.nwapw.abacus.number.NumberInterface
/**
* The multiplication operator.
*
* This is a standard operator that simply performs multiplication.
*/
class OperatorMultiply: NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
override fun matchesParams(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
params.size == 2
override fun applyInternal(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
params[0] * params[1]
}

View File

@@ -0,0 +1,25 @@
package org.nwapw.abacus.plugin.standard.operator
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.function.NumberOperator
import org.nwapw.abacus.function.OperatorAssociativity
import org.nwapw.abacus.function.OperatorType
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.plugin.standard.StandardPlugin.*
/**
* The "N choose R" operator.
*
* This is a standard operator that returns the number of possible combinations, regardless of order,
* of a certain size can be taken out of a pool of a bigger size.
*/
class OperatorNcr: NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
override fun matchesParams(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
params.size == 2 && params[0].fractionalPart().signum() == 0
&& params[1].fractionalPart().signum() == 0
override fun applyInternal(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
OP_NPR.apply(context, *params) / OP_FACTORIAL.apply(context, params[1])
}

View File

@@ -0,0 +1,22 @@
package org.nwapw.abacus.plugin.standard.operator
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.function.NumberOperator
import org.nwapw.abacus.function.OperatorAssociativity
import org.nwapw.abacus.function.OperatorType
import org.nwapw.abacus.number.NumberInterface
/**
* The negation operator.
*
* This is a standard operator that negates a number.
*/
class OperatorNegate: NumberOperator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0) {
override fun matchesParams(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
params.size == 1
override fun applyInternal(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
-params[0]
}

View File

@@ -0,0 +1,42 @@
package org.nwapw.abacus.plugin.standard.operator
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.function.NumberOperator
import org.nwapw.abacus.function.OperatorAssociativity
import org.nwapw.abacus.function.OperatorType
import org.nwapw.abacus.number.NumberInterface
/**
* The "N pick R" operator.
*
* his is a standard operator that returns the number of possible combinations
* of a certain size can be taken out of a pool of a bigger size.
*/
class OperatorNpr: NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
override fun matchesParams(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
params.size == 2 && params[0].fractionalPart().signum() == 0
&& params[1].fractionalPart().signum() == 0
override fun applyInternal(context: MutableEvaluationContext, params: Array<out NumberInterface>): NumberInterface {
val implementation = context.inheritedNumberImplementation
if (params[0] < params[1] ||
params[0].signum() < 0 ||
params[0].signum() == 0 && params[1].signum() != 0)
return implementation.instanceForString("0")
var total = implementation.instanceForString("1")
var multiplyBy = params[0]
var remainingMultiplications = params[1]
val halfway = params[0] / implementation.instanceForString("2")
if (remainingMultiplications > halfway) {
remainingMultiplications = params[0] - remainingMultiplications
}
while (remainingMultiplications.signum() > 0) {
total *= multiplyBy
remainingMultiplications -= implementation.instanceForString("1")
multiplyBy -= implementation.instanceForString("1")
}
return total
}
}

View File

@@ -0,0 +1,28 @@
package org.nwapw.abacus.plugin.standard.operator
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.function.OperatorAssociativity
import org.nwapw.abacus.function.OperatorType
import org.nwapw.abacus.function.TreeValueOperator
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.tree.TreeNode
import org.nwapw.abacus.tree.VariableNode
/**
* The set operator.
*
* This is a standard operator that assigns a value to a variable name.
*/
class OperatorSet: TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
override fun matchesParams(context: MutableEvaluationContext, params: Array<out TreeNode>) =
params.size == 2 && params[0] is VariableNode
override fun applyInternal(context: MutableEvaluationContext, params: Array<out TreeNode>): NumberInterface {
val assignTo = (params[0] as VariableNode).variable
val value = params[1].reduce(context.inheritedReducer)
context.setVariable(assignTo, value)
return value
}
}

View File

@@ -0,0 +1,21 @@
package org.nwapw.abacus.plugin.standard.operator
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.function.NumberOperator
import org.nwapw.abacus.function.OperatorAssociativity
import org.nwapw.abacus.function.OperatorType
import org.nwapw.abacus.number.NumberInterface
/**
* The subtraction operator.
*
* This is a standard operator that performs subtraction.
*/
class OperatorSubtract: NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
override fun matchesParams(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
params.size == 2
override fun applyInternal(context: MutableEvaluationContext, params: Array<out NumberInterface>) =
params[0] - params[1]
}

View File

@@ -2,7 +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.EvaluationException 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> {
@@ -17,15 +18,14 @@ class NumberReducer(val abacus: Abacus, context: EvaluationContext) : Reducer<Nu
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)
?: throw EvaluationException("no number implementation selected.")
} }
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)
throw EvaluationException("variable is not defined.") throw NumberReducerException("variable is not defined.")
} }
is NumberUnaryNode -> { is NumberUnaryNode -> {
val child = children[0] as NumberInterface val child = children[0] as NumberInterface
@@ -58,7 +58,7 @@ class NumberReducer(val abacus: Abacus, context: EvaluationContext) : Reducer<Nu
abacus.pluginManager.treeValueFunctionFor(treeNode.callTo) abacus.pluginManager.treeValueFunctionFor(treeNode.callTo)
.apply(context, *treeNode.children.toTypedArray()) .apply(context, *treeNode.children.toTypedArray())
} }
else -> throw EvaluationException("unrecognized tree node.") else -> throw ReductionException("unrecognized tree node.")
} }
} }

View File

@@ -7,7 +7,7 @@ import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration; import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.exception.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.standard.StandardPlugin;
import org.nwapw.abacus.tree.TreeNode; import org.nwapw.abacus.tree.TreeNode;
public class CalculationTests { public class CalculationTests {
@@ -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

@@ -9,7 +9,7 @@ import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.NumberRange; import org.nwapw.abacus.number.NumberRange;
import org.nwapw.abacus.number.PreciseNumber; import org.nwapw.abacus.number.PreciseNumber;
import org.nwapw.abacus.plugin.StandardPlugin; import org.nwapw.abacus.plugin.standard.StandardPlugin;
import java.util.function.Function; import java.util.function.Function;

View File

@@ -13,16 +13,13 @@ 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.exception.AbacusException;
import org.nwapw.abacus.exception.ComputationInterruptedException;
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.exception.DomainException;
import org.nwapw.abacus.exception.EvaluationException;
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;
import org.nwapw.abacus.plugin.PluginManager; import org.nwapw.abacus.plugin.PluginManager;
import org.nwapw.abacus.plugin.StandardPlugin; import org.nwapw.abacus.plugin.standard.StandardPlugin;
import org.nwapw.abacus.tree.EvaluationResult; import org.nwapw.abacus.tree.EvaluationResult;
import org.nwapw.abacus.tree.TreeNode; import org.nwapw.abacus.tree.TreeNode;
@@ -145,9 +142,6 @@ 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();
String resultingString = evaluatedNumber.toString(); String resultingString = evaluatedNumber.toString();