1
0
mirror of https://github.com/DanilaFe/abacus synced 2026-01-25 08:05:19 +00:00

Compare commits

..

12 Commits

12 changed files with 238 additions and 161 deletions

View File

@@ -1,150 +0,0 @@
package org.nwapw.abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PromotionManager;
import org.nwapw.abacus.parsing.LexerTokenizer;
import org.nwapw.abacus.parsing.ShuntingYardParser;
import org.nwapw.abacus.parsing.TreeBuilder;
import org.nwapw.abacus.plugin.NumberImplementation;
import org.nwapw.abacus.plugin.PluginManager;
import org.nwapw.abacus.plugin.StandardPlugin;
import org.nwapw.abacus.tree.NumberReducer;
import org.nwapw.abacus.tree.TreeNode;
/**
* The main calculator class. This is responsible
* for piecing together all of the components, allowing
* their interaction with each other.
*/
public class Abacus {
/**
* The default number implementation to be used if no other one is available / selected.
*/
public static final NumberImplementation DEFAULT_IMPLEMENTATION = StandardPlugin.IMPLEMENTATION_NAIVE;
/**
* The plugin manager responsible for
* loading and unloading plugins,
* and getting functions from them.
*/
private PluginManager pluginManager;
/**
* The reducer used to evaluate the tree.
*/
private NumberReducer numberReducer;
/**
* The configuration loaded from a file.
*/
private Configuration configuration;
/**
* The tree builder used to construct a tree
* from a string.
*/
private TreeBuilder treeBuilder;
/**
* Promotion manager responsible for the promotion system.
*/
private PromotionManager promotionManager;
/**
* Creates a new instance of the Abacus calculator.
*
* @param configuration the configuration object for this Abacus instance.
*/
public Abacus(Configuration configuration) {
pluginManager = new PluginManager(this);
numberReducer = new NumberReducer(this);
this.configuration = new Configuration(configuration);
LexerTokenizer lexerTokenizer = new LexerTokenizer();
ShuntingYardParser shuntingYardParser = new ShuntingYardParser();
treeBuilder = new TreeBuilder<>(lexerTokenizer, shuntingYardParser);
promotionManager = new PromotionManager(this);
pluginManager.addListener(shuntingYardParser);
pluginManager.addListener(lexerTokenizer);
pluginManager.addListener(promotionManager);
}
/**
* Gets the promotion manager.
*
* @return the promotion manager.
*/
public PromotionManager getPromotionManager() {
return promotionManager;
}
/**
* Gets the current tree builder.
*
* @return the main tree builder in this abacus instance.
*/
public TreeBuilder getTreeBuilder() {
return treeBuilder;
}
/**
* Gets the current plugin manager,
*
* @return the plugin manager in this abacus instance.
*/
public PluginManager getPluginManager() {
return pluginManager;
}
/**
* Get the reducer that is responsible for transforming
* an expression into a number.
*
* @return the number reducer in this abacus instance.
*/
public NumberReducer getNumberReducer() {
return numberReducer;
}
/**
* Gets the configuration object associated with this instance.
*
* @return the configuration object.
*/
public Configuration getConfiguration() {
return configuration;
}
/**
* Parses a string into a tree structure using the main
* tree builder.
*
* @param input the input string to parse
* @return the resulting tree, null if the tree builder or the produced tree are null.
*/
public TreeNode parseString(String input) {
return treeBuilder.fromString(input);
}
/**
* Evaluates the given tree using the main
* number reducer.
*
* @param tree the tree to reduce, must not be null.
* @return the resulting number, or null of the reduction failed.
*/
public NumberInterface evaluateTree(TreeNode tree) {
return tree.reduce(numberReducer);
}
/**
* Gets the number implementation.
*
* @return the number implementation to use for creating numbers.
*/
public NumberImplementation getNumberImplementation() {
NumberImplementation selectedImplementation =
pluginManager.numberImplementationFor(configuration.getNumberImplementation());
if (selectedImplementation != null) return selectedImplementation;
return DEFAULT_IMPLEMENTATION;
}
}

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,14 @@
package org.nwapw.abacus.plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber;
import org.nwapw.abacus.tree.Reducer;
import org.nwapw.abacus.tree.TreeNode;
import org.nwapw.abacus.tree.VariableNode;
import java.util.ArrayList;
import java.util.HashMap;
@@ -14,6 +19,40 @@ import java.util.HashMap;
*/
public class StandardPlugin extends Plugin {
/**
* The set operator.
*/
public final TreeValueOperator opSet = new TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(NumberImplementation implementation, TreeNode[] params) {
return params.length == 2 && params[0] instanceof VariableNode;
}
@Override
public NumberInterface applyWithReducerInternal(NumberImplementation implementation, Reducer<? extends NumberInterface> reducer, TreeNode[] params) {
String assignTo = ((VariableNode) params[0]).getVariable();
NumberInterface value = params[1].reduce(reducer);
getVariableDatabase().getVariables().put(assignTo, value);
return value;
}
};
/**
* The define operator.
*/
public final TreeValueOperator opDefine = new TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(NumberImplementation implementation, TreeNode[] params) {
return params.length == 2 && params[0] instanceof VariableNode;
}
@Nullable
@Override
public NumberInterface applyWithReducerInternal(NumberImplementation implementation, Reducer<? extends NumberInterface> reducer, TreeNode[] params) {
String assignTo = ((VariableNode) params[0]).getVariable();
getVariableDatabase().getDefinitions().put(assignTo, params[1]);
return params[1].reduce(reducer);
}
};
/**
* The addition operator, +
*/
@@ -712,6 +751,9 @@ public class StandardPlugin extends Plugin {
registerOperator("^", OP_CARET);
registerOperator("!", OP_FACTORIAL);
registerTreeValueOperator("=", opSet);
registerTreeValueOperator(":=", opDefine);
registerOperator("nPr", OP_NPR);
registerOperator("nCr", OP_NCR);

View File

@@ -8,6 +8,7 @@ import org.nwapw.abacus.function.TreeValueOperator;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PromotionManager;
import org.nwapw.abacus.number.PromotionResult;
import org.nwapw.abacus.variables.VariableDatabase;
/**
* A reducer implementation that turns a tree into a single number.
@@ -35,6 +36,12 @@ public class NumberReducer implements Reducer<NumberInterface> {
if (node instanceof NumberNode) {
return abacus.getNumberImplementation().instanceForString(((NumberNode) node).getNumber());
} else if (node instanceof VariableNode) {
VariableDatabase database = abacus.getVariableDatabase();
String name = ((VariableNode) node).getVariable();
NumberInterface variable = database.getVariables().get(name);
if(variable != null) return variable;
TreeNode definition = database.getDefinitions().get(name);
if(definition != null) return definition.reduce(this);
return abacus.getNumberImplementation().instanceForString("0");
} else if (node instanceof NumberBinaryNode) {
NumberInterface left = (NumberInterface) children[0];

View File

@@ -0,0 +1,90 @@
package org.nwapw.abacus
import org.nwapw.abacus.config.Configuration
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.number.PromotionManager
import org.nwapw.abacus.parsing.LexerTokenizer
import org.nwapw.abacus.parsing.ShuntingYardParser
import org.nwapw.abacus.parsing.TreeBuilder
import org.nwapw.abacus.plugin.NumberImplementation
import org.nwapw.abacus.plugin.PluginManager
import org.nwapw.abacus.plugin.StandardPlugin
import org.nwapw.abacus.tree.NumberReducer
import org.nwapw.abacus.tree.TreeNode
import org.nwapw.abacus.variables.VariableDatabase
/**
* Core class to handle all mathematics.
*
* The main calculator class. This is responsible
* for piecing together all of the components, allowing
* their interaction with each other.
*
* @property configuration the configuration to use.
*/
class Abacus(val configuration: Configuration) {
/**
* The tokenizer used to convert strings into tokens.
*/
private val tokenizer = LexerTokenizer()
/**
* Parser the parser used to convert tokens into trees.
*/
private val parser = ShuntingYardParser()
/**
* The plugin manager used to handle loading and unloading plugins.
*/
val pluginManager = PluginManager(this)
/**
* The reducer used to turn trees into a single number.
*/
val numberReducer = NumberReducer(this)
/**
* The tree builder that handles the conversion of strings into trees.
*/
val treeBuilder = TreeBuilder(tokenizer, parser)
/**
* The promotion manager used to convert between number implementations.
*/
val promotionManager = PromotionManager(this)
/**
* The database of variable definitions.
*/
val variableDatabase = VariableDatabase(this)
/**
* The number implementation used by default.
*/
val numberImplementation: NumberImplementation
get() {
val selectedImplementation =
pluginManager.numberImplementationFor(configuration.numberImplementation)
if (selectedImplementation != null) return selectedImplementation
return StandardPlugin.IMPLEMENTATION_NAIVE
}
init {
pluginManager.addListener(tokenizer)
pluginManager.addListener(parser)
pluginManager.addListener(promotionManager)
pluginManager.addListener(variableDatabase)
}
/**
* Parses a string into a tree structure using the main
* tree builder.
*
* @param input the input string to parse
* @return the resulting tree, null if the tree builder or the produced tree are null.
*/
fun parseString(input: String): TreeNode? = treeBuilder.fromString(input)
/**
* Evaluates the given tree using the main
* number reducer.
*
* @param tree the tree to reduce, must not be null.
* @return the resulting number, or null of the reduction failed.
*/
fun evaluateTree(tree: TreeNode): NumberInterface? = tree.reduce(numberReducer)
}

View File

@@ -1,5 +1,6 @@
package org.nwapw.abacus.function.applicable
import org.nwapw.abacus.function.DomainException
import org.nwapw.abacus.plugin.NumberImplementation
/**
@@ -25,7 +26,7 @@ interface Applicable<in T : Any, out O : Any> {
* @param params the parameters to apply to.
* @return the result of the application.
*/
fun applyInternal(implementation: NumberImplementation, params: Array<out T>): O?
fun applyInternal(implementation: NumberImplementation, params: Array<out T>): O
/**
* If the parameters can be used with this applicable, returns
@@ -34,8 +35,8 @@ interface Applicable<in T : Any, out O : Any> {
* @param params the parameters to apply to.
* @return the result of the operation, or null if parameters do not match.
*/
fun apply(implementation: NumberImplementation, vararg params: T): O? {
if (!matchesParams(implementation, params)) return null
fun apply(implementation: NumberImplementation, vararg params: T): O {
if (!matchesParams(implementation, params)) throw DomainException()
return applyInternal(implementation, params)
}

View File

@@ -1,5 +1,6 @@
package org.nwapw.abacus.function.applicable
import org.nwapw.abacus.function.DomainException
import org.nwapw.abacus.plugin.NumberImplementation
import org.nwapw.abacus.tree.Reducer
@@ -27,7 +28,7 @@ interface ReducerApplicable<in T : Any, out O : Any, in R : Any> {
* @param params the arguments to apply to.
* @return the result of the application.
*/
fun applyWithReducerInternal(implementation: NumberImplementation, reducer: Reducer<R>, params: Array<out T>): O?
fun applyWithReducerInternal(implementation: NumberImplementation, reducer: Reducer<R>, params: Array<out T>): O
/**
* Applies this applicable to the given arguments, and reducer,
@@ -36,8 +37,8 @@ interface ReducerApplicable<in T : Any, out O : Any, in R : Any> {
* @param params the arguments to apply to.
* @return the result of the application, or null if the arguments are incompatible.
*/
fun applyWithReducer(implementation: NumberImplementation, reducer: Reducer<R>, vararg params: T): O? {
if (!matchesParams(implementation, params)) return null
fun applyWithReducer(implementation: NumberImplementation, reducer: Reducer<R>, vararg params: T): O {
if (!matchesParams(implementation, params)) throw DomainException()
return applyWithReducerInternal(implementation, reducer, params)
}

View File

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

View File

@@ -5,6 +5,7 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.function.DomainException;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.plugin.StandardPlugin;
import org.nwapw.abacus.tree.TreeNode;
@@ -28,11 +29,14 @@ public class CalculationTests {
Assert.assertTrue(result.toString().startsWith(output));
}
private void testEvalError(String input, String parseOutput) {
private void testDomainException(String input, String parseOutput) {
TreeNode parsedTree = abacus.parseString(input);
Assert.assertNotNull(parsedTree);
Assert.assertEquals(parsedTree.toString(), parseOutput);
Assert.assertNull(abacus.evaluateTree(parsedTree));
try {
abacus.evaluateTree(parsedTree);
Assert.fail("Function did not throw DomainException.");
} catch (DomainException e){ }
}
@Test
@@ -73,7 +77,7 @@ public class CalculationTests {
@Test
public void testLn() {
testEvalError("ln(-1)", "ln((1)`)");
testDomainException("ln(-1)", "ln((1)`)");
testOutput("ln2", "ln(2)", "0.6931471805599453094172321214581765680755");
}
@@ -100,8 +104,8 @@ public class CalculationTests {
testOutput("2^-1", "(2^(1)`)", "0.5");
testOutput("2^50", "(2^50)", "112589990684262");
testOutput("7^(-sqrt2*17)", "(7^((sqrt(2)*17))`)", "4.81354609155297814551845300063563");
testEvalError("0^0", "(0^0)");
testEvalError("(-13)^.9999", "((13)`^.9999)");
testDomainException("0^0", "(0^0)");
testDomainException("(-13)^.9999", "((13)`^.9999)");
}
}

View File

@@ -14,6 +14,7 @@ import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.function.Documentation;
import org.nwapw.abacus.function.DocumentationType;
import org.nwapw.abacus.function.DomainException;
import org.nwapw.abacus.number.*;
import org.nwapw.abacus.plugin.ClassFinder;
import org.nwapw.abacus.plugin.PluginListener;
@@ -152,6 +153,8 @@ public class AbacusController implements PluginListener {
return resultingString;
} catch (ComputationInterruptedException exception) {
return ERR_STOP;
} catch (DomainException exception) {
return exception.getMessage();
} catch (RuntimeException exception) {
exception.printStackTrace();
return ERR_EXCEPTION;