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

Compare commits

...

115 Commits

Author SHA1 Message Date
e430e738cf Merge branch 'master' into fixes 2017-09-16 03:03:52 -07:00
f6e326e0f1 Revert "Remove unnecessary nullability from parseString."
88e3bb7109
2017-09-16 03:02:35 -07:00
07581557c7 Merge pull request #27 from DanilaFe/fixes
Fix a number of small issues not worthy of their own branches.
2017-09-16 01:26:40 -07:00
14ac9c67f4 Implement the comparable interface. 2017-09-16 00:18:43 -07:00
0ff071e212 Add a more complete .gitignore 2017-09-16 00:17:03 -07:00
88e3bb7109 Remove unnecessary nullability from parseString. 2017-09-16 00:16:48 -07:00
540e5d6099 Load default implementation if one is not found. 2017-09-16 00:16:32 -07:00
c9e93d87a2 Merge pull request #23 from DanilaFe/thread-safety
Make some parts of the code more thread safe.
2017-09-15 22:59:42 -07:00
337edd68fa Merge branch 'master' into thread-safety 2017-09-15 22:58:33 -07:00
08967fbb8f Merge pull request #22 from DanilaFe/less-null
Remove some null-heavy parts of the code.
2017-09-15 22:56:54 -07:00
46f78bb2ed Merge pull request #21 from DanilaFe/context
Implement a Context system which allows concurrent creation of variables.
2017-09-15 22:51:56 -07:00
5b4773dee1 Do not use null in exceptions and add messages to exceptions. 2017-09-11 19:32:57 -07:00
be94394a5c Catch one exception. 2017-09-11 19:32:57 -07:00
45de25cd50 Move exceptions to their own package and subclass one class. 2017-09-11 19:32:57 -07:00
52ab357fe1 Remove nullability from reduction. 2017-09-11 19:32:57 -07:00
1575d3e574 Remove nullability from tree nodes. 2017-09-11 19:32:57 -07:00
87529da15f Precompute all the transitions ahead of time. 2017-09-11 19:32:51 -07:00
7cd117dac1 Add synchronization to the Standard plugin. 2017-09-11 19:32:51 -07:00
8975bfdb99 Precompute Pi, and do not store documentation on access. 2017-09-11 19:32:51 -07:00
00f8475044 Merge branch 'master' into context 2017-09-11 19:32:42 -07:00
f0efae21be Merge pull request #19 from DanilaFe/fix-loading
Fix exception during startup if configuration is missing.
2017-09-11 19:32:17 -07:00
9f11fd20a2 Fix exception during startup if configuration is missing. 2017-09-11 19:30:21 -07:00
1667edc72b Merge branch 'master' into context 2017-09-11 19:14:07 -07:00
5d2a988f75 Merge pull request #17 from DanilaFe/website-update
Add more information about features and some pictures to main page.
2017-09-11 19:08:35 -07:00
91978686e6 Merge branch 'master' into website-update 2017-09-11 19:06:02 -07:00
9a8d0afc19 Merge pull request #18 from DanilaFe/configuration-refactor
Refactor configuration
2017-09-11 19:05:52 -07:00
5aba5c350b Add comments to the new configuration classes. 2017-09-11 18:15:40 -07:00
21b7bd5e2b Move TOML code out of the configuration in core, and into fx. 2017-09-11 18:06:40 -07:00
f2ac7b109a Remove old imports. 2017-09-10 18:19:30 -07:00
67d240b8f6 Remove the unused variable database class. 2017-09-10 17:57:43 -07:00
dc4eee6342 Decrease the padding and margins on small screens. 2017-09-07 16:22:15 -07:00
6909f210d6 Add a features list to the landing page. 2017-09-07 16:22:08 -07:00
059226a4d4 Rename the context class. 2017-09-06 22:54:21 -07:00
ef1890f24d Switch Abacus to returning an EvaluationResult with the context. 2017-09-06 22:22:15 -07:00
782669a32b Change button to "Save". 2017-09-06 22:04:28 -07:00
924849bd8b Make reloads go through the Abacus core. 2017-09-06 22:03:54 -07:00
91986112a1 Switch all applicables to use the Context. 2017-09-06 21:43:07 -07:00
58fea9c52b Move the files into the correct source directory. 2017-09-06 20:48:43 -07:00
863be5bcfc Rewrite number reducer in Kotlin. 2017-09-06 20:39:38 -07:00
f0e38fed87 Add the contexts and delegates for them. 2017-09-06 20:36:25 -07:00
fd246f935c Merge pull request #12 from DanilaFe/no-null
Remove the use of null in Applicable calls.
2017-09-06 15:18:59 -07:00
6604af5b0f Merge branch 'master' into no-null 2017-09-06 15:16:55 -07:00
d49a763e8f Merge pull request #11 from DanilaFe/tree-value-variables
Add variables implemented via Tree Value Operators.
2017-09-06 15:11:06 -07:00
48a4d8adc2 Merge branch 'master' into tree-value-variables 2017-09-06 15:07:45 -07:00
5417b45106 Merge pull request #9 from DanilaFe/promotion-system
Implement a basic promotion system.
2017-09-06 15:05:22 -07:00
585cabc478 Add the ability for plugins to access variables, and add the operators. 2017-09-05 23:00:25 -07:00
28802cfed3 Remove the additional methods from the VariableDatabase. 2017-09-05 23:00:25 -07:00
428df8bfd3 Use the variable database for the number reducer. 2017-09-05 23:00:25 -07:00
146f3994ef Add the variable database. 2017-09-05 23:00:25 -07:00
daffdb6b42 Move Abacus core into Kotlin. 2017-09-05 23:00:25 -07:00
178f59ef7b Move the exception to the correct package. 2017-09-04 12:55:49 -07:00
61616a428a Fix tests that expected null from functions. 2017-09-04 12:55:49 -07:00
9c77fa8aeb Add a DomainException that avoids using null in functions. 2017-09-04 12:55:49 -07:00
9ddfeb02cf Fix not clearing an important map during reset. 2017-09-04 12:51:53 -07:00
bc4a26aafb Fix weird alignment. 2017-09-01 18:43:07 -07:00
1f6aa70230 Ensure PromotionManager clears its implementation cache. 2017-09-01 18:33:12 -07:00
e62722ce2f Add comments. 2017-09-01 18:32:41 -07:00
ce82fd56dd Remove newlines generated by IntelliJ's addition of @Nullable. 2017-09-01 18:11:19 -07:00
6a65e66935 Format code. 2017-09-01 18:07:48 -07:00
e172108476 Stop using fromInt and the promoteTo function. 2017-09-01 18:07:28 -07:00
2b700d3911 Require applicable interfaces to be passed an implementation they use. 2017-09-01 17:45:32 -07:00
f7c07ca04d Add promotion manager to Abacus. 2017-09-01 17:15:28 -07:00
ecb5139e70 Add a promotion manager to handle promotion. 2017-09-01 17:15:14 -07:00
453cd0ea77 Add type aliases for Kotlin and a simple extension function. 2017-09-01 17:14:54 -07:00
7a296e4e8b Add the ability to retrieve names of plugin implementations. 2017-09-01 17:14:11 -07:00
cbceee4abc Switch number implementation to using Strings. 2017-09-01 17:13:45 -07:00
8ea34b8f6e Merge pull request #8 from DanilaFe/new_image
Add a Logo.
Sorry Arthur, there's literally no reason to wait for approval on this one.
2017-08-30 20:04:12 -07:00
672252ef41 Merge branch 'master' into new_image 2017-08-30 15:35:35 -07:00
824f391fc7 Add the logo to GitHub pages. 2017-08-30 15:31:25 -07:00
63a160659a Merge pull request #7 from DanilaFe/tree-operators
Implement tree operators and functions.
2017-08-30 15:14:41 -07:00
879d09e5b8 Add a logo. 2017-08-30 15:14:08 -07:00
ae0ec0c375 Switch add and multiply to two parameters. 2017-08-29 18:31:47 -07:00
337a38a07d Merge branch 'master' into tree-operators 2017-08-29 18:17:58 -07:00
fbfc68ebfe Merge pull request #6 from DanilaFe/fxml-fix
Move the FXML file for the fx project into the correct location.
2017-08-29 18:17:32 -07:00
01e7a03444 Merge branch 'master' into fxml-fix 2017-08-29 18:14:09 -07:00
0cb180284a Merge pull request #4 from DanilaFe/variable-parsing
Add variables into the parser.
2017-08-29 18:13:35 -07:00
192269ea9a Merge branch 'master' into variable-parsing 2017-08-29 18:11:30 -07:00
f134e5aa04 Merge pull request #5 from DanilaFe/dokka-setup
Add dokka plugin to generate documentation.
2017-08-29 18:09:36 -07:00
e3c37cf10a Bring tests up to date. 2017-08-28 12:59:16 -07:00
823c788148 Move the FXML file for the fx project into the correct location. 2017-08-27 15:49:01 -07:00
ece9f1ae04 Add dokka plugin to generate documentation. 2017-08-27 15:47:32 -07:00
fbc12ec41c Format newly written code. 2017-08-26 12:19:34 -07:00
385a64eace Make ReducerApplicable an independent interface. 2017-08-26 11:52:02 -07:00
c2feedee32 Register precedences of TreeValue operators. 2017-08-25 19:49:37 -07:00
b98b08b872 Make sure TreeValueOperator extends Operator. 2017-08-25 19:49:15 -07:00
f8eb051583 Fix token precedence for variable names / operators. 2017-08-25 19:48:56 -07:00
20b2e77ee1 Add reduction of TreeValue operators. 2017-08-25 19:48:43 -07:00
bfc1ed5819 Fix incorrect template argument. 2017-08-25 18:47:49 -07:00
9d52d55e68 Add TreeValue operator nodes, and parsing for them. 2017-08-25 18:42:41 -07:00
07d7343339 Abstract some Binary and Unary node logic. 2017-08-25 17:46:25 -07:00
73075c57b9 Add registering TreeValueOperators. 2017-08-25 17:31:47 -07:00
5b1a48c02e Convert Applicable interfaces into Kotlin. 2017-08-25 16:07:23 -07:00
ca2681cc21 Add a TreeValueOperator. 2017-08-25 15:49:35 -07:00
8a3c614602 Make applicable into an interface. 2017-08-25 15:49:26 -07:00
da1c78945e Move the code for applicables that require a Reducer. 2017-08-25 15:29:37 -07:00
225a926f86 Move NumberFunction into Kotlin. 2017-08-25 14:59:41 -07:00
f83f2a7aaa Rename Function to NumberFunction. 2017-08-25 14:56:36 -07:00
d04adf4da5 Add Applicable to Operator, therby removing the need for Functions in it 2017-08-25 14:55:05 -07:00
1f0addccea Add documentation loading for functions. 2017-08-25 01:51:14 -07:00
1a47e07e97 Add Tree Value Functions to NumberReducer. 2017-08-25 01:41:51 -07:00
26305c3bae Add the withReducer variants of the Applier functions. 2017-08-25 01:41:32 -07:00
6b9252f902 Add parsing of TreeValueFunctions. 2017-08-25 01:21:28 -07:00
bc26ad0b88 Abstract the call functionality, and add TreeValueFunctionNode. 2017-08-25 01:17:52 -07:00
c5cd0f81ad Remove data modifier from tree classes. 2017-08-25 01:07:59 -07:00
ac19c7b230 Change lexer tokenizer to recognize tree value functions. 2017-08-25 01:03:12 -07:00
40c80db914 Add tree value functions to plugins. 2017-08-25 00:59:39 -07:00
00462281fe Add a function that operates on trees. 2017-08-25 00:49:16 -07:00
01f80bbb53 Abstract some of the Function functionality further. 2017-08-25 00:43:36 -07:00
553c7354c1 Account for the new string-only node structure.
The output has to be the same as the user-provided input, as the
tree isn't converted to numbers until evaluation.
2017-08-18 16:31:54 -07:00
50ede6460c Remove Abacus dependency from ShuntingYardParser. 2017-08-18 15:57:48 -07:00
beb583a231 Move number string parsing from the parser into the reducer. 2017-08-18 14:26:33 -07:00
e0ff229df4 Temporarily substitute 0 for variables. 2017-08-18 14:21:48 -07:00
1c751353f1 Lex and parse variables. 2017-08-18 14:21:14 -07:00
0a15043b63 Implement a variable TreeNode. 2017-08-18 14:20:49 -07:00
21e059c1ca Add a new TokenType for variables. 2017-08-18 14:20:37 -07:00
61 changed files with 1605 additions and 885 deletions

4
.gitignore vendored
View File

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

View File

@@ -1,6 +1,22 @@
buildscript {
ext.kotlin_version = '1.1.3'
ext.dokka_version = '0.9.15'
repositories {
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
}
}
subprojects {
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'org.jetbrains.dokka'
repositories {
mavenCentral()

View File

@@ -1,8 +1,3 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.1.3'
}
dependencies {
compile 'com.moandjiezana.toml:toml4j:0.7.1'
testCompile 'junit:junit:4.12'
}

View File

@@ -1,135 +0,0 @@
package org.nwapw.abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.number.NumberInterface;
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;
/**
* 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(this);
treeBuilder = new TreeBuilder<>(lexerTokenizer, shuntingYardParser);
pluginManager.addListener(shuntingYardParser);
pluginManager.addListener(lexerTokenizer);
}
/**
* 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);
}
/**
* Creates a number from a string.
*
* @param numberString the string to create the number from.
* @return the resulting number.
*/
public NumberInterface numberFromString(String numberString) {
NumberImplementation toInstantiate =
pluginManager.numberImplementationFor(configuration.getNumberImplementation());
if (toInstantiate == null) toInstantiate = DEFAULT_IMPLEMENTATION;
return toInstantiate.instanceForString(numberString);
}
}

View File

@@ -1,159 +0,0 @@
package org.nwapw.abacus.config;
import com.moandjiezana.toml.Toml;
import com.moandjiezana.toml.TomlWriter;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* The configuration object that stores
* options that the user can change.
*/
public class Configuration {
/**
* The defaults TOML string.
*/
private static final String DEFAULT_CONFIG =
"numberImplementation = \"naive\"\n" +
"disabledPlugins = []";
/**
* The defaults TOML object, parsed from the string.
*/
private static final Toml DEFAULT_TOML = new Toml().read(DEFAULT_CONFIG);
/**
* The TOML writer used to write this configuration to a file.
*/
private static final TomlWriter TOML_WRITER = new TomlWriter();
/**
* The computation delay for which the thread can run without interruption.
*/
private double computationDelay = 0;
/**
* The implementation of the number that should be used.
*/
private String numberImplementation = "<default>";
/**
* The list of disabled plugins in this Configuration.
*/
private Set<String> disabledPlugins = new HashSet<>();
/**
* Creates a new configuration form the given configuration.
*
* @param copyFrom the configuration to copy.
*/
public Configuration(Configuration copyFrom) {
copyFrom(copyFrom);
}
/**
* Creates a new configuration with the given values.
*
* @param computationDelay the delay before the computation gets killed.
* @param numberImplementation the number implementation, like "naive" or "precise"
* @param disabledPlugins the list of disabled plugins.
*/
public Configuration(double computationDelay, String numberImplementation, String[] disabledPlugins) {
this.computationDelay = computationDelay;
this.numberImplementation = numberImplementation;
this.disabledPlugins.addAll(Arrays.asList(disabledPlugins));
}
/**
* Loads a configuration from a given file, keeping non-specified fields default.
*
* @param fromFile the file to load from.
*/
public Configuration(File fromFile) {
if (!fromFile.exists()) return;
copyFrom(new Toml(DEFAULT_TOML).read(fromFile).to(Configuration.class));
}
/**
* Copies the values from the given configuration into this one.
*
* @param otherConfiguration the configuration to copy from.
*/
public void copyFrom(Configuration otherConfiguration) {
this.computationDelay = otherConfiguration.computationDelay;
this.numberImplementation = otherConfiguration.numberImplementation;
this.disabledPlugins.addAll(otherConfiguration.disabledPlugins);
}
/**
* Saves this configuration to the given file, creating
* any directories that do not exist.
*
* @param file the file to save to.
*/
public void saveTo(File file) {
if (file.getParentFile() != null) file.getParentFile().mkdirs();
try {
TOML_WRITER.write(this, file);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Gets the value of this configuration as a string.
*
* @return the string that represents this configuration.
*/
public String asTomlString() {
return TOML_WRITER.write(this);
}
/**
* Gets the number implementation from this configuration.
*
* @return the number implementation.
*/
public String getNumberImplementation() {
return numberImplementation;
}
/**
* Sets the number implementation for the configuration
*
* @param numberImplementation the number implementation.
*/
public void setNumberImplementation(String numberImplementation) {
this.numberImplementation = numberImplementation;
}
/**
* Gets the list of disabled plugins.
*
* @return the list of disabled plugins.
*/
public Set<String> getDisabledPlugins() {
return disabledPlugins;
}
/**
* Gets the computation delay specified in the configuration.
*
* @return the computaton delay.
*/
public double getComputationDelay() {
return computationDelay;
}
/**
* Sets the computation delay.
*
* @param computationDelay the new computation delay.
*/
public void setComputationDelay(double computationDelay) {
this.computationDelay = computationDelay;
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,25 @@
package org.nwapw.abacus.exception;
/**
* An exception thrown primarily from Tree Value operators and functions,
* which have to deal with the result of a Reducer as well as the results
* of Applicable.
*/
public class EvaluationException extends AbacusException {
/**
* Creates a new EvaluationException with the default string.
*/
public EvaluationException() {
this("");
}
/**
* Creates a new EvaluationError with the given message string.
* @param message the message string.
*/
public EvaluationException(String message) {
super("Evaluation error", message);
}
}

View File

@@ -6,6 +6,6 @@ package org.nwapw.abacus.function;
*/
public enum DocumentationType {
FUNCTION
FUNCTION, TREE_VALUE_FUNCTION
}

View File

@@ -1,39 +0,0 @@
package org.nwapw.abacus.function;
import org.nwapw.abacus.number.NumberInterface;
/**
* A function that operates on one or more
* inputs and returns a single number.
*/
public abstract class Function {
/**
* Checks whether the given params will work for the given function.
*
* @param params the given params
* @return true if the params can be used with this function.
*/
protected abstract boolean matchesParams(NumberInterface[] params);
/**
* Internal apply implementation, which already receives appropriately promoted
* parameters that have bee run through matchesParams
*
* @param params the promoted parameters.
* @return the return value of the function.
*/
protected abstract NumberInterface applyInternal(NumberInterface[] params);
/**
* Function to check, promote arguments and run the function.
*
* @param params the raw input parameters.
* @return the return value of the function, or null if an error occurred.
*/
public NumberInterface apply(NumberInterface... params) {
if (!matchesParams(params)) return null;
return applyInternal(params);
}
}

View File

@@ -114,15 +114,6 @@ public class NaiveNumber extends NumberInterface {
return (int) value;
}
@Override
public NumberInterface promoteToInternal(Class<? extends NumberInterface> toClass) {
if (toClass == this.getClass()) return this;
else if (toClass == PreciseNumber.class) {
return new PreciseNumber(Double.toString(value));
}
return null;
}
public String toString() {
double shiftBy = Math.pow(10, 10);
return Double.toString(Math.round(value * shiftBy) / shiftBy);

View File

@@ -1,9 +1,11 @@
package org.nwapw.abacus.number;
import org.nwapw.abacus.exception.ComputationInterruptedException;
/**
* An interface used to represent a number.
*/
public abstract class NumberInterface {
public abstract class NumberInterface implements Comparable<NumberInterface> {
/**
* Check if the thread was interrupted and
@@ -156,14 +158,6 @@ public abstract class NumberInterface {
return intPowInternal(exponent);
}
/**
* Compares this number to another.
*
* @param number the number to compare to.
* @return same as Integer.compare();
*/
public abstract int compareTo(NumberInterface number);
/**
* Same as Math.signum().
*
@@ -236,29 +230,6 @@ public abstract class NumberInterface {
*/
public abstract int intValue();
/**
* Promotes this class to another number class.
*
* @param toClass the class to promote to.
* @return the resulting new instance.
*/
@Deprecated
protected abstract NumberInterface promoteToInternal(Class<? extends NumberInterface> toClass);
/**
* Promotes this class to another number class. Also, checks if the
* thread has been interrupted, and if so, throws
* an exception.
*
* @param toClass the class to promote to.
* @return the resulting new instance.
*/
@Deprecated
public final NumberInterface promoteTo(Class<? extends NumberInterface> toClass) {
checkInterrupted();
return promoteToInternal(toClass);
}
/**
* Returns the smallest error this instance can tolerate depending
* on its precision and value.

View File

@@ -152,14 +152,6 @@ public class PreciseNumber extends NumberInterface {
return new PreciseNumber(value.negate());
}
@Override
public NumberInterface promoteToInternal(Class<? extends NumberInterface> toClass) {
if (toClass == this.getClass()) {
return this;
}
return null;
}
@Override
public String toString() {
return value.round(outputContext).toString();

View File

@@ -34,6 +34,7 @@ public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListen
register(" ", TokenType.WHITESPACE);
register(",", TokenType.COMMA);
register("[0-9]*(\\.[0-9]+)?", TokenType.NUM);
register("[a-zA-Z]+", TokenType.VARIABLE);
register("\\(", TokenType.OPEN_PARENTH);
register("\\)", TokenType.CLOSE_PARENTH);
}};
@@ -49,9 +50,15 @@ public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListen
for (String operator : manager.getAllOperators()) {
lexer.register(Pattern.sanitize(operator), TokenType.OP);
}
for (String operator : manager.getAllTreeValueOperators()) {
lexer.register(Pattern.sanitize(operator), TokenType.TREE_VALUE_OP);
}
for (String function : manager.getAllFunctions()) {
lexer.register(Pattern.sanitize(function), TokenType.FUNCTION);
}
for (String function : manager.getAllTreeValueFunctions()) {
lexer.register(Pattern.sanitize(function), TokenType.TREE_VALUE_FUNCTION);
}
}
@Override
@@ -59,9 +66,15 @@ public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListen
for (String operator : manager.getAllOperators()) {
lexer.unregister(Pattern.sanitize(operator), TokenType.OP);
}
for (String operator : manager.getAllTreeValueOperators()) {
lexer.unregister(Pattern.sanitize(operator), TokenType.TREE_VALUE_OP);
}
for (String function : manager.getAllFunctions()) {
lexer.unregister(Pattern.sanitize(function), TokenType.FUNCTION);
}
for (String function : manager.getAllTreeValueFunctions()) {
lexer.unregister(Pattern.sanitize(function), TokenType.TREE_VALUE_FUNCTION);
}
}
}

View File

@@ -1,6 +1,5 @@
package org.nwapw.abacus.parsing;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.function.OperatorAssociativity;
import org.nwapw.abacus.function.OperatorType;
@@ -17,10 +16,6 @@ import java.util.*;
*/
public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListener {
/**
* The Abacus instance used to create number instances.
*/
private Abacus abacus;
/**
* Map of operator precedences, loaded from the plugin operators.
*/
@@ -35,12 +30,9 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
private Map<String, OperatorType> typeMap;
/**
* Creates a new Shunting Yard parser with the given Abacus instance.
*
* @param abacus the abacus instance.
* Creates a new Shunting Yard parser.
*/
public ShuntingYardParser(Abacus abacus) {
this.abacus = abacus;
public ShuntingYardParser() {
precedenceMap = new HashMap<>();
associativityMap = new HashMap<>();
typeMap = new HashMap<>();
@@ -61,12 +53,12 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
Match<TokenType> match = from.remove(0);
previousType = matchType;
matchType = match.getType();
if (matchType == TokenType.NUM) {
if (matchType == TokenType.NUM || matchType == TokenType.VARIABLE) {
output.add(match);
} else if (matchType == TokenType.FUNCTION) {
} else if (matchType == TokenType.FUNCTION || matchType == TokenType.TREE_VALUE_FUNCTION) {
output.add(new Match<>("", TokenType.INTERNAL_FUNCTION_END));
tokenStack.push(match);
} else if (matchType == TokenType.OP) {
} else if (matchType == TokenType.OP || matchType == TokenType.TREE_VALUE_OP) {
String tokenString = match.getContent();
OperatorType type = typeMap.get(tokenString);
int precedence = precedenceMap.get(tokenString);
@@ -78,7 +70,7 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
}
if (tokenString.equals("-") && (previousType == null || previousType == TokenType.OP ||
previousType == TokenType.OPEN_PARENTH)) {
previousType == TokenType.TREE_VALUE_OP || previousType == TokenType.OPEN_PARENTH)) {
from.add(0, new Match<>("`", TokenType.OP));
continue;
}
@@ -86,9 +78,12 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
while (!tokenStack.empty() && type == OperatorType.BINARY_INFIX) {
Match<TokenType> otherMatch = tokenStack.peek();
TokenType otherMatchType = otherMatch.getType();
if (!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break;
if (!(otherMatchType == TokenType.OP ||
otherMatchType == TokenType.TREE_VALUE_OP ||
otherMatchType == TokenType.FUNCTION ||
otherMatchType == TokenType.TREE_VALUE_FUNCTION)) break;
if (otherMatchType == TokenType.OP) {
if (otherMatchType == TokenType.OP || otherMatchType == TokenType.TREE_VALUE_OP) {
int otherPrecedence = precedenceMap.get(otherMatch.getContent());
if (otherPrecedence < precedence ||
(associativity == OperatorAssociativity.RIGHT && otherPrecedence == precedence)) {
@@ -113,7 +108,10 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
while (!tokenStack.empty()) {
Match<TokenType> match = tokenStack.peek();
TokenType newMatchType = match.getType();
if (!(newMatchType == TokenType.OP || newMatchType == TokenType.FUNCTION)) return null;
if (!(newMatchType == TokenType.OP ||
newMatchType == TokenType.TREE_VALUE_OP ||
newMatchType == TokenType.FUNCTION ||
newMatchType == TokenType.TREE_VALUE_FUNCTION)) return null;
output.add(tokenStack.pop());
}
return output;
@@ -129,31 +127,47 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
if (matches.size() == 0) return null;
Match<TokenType> match = matches.remove(0);
TokenType matchType = match.getType();
if (matchType == TokenType.OP) {
if (matchType == TokenType.OP || matchType == TokenType.TREE_VALUE_OP) {
String operator = match.getContent();
OperatorType type = typeMap.get(operator);
if (type == OperatorType.BINARY_INFIX) {
TreeNode right = constructRecursive(matches);
TreeNode left = constructRecursive(matches);
if (left == null || right == null) return null;
else return new BinaryNode(operator, left, right);
if (matchType == TokenType.OP) {
return new NumberBinaryNode(operator, left, right);
} else {
return new TreeValueBinaryNode(operator, left, right);
}
} else {
TreeNode applyTo = constructRecursive(matches);
if (applyTo == null) return null;
else return new UnaryNode(operator, applyTo);
if (matchType == TokenType.OP) {
return new NumberUnaryNode(operator, applyTo);
} else {
return new TreeValueUnaryNode(operator, applyTo);
}
}
} else if (matchType == TokenType.NUM) {
return new NumberNode(abacus.numberFromString(match.getContent()));
} else if (matchType == TokenType.FUNCTION) {
return new NumberNode(match.getContent());
} else if (matchType == TokenType.VARIABLE) {
return new VariableNode(match.getContent());
} else if (matchType == TokenType.FUNCTION || matchType == TokenType.TREE_VALUE_FUNCTION) {
String functionName = match.getContent();
FunctionNode node = new FunctionNode(functionName);
List<TreeNode> children = new ArrayList<>();
while (!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END) {
TreeNode argument = constructRecursive(matches);
if (argument == null) return null;
node.prependChild(argument);
children.add(0, argument);
}
if (matches.isEmpty()) return null;
matches.remove(0);
CallNode node;
if (matchType == TokenType.FUNCTION) {
node = new FunctionNode(functionName, children);
} else {
node = new TreeValueFunctionNode(functionName, children);
}
return node;
}
return null;
@@ -176,6 +190,12 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
associativityMap.put(operator, operatorInstance.getAssociativity());
typeMap.put(operator, operatorInstance.getType());
}
for (String operator : manager.getAllTreeValueOperators()) {
Operator operatorInstance = manager.treeValueOperatorFor(operator);
precedenceMap.put(operator, operatorInstance.getPrecedence());
associativityMap.put(operator, operatorInstance.getAssociativity());
typeMap.put(operator, operatorInstance.getType());
}
}
@Override

View File

@@ -14,7 +14,7 @@ public abstract class NumberImplementation {
/**
* The list of paths through which this implementation can be promoted.
*/
private Map<Class<? extends NumberInterface>, Function<NumberInterface, NumberInterface>> promotionPaths;
private Map<String, Function<NumberInterface, NumberInterface>> promotionPaths;
/**
* The implementation class for this implementation.
*/
@@ -41,7 +41,7 @@ public abstract class NumberImplementation {
*
* @return the map of documentation paths.
*/
public final Map<Class<? extends NumberInterface>, Function<NumberInterface, NumberInterface>> getPromotionPaths() {
public final Map<String, Function<NumberInterface, NumberInterface>> getPromotionPaths() {
return promotionPaths;
}

View File

@@ -1,9 +1,6 @@
package org.nwapw.abacus.plugin;
import org.nwapw.abacus.function.Documentation;
import org.nwapw.abacus.function.DocumentationType;
import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NumberInterface;
/**
@@ -65,10 +62,21 @@ public abstract class Plugin {
* @param name the name to register by.
* @param toRegister the function implementation.
*/
protected final void registerFunction(String name, Function toRegister) {
protected final void registerFunction(String name, NumberFunction toRegister) {
manager.registerFunction(name, toRegister);
}
/**
* To be used in load(). Registers a tree value function abstract class
* with the plugin internally, which makes it accessible to the plugin manager.
*
* @param name the name to register by.
* @param toRegister the tree value function implementation.
*/
protected final void registerTreeValueFunction(String name, TreeValueFunction toRegister) {
manager.registerTreeValueFunction(name, toRegister);
}
/**
* To be used in load(). Registers an operator abstract class
* with the plugin internally, which makes it accessible to
@@ -77,10 +85,22 @@ public abstract class Plugin {
* @param name the name of the operator.
* @param operator the operator to register.
*/
protected final void registerOperator(String name, Operator operator) {
protected final void registerOperator(String name, NumberOperator operator) {
manager.registerOperator(name, operator);
}
/**
* To be used in load(). Registers an operator
* with the plugin internally, which makes it accessible
* to the plugin manager.
*
* @param name the name of the tree value operator.
* @param operator the tree value operator to register.
*/
protected final void registerTreeValueOperator(String name, TreeValueOperator operator) {
manager.registerTreeValueOperator(name, operator);
}
/**
* To be used in load(). Registers a new number implementation with the plugin.
* This makes it accessible to the plugin manager.
@@ -110,10 +130,22 @@ public abstract class Plugin {
* @param name the name for which to search
* @return the resulting function, or null if none was found for that name.
*/
protected final Function functionFor(String name) {
protected final NumberFunction functionFor(String name) {
return manager.functionFor(name);
}
/**
* Searches the PluginManager for the given function name.
* This can be used by the plugins internally in order to call functions
* they do not provide.
*
* @param name the name for which to search.
* @return the resulting tree value function, or null if none was found for that name.
*/
protected final TreeValueFunction treeValueFunctionFor(String name) {
return manager.treeValueFunctionFor(name);
}
/**
* Searches the PluginManager for the given operator name.
* This can be used by the plugins internally in order to call
@@ -122,10 +154,22 @@ public abstract class Plugin {
* @param name the name for which to search
* @return the resulting operator, or null if none was found for that name.
*/
protected final Operator operatorFor(String name) {
protected final NumberOperator operatorFor(String name) {
return manager.operatorFor(name);
}
/**
* Searches the PluginManager for the given tree value operator name.
* This can be used by the plugins internally in order to call
* operations they do not provide.
*
* @param name the name for which to search.
* @return the resulting tree value operator, or null if none was found for that name.
*/
protected final TreeValueOperator treeValueOperatorFor(String name) {
return manager.treeValueOperatorFor(name);
}
/**
* Searches the PluginManager for the given number implementation
* name. This can be used by the plugins internally in order to find

View File

@@ -1,10 +1,7 @@
package org.nwapw.abacus.plugin;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.Documentation;
import org.nwapw.abacus.function.DocumentationType;
import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NumberInterface;
import java.lang.reflect.InvocationTargetException;
@@ -30,11 +27,19 @@ public class PluginManager {
/**
* The map of functions registered by the plugins.
*/
private Map<String, Function> registeredFunctions;
private Map<String, NumberFunction> registeredFunctions;
/**
* The map of tree value functions regstered by the plugins.
*/
private Map<String, TreeValueFunction> registeredTreeValueFunctions;
/**
* The map of operators registered by the plugins
*/
private Map<String, Operator> registeredOperators;
private Map<String, NumberOperator> registeredOperators;
/**
* The map of tree value operators registered by the plugins.
*/
private Map<String, TreeValueOperator> registeredTreeValueOperators;
/**
* The map of number implementations registered by the plugins.
*/
@@ -44,10 +49,13 @@ public class PluginManager {
*/
private Set<Documentation> registeredDocumentation;
/**
* The list of number implementations that have been
* found by their implementation class.
* The list of number implementation names.
*/
private Map<Class<? extends NumberInterface>, NumberImplementation> cachedInterfaceImplementations;
private Map<Class<? extends NumberInterface>, String> interfaceImplementationNames;
/**
* The list of number implementations.
*/
private Map<Class<? extends NumberInterface>, NumberImplementation> interfaceImplementations;
/**
* The pi values for each implementation class that have already been computer.
*/
@@ -72,10 +80,13 @@ public class PluginManager {
loadedPluginClasses = new HashSet<>();
plugins = new HashSet<>();
registeredFunctions = new HashMap<>();
registeredTreeValueFunctions = new HashMap<>();
registeredOperators = new HashMap<>();
registeredTreeValueOperators = new HashMap<>();
registeredNumberImplementations = new HashMap<>();
registeredDocumentation = new HashSet<>();
cachedInterfaceImplementations = new HashMap<>();
interfaceImplementations = new HashMap<>();
interfaceImplementationNames = new HashMap<>();
cachedPi = new HashMap<>();
listeners = new HashSet<>();
}
@@ -86,20 +97,40 @@ public class PluginManager {
* @param name the name of the function.
* @param function the function to register.
*/
public void registerFunction(String name, Function function) {
public void registerFunction(String name, NumberFunction function) {
registeredFunctions.put(name, function);
}
/**
* Registers a tree value function under the given name.
*
* @param name the name of the function.
* @param function the function to register.
*/
public void registerTreeValueFunction(String name, TreeValueFunction function) {
registeredTreeValueFunctions.put(name, function);
}
/**
* Registers an operator under the given name.
*
* @param name the name of the operator.
* @param operator the operator to register.
*/
public void registerOperator(String name, Operator operator) {
public void registerOperator(String name, NumberOperator operator) {
registeredOperators.put(name, operator);
}
/**
* Registers a tree value operator under the given name.
*
* @param name the name of the tree value operator.
* @param operator the tree value operator to register.
*/
public void registerTreeValueOperator(String name, TreeValueOperator operator) {
registeredTreeValueOperators.put(name, operator);
}
/**
* Registers a number implementation under the given name.
*
@@ -108,6 +139,9 @@ public class PluginManager {
*/
public void registerNumberImplementation(String name, NumberImplementation implementation) {
registeredNumberImplementations.put(name, implementation);
interfaceImplementationNames.put(implementation.getImplementation(), name);
interfaceImplementations.put(implementation.getImplementation(), implementation);
cachedPi.put(implementation.getImplementation(), implementation.instanceForPi());
}
/**
@@ -126,20 +160,40 @@ public class PluginManager {
* @param name the name of the function.
* @return the function, or null if it was not found.
*/
public Function functionFor(String name) {
public NumberFunction functionFor(String name) {
return registeredFunctions.get(name);
}
/**
* Gets the tree value function registered under the given name.
*
* @param name the name of the function.
* @return the function, or null if it was not found.
*/
public TreeValueFunction treeValueFunctionFor(String name) {
return registeredTreeValueFunctions.get(name);
}
/**
* Gets the operator registered under the given name.
*
* @param name the name of the operator.
* @return the operator, or null if it was not found.
*/
public Operator operatorFor(String name) {
public NumberOperator operatorFor(String name) {
return registeredOperators.get(name);
}
/**
* Gets the tree value operator registered under the given name.
*
* @param name the name of the tree value operator.
* @return the operator, or null if it was not found.
*/
public TreeValueOperator treeValueOperatorFor(String name) {
return registeredTreeValueOperators.get(name);
}
/**
* Gets the number implementation registered under the given name.
*
@@ -165,10 +219,6 @@ public class PluginManager {
break;
}
}
if (toReturn == null) {
toReturn = new Documentation(name, "", "", "", type);
registerDocumentation(toReturn);
}
return toReturn;
}
@@ -179,17 +229,17 @@ public class PluginManager {
* @return the implementation.
*/
public NumberImplementation interfaceImplementationFor(Class<? extends NumberInterface> name) {
if (cachedInterfaceImplementations.containsKey(name)) return cachedInterfaceImplementations.get(name);
NumberImplementation toReturn = null;
for (String key : registeredNumberImplementations.keySet()) {
NumberImplementation implementation = registeredNumberImplementations.get(key);
if (implementation.getImplementation() == name) {
toReturn = implementation;
break;
}
}
cachedInterfaceImplementations.put(name, toReturn);
return toReturn;
return interfaceImplementations.get(name);
}
/**
* Gets the number implementation name for the given implementation class.
*
* @param name the class for which to find the implementation name.
* @return the implementation name.
*/
public String interfaceImplementationNameFor(Class<? extends NumberInterface> name) {
return interfaceImplementationNames.get(name);
}
/**
@@ -199,14 +249,7 @@ public class PluginManager {
* @return pi
*/
public NumberInterface piFor(Class<? extends NumberInterface> forClass) {
if (cachedPi.containsKey(forClass)) return cachedPi.get(forClass);
NumberImplementation implementation = interfaceImplementationFor(forClass);
NumberInterface generatedPi = null;
if (implementation != null) {
generatedPi = implementation.instanceForPi();
}
cachedPi.put(forClass, generatedPi);
return generatedPi;
return cachedPi.get(forClass);
}
/**
@@ -277,10 +320,13 @@ public class PluginManager {
plugin.disable();
}
registeredFunctions.clear();
registeredTreeValueFunctions.clear();
registeredOperators.clear();
registeredTreeValueOperators.clear();
registeredNumberImplementations.clear();
registeredDocumentation.clear();
cachedInterfaceImplementations.clear();
interfaceImplementationNames.clear();
interfaceImplementations.clear();
cachedPi.clear();
listeners.forEach(e -> e.onUnload(this));
}
@@ -302,6 +348,15 @@ public class PluginManager {
return registeredFunctions.keySet();
}
/**
* Gets all the tree vlaue functions loaded by the PluginManager.
*
* @return the set of all the tree value functions that were loaded.
*/
public Set<String> getAllTreeValueFunctions() {
return registeredTreeValueFunctions.keySet();
}
/**
* Gets all the operators loaded by the Plugin Manager.
*
@@ -311,6 +366,15 @@ public class PluginManager {
return registeredOperators.keySet();
}
/**
* Gets all the tree value operators loaded by the PluginManager.
*
* @return the set of all tree value operators that were loaded.
*/
public Set<String> getAllTreeValueOperators() {
return registeredTreeValueOperators.keySet();
}
/**
* Gets all the number implementations loaded by the Plugin Manager.
*
@@ -347,4 +411,5 @@ public class PluginManager {
public Set<Class<?>> getLoadedPluginClasses() {
return loadedPluginClasses;
}
}

View File

@@ -1,13 +1,15 @@
package org.nwapw.abacus.plugin;
import org.nwapw.abacus.context.MutableEvaluationContext;
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.TreeNode;
import org.nwapw.abacus.tree.VariableNode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.function.BiFunction;
/**
* The plugin providing standard functions such as addition and subtraction to
@@ -16,85 +18,95 @@ import java.util.function.BiFunction;
public class StandardPlugin extends Plugin {
/**
* The addition operator, +
* The set operator.
*/
public static final Operator OP_ADD = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0, new Function() {
public final TreeValueOperator opSet = new TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length >= 1;
public boolean matchesParams(MutableEvaluationContext context, TreeNode[] params) {
return params.length == 2 && params[0] instanceof VariableNode;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface sum = params[0];
for (int i = 1; i < params.length; i++) {
sum = sum.add(params[i]);
}
return sum;
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 subtraction operator, -
* The define operator.
*/
public static final Operator OP_SUBTRACT = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0, new Function() {
public final TreeValueOperator opDefine = new TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override
protected boolean matchesParams(NumberInterface[] params) {
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, +
*/
public static final NumberOperator OP_ADD = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].add(params[1]);
}
};
/**
* The subtraction operator, -
*/
public static final NumberOperator OP_SUBTRACT = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@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, -
*/
public static final Operator OP_NEGATE = new Operator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0, new Function() {
public static final NumberOperator OP_NEGATE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0) {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].negate();
}
});
};
/**
* The multiplication operator, *
*/
public static final Operator OP_MULTIPLY = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() {
public static final NumberOperator OP_MULTIPLY = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length >= 1;
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface product = params[0];
for (int i = 1; i < params.length; i++) {
product = product.multiply(params[i]);
}
return product;
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].multiply(params[1]);
}
});
/**
* The combination operator.
*/
public static final Operator OP_NCR = new Operator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2 && params[0].fractionalPart().signum() == 0
&& params[1].fractionalPart().signum() == 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return OP_NPR.getFunction().apply(params).divide(OP_FACTORIAL.getFunction().apply(params[1]));
}
});
};
/**
* The implementation for double-based naive numbers.
*/
@@ -120,7 +132,8 @@ public class StandardPlugin extends Plugin {
@Override
public NumberInterface instanceForPi() {
NumberInterface C = FUNCTION_SQRT.apply(new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
MutableEvaluationContext dummyContext = new MutableEvaluationContext(null, this, null);
NumberInterface C = FUNCTION_SQRT.apply(dummyContext, new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
NumberInterface M = PreciseNumber.ONE;
NumberInterface L = new PreciseNumber("13591409");
NumberInterface X = M;
@@ -145,42 +158,39 @@ public class StandardPlugin extends Plugin {
return C.divide(sum);
}
};
/**
* Stores objects of NumberInterface with integer values for reuse.
*/
private final static HashMap<Class<? extends NumberInterface>, HashMap<Integer, NumberInterface>> integerValues = new HashMap<>();
/**
* The division operator, /
*/
public static final Operator OP_DIVIDE = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() {
public static final NumberOperator OP_DIVIDE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2 && params[1].compareTo(fromInt(params[0].getClass(), 0)) != 0;
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2 && params[1].compareTo(context.getInheritedNumberImplementation().instanceForString(Integer.toString(0))) != 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].divide(params[1]);
}
});
};
/**
* The factorial operator, !
*/
public static final Operator OP_FACTORIAL = new Operator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0, new Function() {
public static final NumberOperator OP_FACTORIAL = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0) {
//private HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> storedList = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>();
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1
&& params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0
&& params[0].fractionalPart().compareTo(context.getInheritedNumberImplementation().instanceForString("0")) == 0
&& params[0].signum() >= 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
if (params[0].signum() == 0) {
return fromInt(params[0].getClass(), 1);
return implementation.instanceForString("1");
}
NumberInterface one = fromInt(params[0].getClass(), 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.
@@ -194,75 +204,92 @@ public class StandardPlugin extends Plugin {
storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass()));
}*/
}
});
};
/**
* The permutation operator.
*/
public static final Operator OP_NPR = new Operator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0, new Function() {
public static final NumberOperator OP_NPR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2 && params[0].fractionalPart().signum() == 0
&& params[1].fractionalPart().signum() == 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
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 fromInt(params[0].getClass(), 0);
NumberInterface total = fromInt(params[0].getClass(), 1);
(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(fromInt(params[0].getClass(), 2));
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(fromInt(params[0].getClass(), 1));
multiplyBy = multiplyBy.subtract(fromInt(params[0].getClass(), 1));
remainingMultiplications = remainingMultiplications.subtract(implementation.instanceForString("1"));
multiplyBy = multiplyBy.subtract(implementation.instanceForString("1"));
}
return total;
}
});
};
/**
* The combination operator.
*/
public static final NumberOperator OP_NCR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
@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
*/
public static final Function FUNCTION_ABS = new Function() {
public static final NumberFunction FUNCTION_ABS = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].multiply(fromInt(params[0].getClass(), params[0].signum()));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].multiply(context.getInheritedNumberImplementation().instanceForString(Integer.toString(params[0].signum())));
}
};
/**
* The natural log function.
*/
public static final Function FUNCTION_LN = new Function() {
public static final NumberFunction FUNCTION_LN = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1 && params[0].compareTo(fromInt(params[0].getClass(), 0)) > 0;
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1 && params[0].compareTo(context.getInheritedNumberImplementation().instanceForString("0")) > 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
NumberInterface param = params[0];
NumberInterface one = fromInt(param.getClass(), 1);
NumberInterface one = implementation.instanceForString("1");
int powersOf2 = 0;
while (FUNCTION_ABS.apply(param.subtract(one)).compareTo(new NaiveNumber(0.1).promoteTo(param.getClass())) >= 0) {
while (FUNCTION_ABS.apply(context, param.subtract(one)).compareTo(implementation.instanceForString(".1")) >= 0) {
if (param.subtract(one).signum() == 1) {
param = param.divide(fromInt(param.getClass(), 2));
param = param.divide(implementation.instanceForString("2"));
powersOf2++;
if (param.subtract(one).signum() != 1) {
break;
//No infinite loop for you.
}
} else {
param = param.multiply(fromInt(param.getClass(), 2));
param = param.multiply(implementation.instanceForString("2"));
powersOf2--;
if (param.subtract(one).signum() != -1) {
break;
@@ -270,7 +297,7 @@ public class StandardPlugin extends Plugin {
}
}
}
return getLog2(param).multiply(fromInt(param.getClass(), powersOf2)).add(getLogPartialSum(param));
return getLog2(context.getInheritedNumberImplementation(), param).multiply(implementation.instanceForString(Integer.toString(powersOf2))).add(getLogPartialSum(context, param));
}
/**
@@ -279,16 +306,16 @@ public class StandardPlugin extends Plugin {
* @param x value at which the series is evaluated. 0 < x < 2. (x=2 is convergent but impractical.)
* @return the partial sum.
*/
private NumberInterface getLogPartialSum(NumberInterface x) {
private NumberInterface getLogPartialSum(MutableEvaluationContext context, NumberInterface x) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
NumberInterface maxError = x.getMaxError();
x = x.subtract(fromInt(x.getClass(), 1)); //Terms used are for log(x+1).
x = x.subtract(implementation.instanceForString("1")); //Terms used are for log(x+1).
NumberInterface currentNumerator = x, currentTerm = x, sum = x;
int n = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) {
while (FUNCTION_ABS.apply(context, currentTerm).compareTo(maxError) > 0) {
n++;
currentNumerator = currentNumerator.multiply(x).negate();
currentTerm = currentNumerator.divide(fromInt(x.getClass(), n));
currentTerm = currentNumerator.divide(implementation.instanceForString(Integer.toString(n)));
sum = sum.add(currentTerm);
}
return sum;
@@ -299,21 +326,21 @@ public class StandardPlugin extends Plugin {
* @param number a number of the same type as the return type. (Used for precision.)
* @return the value of log(2) with the appropriate precision.
*/
private NumberInterface getLog2(NumberInterface number) {
private NumberInterface getLog2(NumberImplementation implementation, NumberInterface number) {
NumberInterface maxError = number.getMaxError();
//NumberInterface errorBound = fromInt(number.getClass(), 1);
//NumberInterface errorBound = implementation.instanceForString("1");
//We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n)
//In the following, a=1/3^n, b=1/4^n, c = 1/n.
//a is also an error bound.
NumberInterface a = fromInt(number.getClass(), 1), b = a, c = a;
NumberInterface sum = fromInt(number.getClass(), 0);
NumberInterface one = fromInt(number.getClass(), 1);
NumberInterface a = implementation.instanceForString("1"), b = a, c = a;
NumberInterface sum = implementation.instanceForString("0");
NumberInterface one = implementation.instanceForString("1");
int n = 0;
while (a.compareTo(maxError) >= 1) {
n++;
a = a.divide(fromInt(number.getClass(), 3));
b = b.divide(fromInt(number.getClass(), 4));
c = one.divide(fromInt(number.getClass(), n));
a = a.divide(implementation.instanceForString("3"));
b = b.divide(implementation.instanceForString("4"));
c = one.divide(implementation.instanceForString(Integer.toString(n)));
sum = sum.add(a.add(b).multiply(c));
}
return sum;
@@ -322,24 +349,24 @@ public class StandardPlugin extends Plugin {
/**
* Gets a random number smaller or equal to the given number's integer value.
*/
public static final Function FUNCTION_RAND_INT = new Function() {
public static final NumberFunction FUNCTION_RAND_INT = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return fromInt(params[0].getClass(), (int) Math.round(Math.random() * params[0].floor().intValue()));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return context.getInheritedNumberImplementation().instanceForString(Long.toString(Math.round(Math.random() * params[0].floor().intValue())));
}
};
/**
* The caret / pow operator, ^
*/
public static final Operator OP_CARET = new Operator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2, new Function() {
public static final NumberOperator OP_CARET = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2) {
@Override
protected boolean matchesParams(NumberInterface[] params) {
NumberInterface zero = fromInt(params[0].getClass(), 0);
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)
@@ -347,65 +374,67 @@ public class StandardPlugin extends Plugin {
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface zero = fromInt(params[0].getClass(), 0);
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 fromInt(params[0].getClass(), 1);
return implementation.instanceForString("1");
//Detect integer bases:
if (params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0
&& FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[0].getClass(), Integer.MAX_VALUE)) < 0
&& FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[1].getClass(), 1)) >= 0) {
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(newParams));
return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(context, newParams));
}
return FUNCTION_EXP.apply(FUNCTION_LN.apply(FUNCTION_ABS.apply(params[0])).multiply(params[1]));
return FUNCTION_EXP.apply(context, FUNCTION_LN.apply(context, FUNCTION_ABS.apply(context, params[0])).multiply(params[1]));
}
});
};
/**
* The square root function.
*/
public static final Function FUNCTION_SQRT = new Function() {
public static final NumberFunction FUNCTION_SQRT = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return OP_CARET.getFunction().apply(params[0], ((new NaiveNumber(0.5)).promoteTo(params[0].getClass())));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return OP_CARET.apply(context, params[0], context.getInheritedNumberImplementation().instanceForString(".5"));
}
};
private static final HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
private static final HashMap<NumberImplementation, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
/**
* The exponential function, exp(1) = e^1 = 2.71...
*/
public static final Function FUNCTION_EXP = new Function() {
public static final NumberFunction FUNCTION_EXP = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
NumberInterface maxError = params[0].getMaxError();
int n = 0;
if (params[0].signum() < 0) {
NumberInterface[] negatedParams = {params[0].negate()};
return fromInt(params[0].getClass(), 1).divide(applyInternal(negatedParams));
return implementation.instanceForString("1").divide(applyInternal(context, negatedParams));
} else {
//We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!.
//right and left refer to lhs and rhs in the above inequality.
NumberInterface sum = fromInt(params[0].getClass(), 1);
NumberInterface sum = implementation.instanceForString("1");
NumberInterface nextNumerator = params[0];
NumberInterface left = params[0].multiply(fromInt(params[0].getClass(), 3).intPow(params[0].ceiling().intValue())), right = maxError;
NumberInterface left = params[0].multiply(implementation.instanceForString("3").intPow(params[0].ceiling().intValue())), right = maxError;
do {
sum = sum.add(nextNumerator.divide(factorial(params[0].getClass(), n + 1)));
sum = sum.add(nextNumerator.divide(factorial(implementation, n + 1)));
n++;
nextNumerator = nextNumerator.multiply(params[0]);
left = left.multiply(params[0]);
NumberInterface nextN = fromInt(params[0].getClass(), n + 1);
NumberInterface nextN = implementation.instanceForString(Integer.toString(n + 1));
right = right.multiply(nextN);
//System.out.println(left + ", " + right);
}
@@ -418,126 +447,128 @@ public class StandardPlugin extends Plugin {
/**
* The sine function (the argument is interpreted in radians).
*/
public final Function functionSin = new Function() {
public final NumberFunction functionSin = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
NumberInterface pi = piFor(params[0].getClass());
NumberInterface twoPi = pi.multiply(fromInt(pi.getClass(), 2));
NumberInterface theta = getSmallAngle(params[0], pi);
NumberInterface twoPi = pi.multiply(implementation.instanceForString("2"));
NumberInterface theta = getSmallAngle(context, params[0], pi);
//System.out.println(theta);
if (theta.compareTo(pi.multiply(new NaiveNumber(1.5).promoteTo(twoPi.getClass()))) >= 0) {
if (theta.compareTo(pi.multiply(implementation.instanceForString("1.5"))) >= 0) {
theta = theta.subtract(twoPi);
} else if (theta.compareTo(pi.divide(fromInt(pi.getClass(), 2))) > 0) {
} else if (theta.compareTo(pi.divide(implementation.instanceForString("2"))) > 0) {
theta = pi.subtract(theta);
}
//System.out.println(theta);
return sinTaylor(theta);
return sinTaylor(context, theta);
}
};
/**
* The cosine function (the argument is in radians).
*/
public final Function functionCos = new Function() {
public final NumberFunction functionCos = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return functionSin.apply(context, piFor(params[0].getClass()).divide(context.getInheritedNumberImplementation().instanceForString("2"))
.subtract(params[0]));
}
};
/**
* The tangent function (the argument is in radians).
*/
public final Function functionTan = new Function() {
public final NumberFunction functionTan = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(params[0]).divide(functionCos.apply(params[0]));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return functionSin.apply(context, params[0]).divide(functionCos.apply(context, params[0]));
}
};
/**
* The secant function (the argument is in radians).
*/
public final Function functionSec = new Function() {
public final NumberFunction functionSec = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return fromInt(params[0].getClass(), 1).divide(functionCos.apply(params[0]));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return context.getInheritedNumberImplementation().instanceForString("1").divide(functionCos.apply(context, params[0]));
}
};
/**
* The cosecant function (the argument is in radians).
*/
public final Function functionCsc = new Function() {
public final NumberFunction functionCsc = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return fromInt(params[0].getClass(), 1).divide(functionSin.apply(params[0]));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return context.getInheritedNumberImplementation().instanceForString("1").divide(functionSin.apply(context, params[0]));
}
};
/**
* The cotangent function (the argument is in radians).
*/
public final Function functionCot = new Function() {
public final NumberFunction functionCot = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionCos.apply(params[0]).divide(functionSin.apply(params[0]));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return functionCos.apply(context, params[0]).divide(functionSin.apply(context, params[0]));
}
};
/**
* The arcsine function (return type in radians).
*/
public final Function functionArcsin = new Function() {
public final NumberFunction functionArcsin = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1
&& FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) <= 0;
&& FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) <= 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
if (FUNCTION_ABS.apply(params[0]).compareTo(new NaiveNumber(0.8).promoteTo(params[0].getClass())) >= 0) {
NumberInterface[] newParams = {FUNCTION_SQRT.apply(fromInt(params[0].getClass(), 1).subtract(params[0].multiply(params[0])))};
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(applyInternal(newParams)).multiply(fromInt(params[0].getClass(), params[0].signum()));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
if (FUNCTION_ABS.apply(context, params[0]).compareTo(implementation.instanceForString(".8")) >= 0) {
NumberInterface[] newParams = {FUNCTION_SQRT.apply(context, implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
.subtract(applyInternal(context, newParams)).multiply(implementation.instanceForString(Integer.toString(params[0].signum())));
}
NumberInterface currentTerm = params[0], sum = currentTerm,
multiplier = currentTerm.multiply(currentTerm), summandBound = sum.getMaxError().multiply(fromInt(sum.getClass(), 1).subtract(multiplier)),
power = currentTerm, coefficient = fromInt(params[0].getClass(), 1);
multiplier = currentTerm.multiply(currentTerm), summandBound = sum.getMaxError().multiply(implementation.instanceForString("1").subtract(multiplier)),
power = currentTerm, coefficient = implementation.instanceForString("1");
int exponent = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(summandBound) > 0) {
while (FUNCTION_ABS.apply(context, currentTerm).compareTo(summandBound) > 0) {
exponent += 2;
power = power.multiply(multiplier);
coefficient = coefficient.multiply(fromInt(params[0].getClass(), exponent - 2))
.divide(fromInt(params[0].getClass(), exponent - 1));
currentTerm = power.multiply(coefficient).divide(fromInt(power.getClass(), exponent));
coefficient = coefficient.multiply(implementation.instanceForString(Integer.toString(exponent - 2)))
.divide(implementation.instanceForString(Integer.toString(exponent - 1)));
currentTerm = power.multiply(coefficient).divide(implementation.instanceForString(Integer.toString(exponent)));
sum = sum.add(currentTerm);
}
return sum;
@@ -547,86 +578,87 @@ public class StandardPlugin extends Plugin {
/**
* The arccosine function.
*/
public final Function functionArccos = new Function() {
public final NumberFunction functionArccos = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) <= 0;
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) <= 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(functionArcsin.apply(params));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return piFor(params[0].getClass()).divide(context.getInheritedNumberImplementation().instanceForString("2"))
.subtract(functionArcsin.apply(context, params));
}
};
/**
* The arccosecant function.
*/
public final Function functionArccsc = new Function() {
public final NumberFunction functionArccsc = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) >= 0;
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) >= 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface[] reciprocalParamArr = {fromInt(params[0].getClass(), 1).divide(params[0])};
return functionArcsin.apply(reciprocalParamArr);
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberInterface[] reciprocalParamArr = {context.getInheritedNumberImplementation().instanceForString("1").divide(params[0])};
return functionArcsin.apply(context, reciprocalParamArr);
}
};
/**
* The arcsecant function.
*/
public final Function functionArcsec = new Function() {
public final NumberFunction functionArcsec = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) >= 0;
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) >= 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface[] reciprocalParamArr = {fromInt(params[0].getClass(), 1).divide(params[0])};
return functionArccos.apply(reciprocalParamArr);
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberInterface[] reciprocalParamArr = {context.getInheritedNumberImplementation().instanceForString("1").divide(params[0])};
return functionArccos.apply(context, reciprocalParamArr);
}
};
/**
* The arctangent function.
*/
public final Function functionArctan = new Function() {
public final NumberFunction functionArctan = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
NumberImplementation implementation = context.getInheritedNumberImplementation();
if (params[0].signum() == -1) {
NumberInterface[] negatedParams = {params[0].negate()};
return applyInternal(negatedParams).negate();
return applyInternal(context, negatedParams).negate();
}
if (params[0].compareTo(fromInt(params[0].getClass(), 1)) > 0) {
NumberInterface[] reciprocalParams = {fromInt(params[0].getClass(), 1).divide(params[0])};
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(applyInternal(reciprocalParams));
if (params[0].compareTo(implementation.instanceForString("1")) > 0) {
NumberInterface[] reciprocalParams = {implementation.instanceForString("1").divide(params[0])};
return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
.subtract(applyInternal(context, reciprocalParams));
}
if (params[0].compareTo(fromInt(params[0].getClass(), 1)) == 0) {
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 4));
if (params[0].compareTo(implementation.instanceForString("1")) == 0) {
return piFor(params[0].getClass()).divide(implementation.instanceForString("4"));
}
if (params[0].compareTo(new NaiveNumber(0.9).promoteTo(params[0].getClass())) >= 0) {
NumberInterface[] newParams = {params[0].multiply(fromInt(params[0].getClass(), 2))
.divide(fromInt(params[0].getClass(), 1).subtract(params[0].multiply(params[0])))};
return applyInternal(newParams).divide(fromInt(params[0].getClass(), 2));
if (params[0].compareTo(implementation.instanceForString(".9")) >= 0) {
NumberInterface[] newParams = {params[0].multiply(implementation.instanceForString("2"))
.divide(implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
return applyInternal(context, newParams).divide(implementation.instanceForString("2"));
}
NumberInterface currentPower = params[0], currentTerm = currentPower, sum = currentTerm,
maxError = params[0].getMaxError(), multiplier = currentPower.multiply(currentPower).negate();
int n = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) {
while (FUNCTION_ABS.apply(context, currentTerm).compareTo(maxError) > 0) {
n += 2;
currentPower = currentPower.multiply(multiplier);
currentTerm = currentPower.divide(fromInt(currentPower.getClass(), n));
currentTerm = currentPower.divide(implementation.instanceForString(Integer.toString(n)));
sum = sum.add(currentTerm);
}
return sum;
@@ -636,16 +668,16 @@ public class StandardPlugin extends Plugin {
/**
* The arccotangent function. Range: (0, pi).
*/
public final Function functionArccot = new Function() {
public final NumberFunction functionArccot = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(functionArctan.apply(params));
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return piFor(params[0].getClass()).divide(context.getInheritedNumberImplementation().instanceForString("2"))
.subtract(functionArctan.apply(context, params));
}
};
@@ -653,40 +685,24 @@ public class StandardPlugin extends Plugin {
super(manager);
}
/**
* Returns a partial sum of a series whose terms are given by the nthTermFunction, evaluated at x.
*
* @param x the value at which the series is evaluated.
* @param nthTermFunction the function that returns the nth term of the series, in the format term(x, n).
* @param n the number of terms in the partial sum.
* @return the value of the partial sum that has the same class as x.
*/
private static NumberInterface sumSeries(NumberInterface x, BiFunction<Integer, NumberInterface, NumberInterface> nthTermFunction, int n) {
NumberInterface sum = fromInt(x.getClass(), 0);
for (int i = 0; i <= n; i++) {
sum = sum.add(nthTermFunction.apply(i, x));
}
return sum;
}
/**
* A factorial function that uses memoization for each number class; it efficiently
* computes factorials of non-negative integers.
*
* @param numberClass type of number to return.
* @param n non-negative integer.
* @param implementation type of number to return.
* @param n non-negative integer.
* @return a number of numClass with value n factorial.
*/
public static NumberInterface factorial(Class<? extends NumberInterface> numberClass, int n) {
if (!FACTORIAL_LISTS.containsKey(numberClass)) {
FACTORIAL_LISTS.put(numberClass, new ArrayList<>());
FACTORIAL_LISTS.get(numberClass).add(fromInt(numberClass, 1));
FACTORIAL_LISTS.get(numberClass).add(fromInt(numberClass, 1));
synchronized public static NumberInterface factorial(NumberImplementation implementation, int n) {
if (!FACTORIAL_LISTS.containsKey(implementation)) {
FACTORIAL_LISTS.put(implementation, new ArrayList<>());
FACTORIAL_LISTS.get(implementation).add(implementation.instanceForString("1"));
FACTORIAL_LISTS.get(implementation).add(implementation.instanceForString("1"));
}
ArrayList<NumberInterface> list = FACTORIAL_LISTS.get(numberClass);
ArrayList<NumberInterface> list = FACTORIAL_LISTS.get(implementation);
if (n >= list.size()) {
while (list.size() < n + 16) {
list.add(list.get(list.size() - 1).multiply(fromInt(numberClass, list.size())));
list.add(list.get(list.size() - 1).multiply(implementation.instanceForString(Integer.toString(list.size()))));
}
}
return list.get(n);
@@ -698,16 +714,16 @@ public class StandardPlugin extends Plugin {
* @param x where the series is evaluated.
* @return the value of the series
*/
private static NumberInterface sinTaylor(NumberInterface x) {
private static NumberInterface sinTaylor(MutableEvaluationContext context, NumberInterface x) {
NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x;
NumberInterface maxError = x.getMaxError();
int n = 1;
do {
n += 2;
power = power.multiply(multiplier);
currentTerm = power.divide(factorial(x.getClass(), n));
currentTerm = power.divide(factorial(context.getInheritedNumberImplementation(), n));
sum = sum.add(currentTerm);
} while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0);
} while (FUNCTION_ABS.apply(context, currentTerm).compareTo(maxError) > 0);
return sum;
}
@@ -717,33 +733,16 @@ public class StandardPlugin extends Plugin {
* @param phi an angle (in radians).
* @return theta in [0, 2pi) that differs from phi by a multiple of 2pi.
*/
private static NumberInterface getSmallAngle(NumberInterface phi, NumberInterface pi) {
NumberInterface twoPi = pi.multiply(fromInt(pi.getClass(), 2));
NumberInterface theta = FUNCTION_ABS.apply(phi).subtract(twoPi
.multiply(FUNCTION_ABS.apply(phi).divide(twoPi).floor())); //Now theta is in [0, 2pi).
private static NumberInterface getSmallAngle(MutableEvaluationContext context, NumberInterface phi, NumberInterface pi) {
NumberInterface twoPi = pi.multiply(context.getInheritedNumberImplementation().instanceForString("2"));
NumberInterface theta = FUNCTION_ABS.apply(context, phi).subtract(twoPi
.multiply(FUNCTION_ABS.apply(context, phi).divide(twoPi).floor())); //Now theta is in [0, 2pi).
if (phi.signum() < 0) {
theta = twoPi.subtract(theta);
}
return theta;
}
/**
* Returns a number of class numType with value n.
*
* @param numType class of number to return.
* @param n value of returned number.
* @return numClass instance with value n.
*/
private static NumberInterface fromInt(Class<? extends NumberInterface> numType, int n) {
if (!integerValues.containsKey(numType)) {
integerValues.put(numType, new HashMap<>());
}
if (!integerValues.get(numType).containsKey(n)) {
integerValues.get(numType).put(n, new NaiveNumber(n).promoteTo(numType));
}
return integerValues.get(numType).get(n);
}
@Override
public void onEnable() {
registerNumberImplementation("naive", IMPLEMENTATION_NAIVE);
@@ -757,6 +756,9 @@ public class StandardPlugin extends Plugin {
registerOperator("^", OP_CARET);
registerOperator("!", OP_FACTORIAL);
registerTreeValueOperator("=", opSet);
registerTreeValueOperator(":=", opDefine);
registerOperator("nPr", OP_NPR);
registerOperator("nCr", OP_NCR);
@@ -832,6 +834,6 @@ public class StandardPlugin extends Plugin {
@Override
public void onDisable() {
FACTORIAL_LISTS.clear();
}
}

View File

@@ -1,54 +0,0 @@
package org.nwapw.abacus.tree;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.number.NumberInterface;
/**
* A reducer implementation that turns a tree into a single number.
* This is not always guaranteed to work.
*/
public class NumberReducer implements Reducer<NumberInterface> {
/**
* The plugin manager from which to draw the functions.
*/
private Abacus abacus;
/**
* Creates a new number reducer.
*
* @param abacus the calculator instance.
*/
public NumberReducer(Abacus abacus) {
this.abacus = abacus;
}
@Override
public NumberInterface reduceNode(TreeNode node, Object... children) {
if (node instanceof NumberNode) {
return ((NumberNode) node).getNumber();
} else if (node instanceof BinaryNode) {
NumberInterface left = (NumberInterface) children[0];
NumberInterface right = (NumberInterface) children[1];
Function function = abacus.getPluginManager().operatorFor(((BinaryNode) node).getOperation()).getFunction();
if (function == null) return null;
return function.apply(left, right);
} else if (node instanceof UnaryNode) {
NumberInterface child = (NumberInterface) children[0];
Function functionn = abacus.getPluginManager().operatorFor(((UnaryNode) node).getOperation()).getFunction();
if (functionn == null) return null;
return functionn.apply(child);
} else if (node instanceof FunctionNode) {
NumberInterface[] convertedChildren = new NumberInterface[children.length];
for (int i = 0; i < convertedChildren.length; i++) {
convertedChildren[i] = (NumberInterface) children[i];
}
Function function = abacus.getPluginManager().functionFor(((FunctionNode) node).getFunction());
if (function == null) return null;
return function.apply(convertedChildren);
}
return null;
}
}

View File

@@ -7,7 +7,8 @@ package org.nwapw.abacus.tree;
public enum TokenType {
INTERNAL_FUNCTION_END(-1),
ANY(0), WHITESPACE(1), COMMA(2), OP(3), NUM(4), FUNCTION(5), OPEN_PARENTH(6), CLOSE_PARENTH(7);
ANY(0), WHITESPACE(1), COMMA(2), VARIABLE(3), OP(4), TREE_VALUE_OP(4),
NUM(5), FUNCTION(6), TREE_VALUE_FUNCTION(6), OPEN_PARENTH(7), CLOSE_PARENTH(7);
/**
* The priority by which this token gets sorted.

View File

@@ -0,0 +1,115 @@
package org.nwapw.abacus
import org.nwapw.abacus.config.Configuration
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.context.EvaluationContext
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.PluginManager
import org.nwapw.abacus.plugin.StandardPlugin
import org.nwapw.abacus.tree.EvaluationResult
import org.nwapw.abacus.tree.NumberReducer
import org.nwapw.abacus.tree.TreeNode
/**
* 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 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 hidden, mutable implementation of the context.
*/
private val mutableContext = MutableEvaluationContext(numberImplementation = StandardPlugin.IMPLEMENTATION_NAIVE)
/**
* The base context from which calculations are started.
*/
val context: EvaluationContext
get() = mutableContext
init {
pluginManager.addListener(tokenizer)
pluginManager.addListener(parser)
pluginManager.addListener(promotionManager)
}
/**
* Reloads the Abacus core.
*/
fun reload(){
pluginManager.reload()
with(mutableContext) {
numberImplementation = pluginManager.numberImplementationFor(configuration.numberImplementation)
?: StandardPlugin.IMPLEMENTATION_NAIVE
clearVariables()
clearDefinitions()
}
}
/**
* Merges the current context with the provided one, updating
* variables and the like.
* @param context the context to apply.
*/
fun applyToContext(context: EvaluationContext){
mutableContext.apply(context)
}
/**
* 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.
*
* @param tree the tree to reduce, must not be null.
* @return the evaluation result.
*/
fun evaluateTree(tree: TreeNode): EvaluationResult {
return evaluateTreeWithContext(tree, context.mutableSubInstance())
}
/**
* Evaluates the given tree using a different context than
* the default one.
*
* @param tree the tree to reduce, must not be null.
* @param context the context to use for the evaluation.
* @return the evaluation result.
*/
fun evaluateTreeWithContext(tree: TreeNode, context: MutableEvaluationContext): EvaluationResult {
val newReducer = NumberReducer(this, context)
val evaluationValue = tree.reduce(newReducer)
return EvaluationResult(evaluationValue, newReducer.context)
}
}

View File

@@ -0,0 +1,20 @@
package org.nwapw.abacus.config
/**
* A class that holds information that tells Abacus how to behave.
*
* Configuration stores information about how Abacus should behave, for
* instance, what number implementation it should use and what
* plugins should be ignored during loading.
*
* @property numberImplementation the number implementation Abacus should use for loading.
* @param disabledPlugins the plugins that should be disabled and not loaded by the plugin manager.
*/
open class Configuration(var numberImplementation: String = "<default>", disabledPlugins: Array<String> = emptyArray()) {
/**
* The set of disabled plugins that should be ignored by the plugin manager.
*/
val disabledPlugins = disabledPlugins.toMutableSet()
}

View File

@@ -0,0 +1,26 @@
package org.nwapw.abacus.context
import kotlin.reflect.KProperty
/**
* A delegate to accumulate a collection of elements in a [EvaluationContext] hierarchy.
*
* ChainAccumulateDelegate is similar to the [ChainSearchDelegate], however, it operates only on collections.
* Instead of returning the most recent collection, it merges them into a [Set].
*
* @param T the type of element in the collection.
* @property valueGetter the getter used to access the collection from the context.
*/
class ChainAccumulateDelegate<out T>(private val valueGetter: EvaluationContext.() -> Collection<T>) {
operator fun getValue(selfRef: Any, property: KProperty<*>): Set<T> {
val set = mutableSetOf<T>()
var currentRef: EvaluationContext = selfRef as? EvaluationContext ?: return set
while(true) {
set.addAll(currentRef.valueGetter())
currentRef = currentRef.parent ?: break
}
return set
}
}

View File

@@ -0,0 +1,29 @@
package org.nwapw.abacus.context
import kotlin.reflect.KProperty
/**
* A delegate to search a hierarchy made up of [EvaluationContext].
*
* ChainSearchDelegate is a variable delegate written specifically for use in [EvaluationContext], because
* of its hierarchical structure. Variables not found in the current context are searched
* for in its parent, which continues recursively until the context being examined has no parent.
* This class assists that logic, which is commonly re-used with different variable types, by calling
* [valueGetter] on the current context, then its parent, etc.
*
* @param V the type of the property to search recursively.
* @property valueGetter the getter lambda to access the value from the context.
*/
class ChainSearchDelegate<out V>(private val valueGetter: EvaluationContext.() -> V?) {
operator fun getValue(selfRef: Any, property: KProperty<*>): V? {
var currentRef = selfRef as? EvaluationContext ?: return null
var returnedValue = currentRef.valueGetter()
while (returnedValue == null) {
currentRef = currentRef.parent ?: break
returnedValue = currentRef.valueGetter()
}
return returnedValue
}
}

View File

@@ -0,0 +1,84 @@
package org.nwapw.abacus.context
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.plugin.NumberImplementation
import org.nwapw.abacus.tree.Reducer
import org.nwapw.abacus.tree.TreeNode
/**
* A context for the reduction of a [org.nwapw.abacus.tree.TreeNode] into a number.
*
* The reduction context is used to carry important state information captured at the beginning
* of the reduction of an expression, such as the variables and the implementation in use.
*
* @property parent the parent of this context.
* @property numberImplementation the implementation for numbers of this context.
* @property reducer the reducer used by this context.
*/
open class EvaluationContext(val parent: EvaluationContext? = null,
open val numberImplementation: NumberImplementation? = null,
open val reducer: Reducer<NumberInterface>? = null) {
/**
* The map of variables in this context.
*/
protected val variableMap = mutableMapOf<String, NumberInterface>()
/**
* The map of definitions in this context.
*/
protected val definitionMap = mutableMapOf<String, TreeNode>()
/**
* The set of all variable names defined in this context.
*/
val variables: Set<String>
get() = variableMap.keys
/**
* The set of all definition names defined in this context.
*/
val definitions: Set<String>
get() = definitionMap.keys
/**
* The implementation inherited from this context's parent.
*/
val inheritedNumberImplementation: NumberImplementation?
by ChainSearchDelegate { numberImplementation}
/**
* The reducer inherited from this context's parent.
*/
val inheritedReducer: Reducer<NumberInterface>?
by ChainSearchDelegate { reducer }
/**
* The set of all variables in this context and its parents.
*/
val inheritedVariables: Set<String> by ChainAccumulateDelegate { variables }
/**
* The set of all definition in this context and its parents.
*/
val inheritedDefinitions: Set<String> by ChainAccumulateDelegate { definitions }
/**
* Create a new child instance of this context that is mutable.
* @return the new child instance.
*/
fun mutableSubInstance(): MutableEvaluationContext = MutableEvaluationContext(this)
/**
* Gets a variable stored in this context.
*/
fun getVariable(name: String): NumberInterface? {
return variableMap[name] ?: parent?.getVariable(name)
}
/**
* Gets the definition stored in this context.
*/
fun getDefinition(name: String): TreeNode? {
return definitionMap[name] ?: parent?.getDefinition(name)
}
}

View File

@@ -0,0 +1,69 @@
package org.nwapw.abacus.context
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.plugin.NumberImplementation
import org.nwapw.abacus.tree.Reducer
import org.nwapw.abacus.tree.TreeNode
/**
* A reduction context that is mutable.
* @param parent the parent of this context.
* @param numberImplementation the number implementation used in this context.
* @param reducer the reducer used in this context
*/
class MutableEvaluationContext(parent: EvaluationContext? = null,
numberImplementation: NumberImplementation? = null,
reducer: Reducer<NumberInterface>? = null) :
EvaluationContext(parent, numberImplementation, reducer) {
override var numberImplementation: NumberImplementation? = super.numberImplementation
override var reducer: Reducer<NumberInterface>? = super.reducer
/**
* Writes data stored in the [other] context over data stored in this one.
* @param other the context from which to copy data.
*/
fun apply(other: EvaluationContext) {
if(other.numberImplementation != null) numberImplementation = other.numberImplementation
if(other.reducer != null) reducer = other.reducer
for(name in other.variables) {
setVariable(name, other.getVariable(name) ?: continue)
}
for(name in other.definitions) {
setDefinition(name, other.getDefinition(name) ?: continue)
}
}
/**
* Sets a variable to a certain [value].
* @param name the name of the variable.
* @param value the value of the variable.
*/
fun setVariable(name: String, value: NumberInterface) {
variableMap[name] = value
}
/**
* Set a definition to a certain [value].
* @param name the name of the definition.
* @param value the value of the definition.
*/
fun setDefinition(name: String, value: TreeNode) {
definitionMap[name] = value
}
/**
* Clears the variables defined in this context.
*/
fun clearVariables(){
variableMap.clear()
}
/**
* Clears the definitions defined in this context.
*/
fun clearDefinitions(){
definitionMap.clear()
}
}

View File

@@ -0,0 +1,12 @@
package org.nwapw.abacus.function
import org.nwapw.abacus.function.applicable.Applicable
import org.nwapw.abacus.number.NumberInterface
/**
* A function that operates on numbers.
*
* This function takes some number of input NumberInterfaces and returns
* another NumberInterface as a result.
*/
abstract class NumberFunction : Applicable<NumberInterface, NumberInterface>

View File

@@ -0,0 +1,17 @@
package org.nwapw.abacus.function
import org.nwapw.abacus.function.applicable.Applicable
import org.nwapw.abacus.number.NumberInterface
/**
* An operator that operates on NumberImplementations.
*
* This is simply an alias for Operator<NumberInterface, NumberInterface>.
* @param associativity the associativity of the operator.
* @param type the type of the operator (binary, unary, etc)
* @param precedence the precedence of the operator.
*/
abstract class NumberOperator(associativity: OperatorAssociativity, type: OperatorType,
precedence: Int) :
Operator(associativity, type, precedence),
Applicable<NumberInterface, NumberInterface>

View File

@@ -3,12 +3,11 @@ package org.nwapw.abacus.function
/**
* A single operator that can be used by Abacus.
*
* This is a data class that holds the information about a single operator, such as a plus or minus.
* This is a class that holds the information about a single operator, such as a plus or minus.
*
* @param associativity the associativity of this operator, used for order of operations;.
* @param type the type of this operator, used for parsing (infix / prefix / postfix and binary / unary)
* @param precedence the precedence of this operator, used for order of operations.
* @param function the function this operator applies to its arguments.
*/
data class Operator(val associativity: OperatorAssociativity, val type: OperatorType,
val precedence: Int, val function: Function)
open class Operator(val associativity: OperatorAssociativity, val type: OperatorType,
val precedence: Int)

View File

@@ -0,0 +1,13 @@
package org.nwapw.abacus.function
import org.nwapw.abacus.function.applicable.Applicable
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.tree.TreeNode
/**
* A function that operates on trees.
*
* A function that operates on parse tree nodes instead of on already simplified numbers.
* Despite this, it returns a number, not a tree.
*/
abstract class TreeValueFunction : Applicable<TreeNode, NumberInterface>

View File

@@ -0,0 +1,18 @@
package org.nwapw.abacus.function
import org.nwapw.abacus.function.applicable.Applicable
import org.nwapw.abacus.number.NumberInterface
import org.nwapw.abacus.tree.TreeNode
/**
* An operator that operates on trees.
*
* This operator operates on parse trees, returning, however a number.
* @param associativity the associativity of the operator.
* @param type the type of the operator (infix, postfix, etc)
* @param precedence the precedence of the operator.
*/
abstract class TreeValueOperator(associativity: OperatorAssociativity, type: OperatorType,
precedence: Int) :
Operator(associativity, type, precedence),
Applicable<TreeNode, NumberInterface>

View File

@@ -0,0 +1,44 @@
package org.nwapw.abacus.function.applicable
import org.nwapw.abacus.context.MutableEvaluationContext
import org.nwapw.abacus.exception.DomainException
/**
* A class that can be applied to arguments.
*
* Applicable is a class that represents something that can be applied to one or more
* arguments of the same type, and returns a single value from that application.
* @param <T> the type of the parameters passed to this applicable.
* @param <O> the return type of the applicable.
*/
interface Applicable<in T : Any, out O : Any> {
/**
* Checks if the given applicable can be used with the given parameters.
* @param params the parameter array to verify for compatibility.
* @return whether the array can be used with applyInternal.
*/
fun matchesParams(context: MutableEvaluationContext, params: Array<out T>): Boolean
/**
* Applies the applicable object to the given parameters,
* without checking for compatibility.
* @param params the parameters to apply to.
* @return the result of the application.
*/
fun applyInternal(context: MutableEvaluationContext, params: Array<out T>): O
/**
* If the parameters can be used with this applicable, returns
* the result of the application of the applicable to the parameters.
* Otherwise, returns null.
* @param params the parameters to apply to.
* @return the result of the operation, or null if parameters do not match.
*/
fun apply(context: MutableEvaluationContext, vararg params: T): O {
if (!matchesParams(context, params))
throw DomainException("parameters do not match function requirements.")
return applyInternal(context, params)
}
}

View File

@@ -0,0 +1,16 @@
@file:JvmName("NumberUtils")
package org.nwapw.abacus.number
typealias PromotionFunction = java.util.function.Function<NumberInterface, NumberInterface>
typealias PromotionPath = List<PromotionFunction>
typealias NumberClass = Class<NumberInterface>
/**
* Promote a number through this path. The functions in this path
* are applied in order to the number, and the final result is returned.
*
* @param from the number to start from.
*/
fun PromotionPath.promote(from: NumberInterface): NumberInterface {
return fold(from, { current, function -> function.apply(current) })
}

View File

@@ -0,0 +1,78 @@
package org.nwapw.abacus.number
import org.nwapw.abacus.Abacus
import org.nwapw.abacus.plugin.NumberImplementation
import org.nwapw.abacus.plugin.PluginListener
import org.nwapw.abacus.plugin.PluginManager
import java.util.function.Function
/**
* A class that handles promotions based on priority and the
* transition paths each implementation provides.
*
* @property abacus the Abacus instance to use to access other components.
*/
class PromotionManager(val abacus: Abacus) : PluginListener {
/**
* The already computed paths
*/
val computePaths = mutableMapOf<Pair<NumberImplementation, NumberImplementation>, PromotionPath?>()
/**
* Computes a path between a starting and an ending implementation.
*
* @param from the implementation to start from.
* @param to the implementation to get to.
* @return the resulting promotion path, or null if it is not found
*/
fun computePathBetween(from: NumberImplementation, to: NumberImplementation): PromotionPath? {
val fromName = abacus.pluginManager.interfaceImplementationNameFor(from.implementation)
val toName = abacus.pluginManager.interfaceImplementationNameFor(to.implementation)
if(fromName == toName) return listOf(Function { it })
if(from.promotionPaths.containsKey(toName))
return listOf(from.promotionPaths[toName] ?: return null)
return null
}
/**
* Promote all the numbers in the list to the same number implementation, to ensure
* they can be used with each other. Finds the highest priority implementation
* in the list, and promotes all other numbers to it.
*
* @param numbers the numbers to promote.
* @return the resulting promotion result.
*/
fun promote(vararg numbers: NumberInterface): PromotionResult? {
val pluginManager = abacus.pluginManager
val implementations = numbers.map { pluginManager.interfaceImplementationFor(it.javaClass) }
val highestPriority = implementations.sortedBy { it.priority }.last()
return PromotionResult(items = numbers.map {
if(it.javaClass == highestPriority.implementation) it
else computePaths[pluginManager.interfaceImplementationFor(it.javaClass) to highestPriority]
?.promote(it) ?: return null
}.toTypedArray(), promotedTo = highestPriority)
}
override fun onLoad(manager: PluginManager) {
val implementations = manager.allNumberImplementations.map { manager.numberImplementationFor(it) }
for((index, value) in implementations.withIndex()){
for(i in index until implementations.size){
val other = implementations[i]
val promoteFrom = if(other.priority > value.priority) value else other
val promoteTo = if(other.priority > value.priority) other else value
val path = computePathBetween(promoteFrom, promoteTo)
computePaths.put(promoteFrom to promoteTo, path)
}
}
}
override fun onUnload(manager: PluginManager) {
computePaths.clear()
}
}

View File

@@ -0,0 +1,11 @@
package org.nwapw.abacus.number
import org.nwapw.abacus.plugin.NumberImplementation
/**
* The result of promoting an array of NumberInterfaces.
*
* @param promotedTo the implementation to which the numbers were promoted.
* @param items the items the items resulting from the promotion.
*/
data class PromotionResult(val promotedTo: NumberImplementation, val items: Array<NumberInterface>)

View File

@@ -11,16 +11,10 @@ package org.nwapw.abacus.tree
* @param left the left node.
* @param right the right node.
*/
data class BinaryNode(val operation: String, val left: TreeNode? = null, val right: TreeNode?) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
val leftReduce = left?.reduce(reducer) ?: return null
val rightReduce = right?.reduce(reducer) ?: return null
return reducer.reduceNode(this, leftReduce, rightReduce)
}
abstract class BinaryNode(val operation: String, val left: TreeNode, val right: TreeNode) : TreeNode() {
override fun toString(): String {
return "(" + (left?.toString() ?: "null") + operation + (right?.toString() ?: "null") + ")"
return "(" + left.toString() + operation + right.toString() + ")"
}
}

View File

@@ -0,0 +1,25 @@
package org.nwapw.abacus.tree
/**
* Represents a more generic function call.
*
* This class does not specify how it should be reduced, allowing other classes
* to extend this functionality.
*
* @param callTo the name of the things being called.
* @param children the children of this node.
*/
abstract class CallNode(val callTo: String, val children: List<TreeNode>) : TreeNode() {
override fun toString(): String {
val buffer = StringBuffer()
buffer.append(callTo)
buffer.append("(")
for (i in 0 until children.size) {
buffer.append(children[i].toString())
buffer.append(if (i != children.size - 1) ", " else ")")
}
return buffer.toString()
}
}

View File

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

View File

@@ -8,45 +8,11 @@ package org.nwapw.abacus.tree
*
* @param function the function string.
*/
data class FunctionNode(val function: String) : TreeNode() {
class FunctionNode(function: String, children: List<TreeNode>) : CallNode(function, children) {
/**
* List of function parameters added to this node.
*/
val children: MutableList<TreeNode> = mutableListOf()
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
val children = Array<Any>(children.size, { children[it].reduce(reducer) ?: return null; })
override fun <T : Any> reduce(reducer: Reducer<T>): T {
val children = Array<Any>(children.size, { children[it].reduce(reducer) })
return reducer.reduceNode(this, *children)
}
override fun toString(): String {
val buffer = StringBuffer()
buffer.append(function)
buffer.append('(')
for (i in 0 until children.size) {
buffer.append(children[i].toString())
buffer.append(if (i == children.size - 1) ")" else ",")
}
return buffer.toString()
}
/**
* Appends a child to this node's list of children.
*
* @node the node to append.
*/
fun appendChild(node: TreeNode) {
children.add(node)
}
/**
* Prepends a child to this node's list of children.
*
* @node the node to prepend.
*/
fun prependChild(node: TreeNode) {
children.add(0, node)
}
}

View File

@@ -0,0 +1,22 @@
package org.nwapw.abacus.tree
/**
* A binary operator node that reduces its children.
*
* NumberBinaryNode operates by simply reducing its children and
* then using the result of that reduction to reduce itself.
*
* @param operation the operation this node performs.
* @param left the left child of this node.
* @param right the right child of this node.
*/
class NumberBinaryNode(operation: String, left: TreeNode, right: TreeNode)
: BinaryNode(operation, left, right) {
override fun <T : Any> reduce(reducer: Reducer<T>): T {
val left = left.reduce(reducer)
val right = right.reduce(reducer)
return reducer.reduceNode(this, left, right)
}
}

View File

@@ -1,7 +1,5 @@
package org.nwapw.abacus.tree
import org.nwapw.abacus.number.NumberInterface
/**
* A tree node that holds a single number value.
*
@@ -10,14 +8,14 @@ import org.nwapw.abacus.number.NumberInterface
*
* @number the number value of this node.
*/
data class NumberNode(val number: NumberInterface) : TreeNode() {
class NumberNode(val number: String) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
override fun <T : Any> reduce(reducer: Reducer<T>): T {
return reducer.reduceNode(this)
}
override fun toString(): String {
return number.toString()
return number
}
}

View File

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

View File

@@ -0,0 +1,19 @@
package org.nwapw.abacus.tree
/**
* A unary operator node that reduces its children.
*
* NumberUnaryNode operates by simply reducing its child,
* and using the result of that reduction to reduce itself.
* @param operation the operation this node performs.
* @param child the child this node should be applied to.
*/
class NumberUnaryNode(operation: String, child: TreeNode)
: UnaryNode(operation, child) {
override fun <T : Any> reduce(reducer: Reducer<T>): T {
val child = applyTo.reduce(reducer)
return reducer.reduceNode(this, child)
}
}

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
package org.nwapw.abacus.tree
/**
* A tree node that represents a binary tree value operator.
*
*
* The tree value operators operate on trees, and so this
* node does not reduce its children. It is up to the implementation to handle
* reduction.
* @param operation the operation this node performs.
* @param left the left child of this node.
* @param right the right child of this node.
*/
class TreeValueBinaryNode(operation: String, left: TreeNode, right: TreeNode)
: BinaryNode(operation, left, right) {
override fun <T : Any> reduce(reducer: Reducer<T>): T {
return reducer.reduceNode(this)
}
}

View File

@@ -0,0 +1,16 @@
package org.nwapw.abacus.tree
/**
* A tree node that represents a tree value function call.
*
* This is in many ways similar to a simple FunctionNode, and the distinction
* is mostly to help the reducer. Besides that, this class also does not
* even attempt to reduce its children.
*/
class TreeValueFunctionNode(name: String, children: List<TreeNode>) : CallNode(name, children) {
override fun <T : Any> reduce(reducer: Reducer<T>): T {
return reducer.reduceNode(this)
}
}

View File

@@ -0,0 +1,19 @@
package org.nwapw.abacus.tree
/**
* A tree node that represents a unary tree value operator.
*
* The tree value operators operate on trees, and so this
* node does not reduce its children. It is up to the implementation to handle
* reduction.
* @param operation the operation this node performs.
* @param child the node the operation should be applied to.
*/
class TreeValueUnaryNode(operation: String, child: TreeNode)
: UnaryNode(operation, child) {
override fun <T : Any> reduce(reducer: Reducer<T>): T {
return reducer.reduceNode(this)
}
}

View File

@@ -9,15 +9,10 @@ package org.nwapw.abacus.tree
* @param operation the operation applied to the given node.
* @param applyTo the node to which the operation will be applied.
*/
data class UnaryNode(val operation: String, val applyTo: TreeNode? = null) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
val reducedChild = applyTo?.reduce(reducer) ?: return null
return reducer.reduceNode(this, reducedChild)
}
abstract class UnaryNode(val operation: String, val applyTo: TreeNode) : TreeNode() {
override fun toString(): String {
return "(" + (applyTo?.toString() ?: "null") + ")" + operation
return "(" + applyTo.toString() + ")" + operation
}
}

View File

@@ -0,0 +1,21 @@
package org.nwapw.abacus.tree
/**
* A tree node that holds a placeholder variable.
*
* This node holds a variable string, and acts similarly to a number,
* with the key difference of not actually holding a value at runtime.
*
* @param variable the actual variable name that this node represents.
*/
class VariableNode(val variable: String) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T {
return reducer.reduceNode(this)
}
override fun toString(): String {
return variable
}
}

View File

@@ -5,34 +5,38 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.exception.DomainException;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.plugin.StandardPlugin;
import org.nwapw.abacus.tree.TreeNode;
public class CalculationTests {
private static Abacus abacus = new Abacus(new Configuration(0, "precise", new String[]{}));
private static Abacus abacus = new Abacus(new Configuration( "precise", new String[]{}));
@BeforeClass
public static void prepareTests() {
abacus.getPluginManager().addInstantiated(new StandardPlugin(abacus.getPluginManager()));
abacus.getPluginManager().load();
abacus.reload();
}
private void testOutput(String input, String parseOutput, String output) {
TreeNode parsedTree = abacus.parseString(input);
Assert.assertNotNull(parsedTree);
Assert.assertEquals(parsedTree.toString(), parseOutput);
NumberInterface result = abacus.evaluateTree(parsedTree);
NumberInterface result = abacus.evaluateTree(parsedTree).getValue();
Assert.assertNotNull(result);
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)`^0.9999)");
testDomainException("0^0", "(0^0)");
testDomainException("(-13)^.9999", "((13)`^.9999)");
}
}

View File

@@ -5,10 +5,8 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.function.OperatorAssociativity;
import org.nwapw.abacus.function.OperatorType;
import org.nwapw.abacus.context.MutableEvaluationContext;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.lexing.pattern.Match;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.parsing.LexerTokenizer;
@@ -19,26 +17,48 @@ import java.util.List;
public class TokenizerTests {
private static Abacus abacus = new Abacus(new Configuration(0, "precise", new String[]{}));
private static Abacus abacus = new Abacus(new Configuration("precise", new String[]{}));
private static LexerTokenizer lexerTokenizer = new LexerTokenizer();
private static Function subtractFunction = new Function() {
private static NumberFunction subtractFunction = new NumberFunction() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return params.length == 2;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return params[0].subtract(params[1]);
}
};
private static Plugin testPlugin = new Plugin(abacus.getPluginManager()) {
@Override
public void onEnable() {
registerOperator("+", new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX,
0, subtractFunction));
registerOperator("-", new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX,
0, subtractFunction));
registerOperator("+", new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX,
0) {
@Override
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return true;
}
@Override
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return subtractFunction.apply(context, params);
}
});
registerOperator("-", new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX,
0) {
@Override
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
return true;
}
@Override
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
return subtractFunction.apply(context, params);
}
});
registerFunction("subtract", subtractFunction);
}
@@ -60,7 +80,7 @@ public class TokenizerTests {
public static void prepareTests() {
abacus.getPluginManager().addListener(lexerTokenizer);
abacus.getPluginManager().addInstantiated(testPlugin);
abacus.getPluginManager().load();
abacus.reload();
}
@Test

View File

@@ -3,7 +3,8 @@
{% include head.html %}
<style>
body {
margin-top: 100px;
margin: 0px;
margin-top: 50px;
color: white;
text-align: center;
}
@@ -11,7 +12,13 @@
font-family: "Source Code Pro"
}
img {
img#logo {
margin: auto;
width: 100%;
max-width: 100px;
}
img#image_preview {
margin: auto;
width: 100%;
max-width: 432px;
@@ -31,8 +38,61 @@
background-color: #06e8a4;
color: white;
}
div.fullwidth {
width: 100%;
height: auto;
overflow: hidden;
}
div.fullwidth img {
max-width: 100%;
max-height: 450px;
margin: auto;
margin-top: 20px;
margin-bottom: 20px;
display: block;
border-radius: 5px;
}
div.white {
background-color: white;
color: black;
}
div.green {
background-color: #06e8a4;
color: white;
}
div.fullwidth div.double {
height: 100%;
text-align: left;
width: 50%;
box-sizing: border-box;
padding: 40px;
float: left;
background-color: inherit;
}
@media (max-width: 750px) {
div.fullwidth div.double {
width: 100%;
padding: 15px;
}
div.fullwidth img {
margin-top: 0px;
margin-bottom: 0px;
}
}
div.fullwidth div.double h1, h2, h3, h4, h5, h6 {
text-align: center;
}
</style>
<body>
<img src="https://raw.githubusercontent.com/DanilaFe/abacus/master/image/logo.png" id="logo">
<h1>Abacus</h1>
<h2>The programmer's calculator</h2>
<div id="buttons">
@@ -41,6 +101,59 @@
<a class="button inverted" href="https://github.com/DanilaFe/abacus">Contribute</a>
<a class="button inverted" href="https://github.com/DanilaFe/abacus/wiki">Wiki</a>
</div>
<img src="http://i.imgur.com/Min70QY.png" title="source: imgur.com" />
<img src="http://i.imgur.com/Min70QY.png" title="source: imgur.com" id="image_preview"/>
<h2>Features</h2>
<div class="fullwidth white">
<div class="double">
<img src="https://i.imgur.com/gmGJBBK.png">
</div>
<div class="double">
<h2>Precision</h2>
Abacus uses a mathematical tool called Taylor Series to determine values
as accurate as the user desires. Of course, this comes with some
performance issues with larger numbers. However, Abacus has been
tested to generate the value of e correctly to a thousand digits.
</div>
</div>
<div class="fullwidth green">
<div class="double">
<h2>Configurable and Customizable</h2>
The very first idea for Abacus was inspired by how difficult it was
to program a TI-84 calculator. Only two languages were available, TI-BASIC
and Assembly, the latter having virtually no documentation. Determined
to be better than a TI-84, Abacus implemented a plugin system that allows
users to easily create and add plugins written in the same programming
language as Abacus itself - Java. These plugins can access the full
power of the language, and implement their own ways of handling numbers,
as well as their own functions and even operators.<br><br>
Besides the ability to add plugins, Abacus also adds some general
options that can be used to make the user's experience more pleasant.
For instance, it allows for a computation limit to be set in order
to prevent excessively long evaluation: 8!!! is, for example, an expression
that even Wolfram Alpha doesn't compute accurately, and will never finish
on Abacus (it's simply too large). The computation limit will allow Abacus
to kill a computation if it takes too long. Support for user-definable
precision is also planned.
</div>
<div class="double">
<img src="https://i.imgur.com/JzenWPV.png">
</div>
</div>
<div class="fullwidth white">
<div class="double">
<img src="https://i.imgur.com/jY17I3A.png">
</div>
<div class="double">
<h2>Built-in Documentation</h2>
Abacus plugins are given a mechanism to register documentation for
the functions that they provide. The Abacus GUI displays these
functions in a searchable list, allowing the user to read the parameters
that have to be supplied to each function, as well as learn about
its return value.<br><br>
The search finds functions not only by their names, but also by relevant
terms mentioned in the function's description, thus allowing related
functions to be displayed together.
</div>
</div>
</body>
</html>

View File

@@ -7,7 +7,7 @@ $code-color: #efefef;
$accent-color: #00AFE8;
$clear-color: white;
$title-font: "Open Sans";
$text-font: Helvetica;
$text-font: "Raleway";
$code-font: "Source Code Pro";
$max-width: 850px;

View File

@@ -1,9 +1,7 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.1.3'
}
apply plugin: 'application'
dependencies {
compile 'com.moandjiezana.toml:toml4j:0.7.1'
compile project(':core')
}

View File

@@ -12,14 +12,18 @@ import javafx.util.Callback;
import javafx.util.StringConverter;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.exception.AbacusException;
import org.nwapw.abacus.exception.ComputationInterruptedException;
import org.nwapw.abacus.function.Documentation;
import org.nwapw.abacus.function.DocumentationType;
import org.nwapw.abacus.number.ComputationInterruptedException;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.exception.DomainException;
import org.nwapw.abacus.exception.EvaluationException;
import org.nwapw.abacus.number.*;
import org.nwapw.abacus.plugin.ClassFinder;
import org.nwapw.abacus.plugin.PluginListener;
import org.nwapw.abacus.plugin.PluginManager;
import org.nwapw.abacus.plugin.StandardPlugin;
import org.nwapw.abacus.tree.EvaluationResult;
import org.nwapw.abacus.tree.TreeNode;
import java.io.File;
@@ -144,15 +148,14 @@ public class AbacusController implements PluginListener {
if (constructedTree == null) {
return ERR_SYNTAX;
}
NumberInterface evaluatedNumber = abacus.evaluateTree(constructedTree);
if (evaluatedNumber == null) {
return ERR_EVAL;
}
EvaluationResult result = abacus.evaluateTree(constructedTree);
NumberInterface evaluatedNumber = result.getValue();
String resultingString = evaluatedNumber.toString();
historyData.add(new HistoryModel(inputField.getText(), constructedTree.toString(), resultingString));
abacus.applyToContext(result.getResultingContext());
return resultingString;
} catch (ComputationInterruptedException exception) {
return ERR_STOP;
} catch (AbacusException exception) {
return exception.getMessage();
} catch (RuntimeException exception) {
exception.printStackTrace();
return ERR_EXCEPTION;
@@ -195,7 +198,7 @@ public class AbacusController implements PluginListener {
*/
private final Runnable TIMER_RUNNABLE = () -> {
try {
Configuration abacusConfig = abacus.getConfiguration();
ExtendedConfiguration abacusConfig = (ExtendedConfiguration) abacus.getConfiguration();
if (abacusConfig.getComputationDelay() == 0) return;
Thread.sleep((long) (abacusConfig.getComputationDelay() * 1000));
performStop();
@@ -255,12 +258,12 @@ public class AbacusController implements PluginListener {
if (oldValue.equals(settingsTab)) alertIfApplyNeeded(true);
});
abacus = new Abacus(new Configuration(CONFIG_FILE));
abacus = new Abacus(new ExtendedConfiguration(CONFIG_FILE));
PluginManager abacusPluginManager = abacus.getPluginManager();
abacusPluginManager.addListener(this);
performScan();
computationLimitField.setText(Double.toString(abacus.getConfiguration().getComputationDelay()));
computationLimitField.setText(Double.toString(((ExtendedConfiguration) abacus.getConfiguration()).getComputationDelay()));
computationLimitField.textProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue.matches("(\\d+(\\.\\d*)?)?")) {
computationLimitField.setText(oldValue);
@@ -318,13 +321,13 @@ public class AbacusController implements PluginListener {
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
abacusPluginManager.reload();
abacus.reload();
}
@FXML
public void performReload() {
alertIfApplyNeeded(true);
abacus.getPluginManager().reload();
abacus.reload();
}
@FXML
@@ -337,8 +340,8 @@ public class AbacusController implements PluginListener {
if (!pluginEntry.isEnabled()) disabledPlugins.add(pluginEntry.getClassName());
}
if (computationLimitField.getText().matches("\\d*(\\.\\d+)?") && computationLimitField.getText().length() != 0)
configuration.setComputationDelay(Double.parseDouble(computationLimitField.getText()));
configuration.saveTo(CONFIG_FILE);
((ExtendedConfiguration) configuration).setComputationDelay(Double.parseDouble(computationLimitField.getText()));
((ExtendedConfiguration) configuration).saveTo(CONFIG_FILE);
changesMade = false;
reloadAlertShown = false;
}
@@ -358,7 +361,14 @@ public class AbacusController implements PluginListener {
enabledPlugins.add(plugin);
}
PluginManager pluginManager = abacus.getPluginManager();
functionList.addAll(manager.getAllFunctions().stream().map(name -> pluginManager.documentationFor(name, DocumentationType.FUNCTION))
functionList.addAll(manager.getAllFunctions().stream().map(name -> {
Documentation documentationInstance = pluginManager.documentationFor(name, DocumentationType.FUNCTION);
if(documentationInstance == null)
documentationInstance = new Documentation(name, "", "", "", DocumentationType.FUNCTION);
return documentationInstance;
})
.collect(Collectors.toCollection(ArrayList::new)));
functionList.addAll(manager.getAllTreeValueFunctions().stream().map(name -> pluginManager.documentationFor(name, DocumentationType.TREE_VALUE_FUNCTION))
.collect(Collectors.toCollection(ArrayList::new)));
functionList.sort(Comparator.comparing(Documentation::getCodeName));
}

View File

@@ -0,0 +1,72 @@
package org.nwapw.abacus.fx
import com.moandjiezana.toml.Toml
import com.moandjiezana.toml.TomlWriter
import org.nwapw.abacus.config.Configuration
import java.io.File
/**
* Additional settings for user interface.
*
* ExtendedConfiguration is used to add other settings
* that aren't built into Abacus core, but are necessary
* for the fx module.
*
* @property computationDelay the delay before which the computation stops.
* @param implementation the number implementation, same as [Configuration.numberImplementation]
* @param disabledPlugins the list of plugins that should be disabled, same as [Configuration.disabledPlugins]
*/
class ExtendedConfiguration(var computationDelay: Double = 0.0,
implementation: String = "<default>",
disabledPlugins: Array<String> = emptyArray())
: Configuration(implementation, disabledPlugins) {
companion object {
/**
* The default TOML.
*/
val DEFAULT_TOML_STRING = """
computationDelay=0.0
implementation="naive"
disabledPlugins=[]
"""
/**
* A reader with the default TOML data.
*/
val DEFAULT_TOML_READER = Toml().read(DEFAULT_TOML_STRING)
/**
* A writer used to writing the configuration to disk.
*/
val DEFAULT_TOML_WRITER = TomlWriter()
}
/**
* Constructs a new configuration from a file on disk.
* @param tomlFile the file from disk to load.
*/
constructor(tomlFile: File) : this() {
val toml = Toml(DEFAULT_TOML_READER)
if(tomlFile.exists()) toml.read(tomlFile)
copyFrom(toml.to(ExtendedConfiguration::class.java))
}
/**
* Copies data from another configuration into this one.
* @param config the configuration to copy from.
*/
fun copyFrom(config: ExtendedConfiguration) {
computationDelay = config.computationDelay
numberImplementation = config.numberImplementation
disabledPlugins.clear()
disabledPlugins.addAll(config.disabledPlugins)
}
/**
* Saves this configuration to a file.
* @param file the file to save to.
*/
fun saveTo(file: File) {
DEFAULT_TOML_WRITER.write(this, file)
}
}

View File

@@ -54,7 +54,7 @@
<TextField fx:id="computationLimitField" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<FlowPane GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="3" hgap="10"
vgap="10">
<Button text="Apply" onAction="#performSave"/>
<Button text="Save" onAction="#performSave"/>
<Button text="Reload Plugins" onAction="#performReload"/>
<Button text="Apply and Reload" onAction="#performSaveAndReload"/>
<Button text="Scan Plugins" onAction="#performScan"/>

BIN
image/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB