mirror of
https://github.com/DanilaFe/abacus
synced 2026-01-25 16:15:19 +00:00
Compare commits
21 Commits
promotion-
...
graphing
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d1ee2a662 | |||
| 122b402ef4 | |||
| 2f4362e23b | |||
| bbe4fa182f | |||
| 3bffa1c78f | |||
| b0cddf75f0 | |||
| 2e8e1488c4 | |||
| e0ccb67ad3 | |||
| ea4588be44 | |||
| 31996219ad | |||
| a3bfc34c1c | |||
| 8dc7acd4b3 | |||
| 76fcd8ec1c | |||
| fbdf2c7e52 | |||
| 3057f66e66 | |||
| f385a48aa2 | |||
| fd3f56aa8f | |||
| e364f4e94b | |||
| 4c94abb18b | |||
| ba63dd7874 | |||
| 566598b702 |
@@ -1,5 +1,5 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.1.3'
|
ext.kotlin_version = '1.1.4-3'
|
||||||
ext.dokka_version = '0.9.15'
|
ext.dokka_version = '0.9.15'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@@ -19,7 +19,7 @@ subprojects {
|
|||||||
apply plugin: 'org.jetbrains.dokka'
|
apply plugin: 'org.jetbrains.dokka'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -238,4 +238,16 @@ public abstract class NumberInterface implements Comparable<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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -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<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
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
//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;
|
||||||
@@ -715,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 {
|
||||||
|
|||||||
@@ -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.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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 }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
28
core/src/main/kotlin/org/nwapw/abacus/number/NumberRange.kt
Normal file
28
core/src/main/kotlin/org/nwapw/abacus/number/NumberRange.kt
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
97
core/src/test/java/org/nwapw/abacus/tests/RangeTests.java
Normal file
97
core/src/test/java/org/nwapw/abacus/tests/RangeTests.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,7 +2,14 @@ apply plugin: 'application'
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.moandjiezana.toml:toml4j:0.7.1'
|
compile 'com.moandjiezana.toml:toml4j:0.7.1'
|
||||||
|
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.18'
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
experimental {
|
||||||
|
coroutines "enable"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mainClassName = 'org.nwapw.abacus.fx.AbacusApplication'
|
mainClassName = 'org.nwapw.abacus.fx.AbacusApplication'
|
||||||
|
|||||||
@@ -13,11 +13,8 @@ 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;
|
||||||
@@ -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();
|
||||||
|
|||||||
121
fx/src/main/kotlin/org/nwapw/abacus/fx/graphing/Graph.kt
Normal file
121
fx/src/main/kotlin/org/nwapw/abacus/fx/graphing/Graph.kt
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package org.nwapw.abacus.fx.graphing
|
||||||
|
|
||||||
|
import kotlinx.coroutines.experimental.CommonPool
|
||||||
|
import kotlinx.coroutines.experimental.async
|
||||||
|
import kotlinx.coroutines.experimental.runBlocking
|
||||||
|
import org.nwapw.abacus.Abacus
|
||||||
|
import org.nwapw.abacus.config.Configuration
|
||||||
|
import org.nwapw.abacus.context.EvaluationContext
|
||||||
|
import org.nwapw.abacus.context.MutableEvaluationContext
|
||||||
|
import org.nwapw.abacus.number.NaiveNumber
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
import org.nwapw.abacus.number.NumberRange
|
||||||
|
import org.nwapw.abacus.plugin.StandardPlugin
|
||||||
|
import org.nwapw.abacus.tree.TreeNode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that holds information about a graph.
|
||||||
|
*
|
||||||
|
* Graph that uses an instance of [Abacus] to generate graphs of an
|
||||||
|
* [expression]. Continuous graphing on large expressions is expensive,
|
||||||
|
* and therefore the method of graphing is discrete. The graph class
|
||||||
|
* uses the [pointExpression] to generate inputs until an input is outside
|
||||||
|
* the function's domain. In both the expressions, the [inputVariable] and
|
||||||
|
* [pointInputVariable] are used, respectively, in order to either pass in
|
||||||
|
* the x-coordinate or the number of the input value being generated.
|
||||||
|
*
|
||||||
|
* @property abacus the abacus instance to use to evaluate expressions.
|
||||||
|
* @property domain the domain used in computation.
|
||||||
|
* @property range the range used for displaying the output.
|
||||||
|
* @property inputVariable the variable which is substituted for the input x-coordinate.
|
||||||
|
* @property pointInputVariable the variable which is substituted for the number of the input point being generated.
|
||||||
|
*/
|
||||||
|
class Graph(val abacus: Abacus,
|
||||||
|
var domain: NumberRange, var range: NumberRange,
|
||||||
|
var inputVariable: String = "x", var pointInputVariable: String = "n") {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property used for storing the parsed version of the [expression]
|
||||||
|
*/
|
||||||
|
private var expressionTree: TreeNode? = null
|
||||||
|
/**
|
||||||
|
* Property used for storing the parsed version of the [pointExpression]
|
||||||
|
*/
|
||||||
|
private var pointExpressionTree: TreeNode? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The expression being graphed.
|
||||||
|
*/
|
||||||
|
var expression: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
expressionTree = abacus.parseString(value ?: return)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* The expression being used to generate points.
|
||||||
|
*/
|
||||||
|
var pointExpression: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
pointExpressionTree = abacus.parseString(value ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates a parsed expression [tree] with the given input [values] of indeterminate type.
|
||||||
|
* This is is an asynchronous operation using coroutines, and for every input
|
||||||
|
* value a new sub-context is created. The [contextModifier] function is executed
|
||||||
|
* on each new sub-context, and is expected to use the input value to modify the context
|
||||||
|
* state so that the evaluation of the expression produces a correct result.
|
||||||
|
*
|
||||||
|
* @param T the type of input values.
|
||||||
|
* @param tree the tree to evaluate.
|
||||||
|
* @param values the values to plug into the expression.
|
||||||
|
* @param contextModifier the function that plugs values into the context.
|
||||||
|
* @return the list of outputs.
|
||||||
|
*/
|
||||||
|
fun <T> evaluateWith(tree: TreeNode, values: List<T>,
|
||||||
|
contextModifier: MutableEvaluationContext.(T) -> Unit) = runBlocking {
|
||||||
|
values.map {
|
||||||
|
val context = abacus.context.mutableSubInstance()
|
||||||
|
context.contextModifier(it)
|
||||||
|
async(CommonPool) { abacus.evaluateTreeWithContext(tree, context) }
|
||||||
|
}.map { it.await() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension function that calls [evaluateWith] with the current list.
|
||||||
|
* @param T the type of the values in the list.
|
||||||
|
* @param tree the tree node to evaluate.
|
||||||
|
* @param contextModifier the function that plugs values into the context.
|
||||||
|
* @return the list of outputs.
|
||||||
|
*/
|
||||||
|
fun <T> List<T>.evaluateWith(tree: TreeNode,
|
||||||
|
contextModifier: MutableEvaluationContext.(T) -> Unit ) =
|
||||||
|
evaluateWith(tree, this, contextModifier)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the [pointExpression] and [pointInputVariable] to generate
|
||||||
|
* a set of points as inputs for the actual expression. These points are generated
|
||||||
|
* as long as they are within the [domain].
|
||||||
|
* @return the list of generated input values.
|
||||||
|
*/
|
||||||
|
fun generateInputs(): List<NumberInterface> =
|
||||||
|
generateSequence(1) {
|
||||||
|
it + 1
|
||||||
|
}.map {
|
||||||
|
val context = abacus.context.mutableSubInstance()
|
||||||
|
context.setVariable(pointInputVariable,
|
||||||
|
context.inheritedNumberImplementation!!.instanceForString(it.toString()))
|
||||||
|
abacus.evaluateTreeWithContext(pointExpressionTree!!, context).value
|
||||||
|
}.takeWhile { it in domain }.toList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the [expression] and [inputVariable] to generate
|
||||||
|
* a set of outputs from the given set of [inputs].
|
||||||
|
* @return the list of generated points.
|
||||||
|
*/
|
||||||
|
fun generateOutputs(inputs: List<NumberInterface>): List<Pair<NumberInterface, NumberInterface>> =
|
||||||
|
inputs.evaluateWith(expressionTree!!) {
|
||||||
|
setVariable(inputVariable, it)
|
||||||
|
}.mapIndexed { index, (value) -> inputs[index] to value }
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.nwapw.abacus.fx.graphing
|
||||||
|
|
||||||
|
import javafx.beans.value.ChangeListener
|
||||||
|
import javafx.scene.canvas.Canvas
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A canvas that renders a graph.
|
||||||
|
*
|
||||||
|
* The GraphCanvas uses the provided [Graph] instance in order to draw the outputs on itself.
|
||||||
|
* @param graph the graph used to render.
|
||||||
|
*/
|
||||||
|
class GraphCanvas(graph: Graph): Canvas() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The graph that is currently being used to generate inputs / outputs.
|
||||||
|
* The redraw is triggered if this graph is reset.
|
||||||
|
*/
|
||||||
|
var graph: Graph = graph
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
redraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val redrawListener = ChangeListener<Number> { _, _, _ -> redraw() }
|
||||||
|
widthProperty().addListener(redrawListener)
|
||||||
|
heightProperty().addListener(redrawListener)
|
||||||
|
redraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redraws the graph onto the canvas.
|
||||||
|
*/
|
||||||
|
fun redraw() {
|
||||||
|
val graphicsContext = graphicsContext2D
|
||||||
|
val outputs = graph.generateOutputs(graph.generateInputs())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user