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

Compare commits

...

110 Commits

Author SHA1 Message Date
205b73f62c Add missing comments. 2017-08-07 11:11:41 -07:00
1cd332b97d Remove output. 2017-08-07 10:58:07 -07:00
314552f95a Read delay input from input field, and kill delay thread. 2017-08-07 10:58:04 -07:00
c5ec521996 Fix tests to work with the configuration. 2017-08-07 10:57:51 -07:00
4712bbfded Add a setting to the timeout delay. 2017-08-07 10:57:48 -07:00
7ae7f6d9a5 Add a timer to the computation thread to stop it from running. 2017-08-07 10:57:04 -07:00
arthur326
6a15c266c4 Fix typo in cot function. 2017-08-06 18:13:01 -07:00
9f61fc5dbe Remove the correct unused intPow function. 2017-08-05 18:23:24 -07:00
bae6ee5526 Revert "Remove the NumberInterface::intPow method."
0c16bb4e9b
2017-08-05 18:22:43 -07:00
4f94700aef Remove the NumberInterface::intPow method. 2017-08-05 18:11:16 -07:00
b7152da58d Merge branch 'stoppable-alternate' 2017-08-05 17:58:26 -07:00
d17a8a9fa7 Add missing javadoc. 2017-08-05 17:29:35 -07:00
71c9f0d141 Merge branch 'documentation' 2017-08-05 17:28:08 -07:00
fb02984e60 Decrease test intensity to prevent travis CI from killing gradle. 2017-08-05 17:24:16 -07:00
a9ac4681f0 Use a specific configuration instead of chancing it with a local file. 2017-08-05 17:13:03 -07:00
62d7053441 Get rid of unnecessary supplier. 2017-08-05 17:09:12 -07:00
f3cbb600ac Remove the default load-from-file behavior from the Abacus core. 2017-08-05 17:04:07 -07:00
abc0e2d59f Add tests for more complex functions. 2017-08-05 16:57:52 -07:00
f7d1be086b Add tests for basic operations. 2017-08-05 16:57:22 -07:00
21a925d6d2 Write two functions to help test the code. 2017-08-05 16:56:50 -07:00
0d21898f20 Make loading plugins a non-core part of Abacus, avoiding desktop APIs. 2017-08-05 16:21:02 -07:00
3e39087fde Add numerous documentation fixes. 2017-08-05 16:15:30 -07:00
a984f2960d Prevent leaving unparsed tokens ignored, throwing error instead. 2017-08-05 16:01:08 -07:00
a6832e09f4 Fix mismatched parentheses causing exceptions. 2017-08-05 15:59:49 -07:00
0bcb3b25d9 Fix exception handling. 2017-08-05 15:58:43 -07:00
2f5f967be4 Add new comments. 2017-08-05 15:19:39 -07:00
72a2a8f1c1 Set output string correctly. 2017-08-05 14:43:24 -07:00
58fc94e9d0 Fix clearing input field. 2017-08-05 14:36:42 -07:00
9cedb100ad Re-introduce arthur326's fix for ln speed. 2017-08-05 14:34:57 -07:00
99be2d80f1 Run calculations via thread, and stop thread if necessary. 2017-08-05 14:34:31 -07:00
2523b9b04b Add a stop button. 2017-08-05 13:57:27 -07:00
cd60c9d52f Convert NumberInterface into abstract class, and check for interruption. 2017-08-05 13:54:06 -07:00
23a3eb88f1 Remove old stopping code. 2017-08-05 13:26:29 -07:00
508e98413d Fix caret just multiplying. 2017-08-04 14:54:01 -07:00
d06f611a2e Remove leftover merge conflict marker. 2017-08-04 14:48:08 -07:00
c541eaab97 Format code. 2017-08-04 14:29:24 -07:00
0058ec9c71 Merge branch 'stoppable-new'
# Conflicts:
#	src/main/java/org/nwapw/abacus/fx/AbacusController.java
#	src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java
2017-08-04 14:28:22 -07:00
Riley Jones
f8bf60f383 Fix exp function 2017-08-04 13:52:41 -07:00
Riley Jones
4369eba107 Allow all standard functions to end early 2017-08-04 13:45:29 -07:00
385a0c960d Merge the new edge cases. 2017-08-04 13:32:06 -07:00
Arthur Drobot
d7ae1a80f1 Merge branch 'master' of https://github.com/DanilaFe/abacus 2017-08-04 13:30:05 -07:00
Arthur Drobot
e4a45c0ec4 Add handling of edge cases to pow. 2017-08-04 13:29:53 -07:00
22cf99d23d Add missing comment. 2017-08-04 13:21:28 -07:00
39b36f84e0 Format code. 2017-08-04 13:20:57 -07:00
b036b6c242 Add comments and clean some code. 2017-08-04 12:52:02 -07:00
eb3410f854 Merge branch 'sin'
# Conflicts:
#	src/main/java/org/nwapw/abacus/Abacus.java
#	src/main/java/org/nwapw/abacus/plugin/PluginManager.java
2017-08-04 12:04:37 -07:00
f967053e3d Merge branch 'master' of github.com:DanilaFe/abacus 2017-08-04 11:55:06 -07:00
75824a2a77 Merge branch 'provider-rewrite' into sin
# Conflicts:
#	src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java
2017-08-04 11:54:12 -07:00
da602876e7 Add comments to all the pi-related stuff. 2017-08-04 11:24:07 -07:00
Arthur Drobot
8df468e04a Check arguments in factorial function. 2017-08-04 10:33:55 -07:00
Arthur Drobot
e029ab1fea Add argument checks in division and natural log. 2017-08-04 10:05:18 -07:00
af56d31723 Implement a getPi function for the plugin, and use the new pi value. 2017-08-04 09:55:24 -07:00
Riley Jones
eff7be0204 StandardPlugin uses null 2017-08-03 23:59:23 -07:00
34ae4b42c6 Add attempt to find the number implementation from the class. 2017-08-03 22:16:08 -07:00
b680215f57 Make the promotion map protected and use classes. 2017-08-03 21:27:53 -07:00
e6cc08043e Add the number implementation to the plugin manager, and use it. 2017-08-03 21:05:25 -07:00
3e10ea223f Add a class that keeps track of the implementation information. 2017-08-03 20:50:53 -07:00
44c52b412c Remove old reference to the promotion system and the number class map. 2017-08-03 20:50:39 -07:00
eb51d5d3e4 Add a save and reload button. 2017-08-03 19:00:13 -07:00
8ae28f2dab Add the apply warnings when switching tabs. 2017-08-03 18:16:48 -07:00
0bade4a7df Add warnings that trigger if configuration has been changed. 2017-08-03 18:12:40 -07:00
Riley Jones
9d5f9d901c Plugin fixes 2017-08-03 15:16:26 -07:00
0f02867a4e Add the list of number interface providers to Plugins. 2017-08-03 14:10:04 -07:00
Riley Jones
dad546c5b5 Add stop button 2017-08-03 14:04:09 -07:00
f0e1b85dcf Make sure disk writes are only on save, and add an alert dialog. 2017-08-03 13:55:39 -07:00
37261c2f58 Fix order of operations bug. 2017-08-03 13:14:09 -07:00
691118c206 Add a number provider cache. 2017-08-03 10:54:09 -07:00
Arthur Drobot
95845a1585 Add tan, sec, csc, cot in terms of what we currently have. 2017-08-03 10:30:42 -07:00
819fff6391 Change the cache search function to be more generic. 2017-08-03 10:03:13 -07:00
Arthur Drobot
8cf0c94947 Add cos. 2017-08-03 09:52:56 -07:00
20f6e0b0b2 Merge branch 'plugin-list' 2017-08-03 09:34:14 -07:00
4056013d1f Add defaults that actually work. 2017-08-02 21:57:53 -07:00
c7b5d4c4fc Merge branch 'unit-tests' (only typos fixed) 2017-08-02 21:30:50 -07:00
be28e26607 Stop autosaving, switch to save + reload buttons. 2017-08-02 19:40:22 -07:00
2f1ed5f0d1 Change the default implementation string to "<default>" 2017-08-02 19:26:14 -07:00
2615273d28 Refresh all settings on plugin load. 2017-08-02 19:18:33 -07:00
6e1d2ce629 Clear caches on unload and call onUnload before plugins are removed. 2017-08-02 19:14:50 -07:00
44b8efd9bc Actually disable loading the plugin functions in the PluginManager. 2017-08-02 19:06:16 -07:00
2502c90837 Write disabled / enabled plugins to the configuration. 2017-08-02 19:01:01 -07:00
e49f28a850 Add a check box list cell generator. 2017-08-02 18:48:42 -07:00
88e4a87d81 Add a data model for the plugins displayed in the enabled plugins list. 2017-08-02 18:39:00 -07:00
cda09518c3 Add the disabled plugins configuration option. 2017-08-02 18:38:37 -07:00
56510d97de Add the new UI components required for the plugin loading. 2017-08-02 18:24:20 -07:00
Arthur Drobot
86533d53c9 Fix scaling for optimization in FUNCTION_LN, in the positive direction towards unity (i.e., when the argument passed to ln is small). 2017-08-02 15:33:34 -07:00
Arthur Drobot
9b71f9aaf4 Add comments for FUNCTION_SIN and intFactorial in StandardPlugin. 2017-08-02 15:13:04 -07:00
Arthur Drobot
cf953da40a Fix getPi function. 2017-08-02 14:13:30 -07:00
Arthur Drobot
27ad10c0f1 Remember to use floor in the angle reduction function. Comment debugging output. 2017-08-02 13:27:39 -07:00
Arthur Drobot
601c4fea55 Change ceiling and floor to return NumberInterface. Add fractional part function. Add intValue function. Change StandardPlugin correspondingly. 2017-08-02 12:00:56 -07:00
c2ae0b4138 Merge branch 'negatives' 2017-08-02 11:33:21 -07:00
16938b4e06 Fix division to not multiply numbers. 2017-08-02 11:28:49 -07:00
d964fbfb6f Implement the negation operator. 2017-08-02 11:26:59 -07:00
Arthur Drobot
52fbfd5134 Change getPi to calculate pi using a series. 2017-08-02 11:03:17 -07:00
9713f24ed2 Rename nodes to more general names. 2017-08-02 10:41:52 -07:00
5de9453bec Merge branch 'config-rewrite' 2017-08-01 16:42:58 -07:00
Arthur Drobot
b31151384d Implement sin function and helper functions such as getSmallAngle and floor. 2017-08-01 15:36:54 -07:00
d205651332 Add a number implementation selector box. 2017-08-01 11:52:48 -07:00
6f99f07150 Add comment to PreciseNumber. 2017-08-01 10:49:50 -07:00
2cf41c1029 Add comments to the JavaFX codebase 2017-08-01 10:49:00 -07:00
76677ef494 Merge the two configuration classes into one. 2017-08-01 10:24:32 -07:00
0cd40b028a Merge branch 'javafx' 2017-08-01 09:53:47 -07:00
Arthur Drobot
1ee8c7d231 Keep 15 additional decimal places. 2017-07-31 23:16:37 -07:00
Arthur Drobot
f97d16c640 Comment out debugging output. 2017-07-31 23:09:11 -07:00
Arthur Drobot
a0bba03c2c Separate power and factorial calculations to fix large precision loss in exp. 2017-07-31 22:56:55 -07:00
Arthur Drobot
8666e96420 Remove unused code and functions in StandardPlugin. 2017-07-31 14:53:41 -07:00
Arthur Drobot
fd40e6b297 Rewrite exp. (Now works faster.) Add private factorial function to StandardPlugin as well. 2017-07-31 14:49:25 -07:00
Arthur Drobot
79ccd61af3 Add ceiling to NumberInterface and the two numbers that implement it. 2017-07-31 13:25:23 -07:00
Arthur Drobot
699ba9e193 Merge branch 'master' of https://github.com/DanilaFe/abacus 2017-07-31 12:40:19 -07:00
Arthur Drobot
e43f223086 Optimize log. 2017-07-31 12:39:56 -07:00
21d88fe256 Update README.md 2017-07-30 14:04:24 -07:00
3d61ead0f6 Update README.md 2017-07-30 14:03:58 -07:00
29 changed files with 1578 additions and 383 deletions

View File

@@ -5,10 +5,10 @@ Summer project for NWAPW.
Created by Arthur Drobot, Danila Fedorin and Riley Jones. Created by Arthur Drobot, Danila Fedorin and Riley Jones.
## Project Description ## Project Description
Abacus is a calculator built with extensibility and usability in mind. It provides a plugin interface, via Java, as Lua provides too difficult to link up to the Java core. The description of the internals of the project can be found on the wiki page. Abacus is a calculator built with extensibility and usability in mind. It provides a plugin interface, via Java, as Lua proves too difficult to link up to the Java core. The description of the internals of the project can be found on the wiki page.
## Current State ## Current State
Abacus is being built for the Northwest Advanced Programming Workshop, a 3 week program in which students work in treams to complete a single project, following principles of agile development. Because of its short timeframe, Abacus is not even close to completed state. Below is a list of the current features and problems. Abacus is being built for the Northwest Advanced Programming Workshop, a 3 week program in which students work in teams to complete a single project, following principles of agile development. Because of its short timeframe, Abacus is not even close to completed state. Below is a list of the current features and problems.
- [x] Basic number class - [x] Basic number class
- [x] Implementation of basic functions - [x] Implementation of basic functions
- [x] Implementation of `exp`, `ln`, `sqrt` using the basic functions and Taylor Series - [x] Implementation of `exp`, `ln`, `sqrt` using the basic functions and Taylor Series

View File

@@ -1,22 +1,17 @@
package org.nwapw.abacus; package org.nwapw.abacus;
import org.nwapw.abacus.config.ConfigurationObject; import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.fx.AbacusApplication; import org.nwapw.abacus.fx.AbacusApplication;
import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.parsing.LexerTokenizer; import org.nwapw.abacus.parsing.LexerTokenizer;
import org.nwapw.abacus.parsing.ShuntingYardParser; import org.nwapw.abacus.parsing.ShuntingYardParser;
import org.nwapw.abacus.parsing.TreeBuilder; import org.nwapw.abacus.parsing.TreeBuilder;
import org.nwapw.abacus.plugin.ClassFinder; import org.nwapw.abacus.plugin.NumberImplementation;
import org.nwapw.abacus.plugin.PluginManager; import org.nwapw.abacus.plugin.PluginManager;
import org.nwapw.abacus.plugin.StandardPlugin; import org.nwapw.abacus.plugin.StandardPlugin;
import org.nwapw.abacus.tree.NumberReducer; import org.nwapw.abacus.tree.NumberReducer;
import org.nwapw.abacus.tree.TreeNode; import org.nwapw.abacus.tree.TreeNode;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
/** /**
* The main calculator class. This is responsible * The main calculator class. This is responsible
* for piecing together all of the components, allowing * for piecing together all of the components, allowing
@@ -25,13 +20,9 @@ import java.lang.reflect.InvocationTargetException;
public class Abacus { public class Abacus {
/** /**
* The default implementation to use for the number representation. * The default number implementation to be used if no other one is available / selected.
*/ */
public static final Class<? extends NumberInterface> DEFAULT_NUMBER = NaiveNumber.class; public static final NumberImplementation DEFAULT_IMPLEMENTATION = StandardPlugin.IMPLEMENTATION_NAIVE;
/**
* The file used for saving and loading configuration.
*/
public static final File CONFIG_FILE = new File("config.toml");
/** /**
* The plugin manager responsible for * The plugin manager responsible for
@@ -46,7 +37,7 @@ public class Abacus {
/** /**
* The configuration loaded from a file. * The configuration loaded from a file.
*/ */
private ConfigurationObject configuration; private Configuration configuration;
/** /**
* The tree builder used to construct a tree * The tree builder used to construct a tree
* from a string. * from a string.
@@ -55,26 +46,19 @@ public class Abacus {
/** /**
* Creates a new instance of the Abacus calculator. * Creates a new instance of the Abacus calculator.
*
* @param configuration the configuration object for this Abacus instance.
*/ */
public Abacus() { public Abacus(Configuration configuration) {
pluginManager = new PluginManager(); pluginManager = new PluginManager(this);
numberReducer = new NumberReducer(this); numberReducer = new NumberReducer(this);
configuration = new ConfigurationObject(CONFIG_FILE); this.configuration = new Configuration(configuration);
configuration.save(CONFIG_FILE);
LexerTokenizer lexerTokenizer = new LexerTokenizer(); LexerTokenizer lexerTokenizer = new LexerTokenizer();
ShuntingYardParser shuntingYardParser = new ShuntingYardParser(this); ShuntingYardParser shuntingYardParser = new ShuntingYardParser(this);
treeBuilder = new TreeBuilder<>(lexerTokenizer, shuntingYardParser); treeBuilder = new TreeBuilder<>(lexerTokenizer, shuntingYardParser);
pluginManager.addListener(lexerTokenizer);
pluginManager.addListener(shuntingYardParser); pluginManager.addListener(shuntingYardParser);
pluginManager.addInstantiated(new StandardPlugin(pluginManager)); pluginManager.addListener(lexerTokenizer);
try {
ClassFinder.loadJars("plugins")
.forEach(plugin -> pluginManager.addClass(plugin));
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
pluginManager.load();
} }
public static void main(String[] args) { public static void main(String[] args) {
@@ -114,7 +98,7 @@ public class Abacus {
* *
* @return the configuration object. * @return the configuration object.
*/ */
public ConfigurationObject getConfiguration() { public Configuration getConfiguration() {
return configuration; return configuration;
} }
@@ -147,15 +131,10 @@ public class Abacus {
* @return the resulting number. * @return the resulting number.
*/ */
public NumberInterface numberFromString(String numberString) { public NumberInterface numberFromString(String numberString) {
Class<? extends NumberInterface> toInstantiate = NumberImplementation toInstantiate =
pluginManager.numberFor(configuration.getNumberImplementation()); pluginManager.numberImplementationFor(configuration.getNumberImplementation());
if (toInstantiate == null) toInstantiate = DEFAULT_NUMBER; if (toInstantiate == null) toInstantiate = DEFAULT_IMPLEMENTATION;
try { return toInstantiate.instanceForString(numberString);
return toInstantiate.getConstructor(String.class).newInstance(numberString);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
return null;
} }
} }

View File

@@ -1,14 +1,159 @@
package org.nwapw.abacus.config; 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;
/** /**
* Serializable class that will be used to load TOML * The configuration object that stores
* configurations. * options that the user can change.
*/ */
public class Configuration { public class Configuration {
/** /**
* The type of number this calculator should use. * The defaults TOML string.
*/ */
public String numberType; 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

@@ -1,111 +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;
/**
* A configuration object, which essentially
* manages saving, loading, and getting values
* from the configuration. While Configuration is
* the data model, this is the interface with it.
*/
public class ConfigurationObject {
/**
* The writer used to store the configuration.
*/
private static final TomlWriter TOML_WRITER = new TomlWriter();
/**
* The configuration instance being modeled.
*/
private Configuration configuration;
/**
* Creates a new configuration object with the given config.
*
* @param config the config to use.
*/
public ConfigurationObject(Configuration config) {
setup(config);
}
/**
* Create a configuration object by attempting to
* load a config from the given path, using the
* default configuration otherwise.
*
* @param path the path to attempt to load.
*/
public ConfigurationObject(File path) {
Configuration config;
if (!path.exists()) {
config = getDefaultConfig();
} else {
Toml parse = new Toml();
parse.read(path);
config = parse.to(Configuration.class);
}
setup(config);
}
/**
* Creates a new configuration object with the
* default configuration.
*/
public ConfigurationObject() {
setup(getDefaultConfig());
}
/**
* Sets up the ConfigurationObject.
* different constructors do different things,
* but they all lead here.
*
* @param configuration the configuration to set up with.
*/
private void setup(Configuration configuration) {
this.configuration = configuration;
}
/**
* Creates a default configuration.
*
* @return the newly created default configuration.
*/
private Configuration getDefaultConfig() {
configuration = new Configuration();
configuration.numberType = "naive";
return configuration;
}
/**
* Returns the implementation the user has requested to
* represent their numbers.
*
* @return the implementation name.
*/
public String getNumberImplementation() {
return configuration.numberType;
}
/**
* Saves the ConfigurationObject to the given file.
*
* @param toFile the file to save ot.
* @return true if the save succeed, false if otherwise.
*/
public boolean save(File toFile) {
if (toFile.getParentFile() != null) toFile.getParentFile().mkdirs();
try {
TOML_WRITER.write(configuration, toFile);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}

View File

@@ -1,10 +1,6 @@
package org.nwapw.abacus.function; package org.nwapw.abacus.function;
import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber;
import java.util.HashMap;
/** /**
* A function that operates on one or more * A function that operates on one or more
@@ -12,15 +8,6 @@ import java.util.HashMap;
*/ */
public abstract class Function { public abstract class Function {
/**
* A map to correctly promote different number implementations to each other.
*/
private static final HashMap<Class<? extends NumberInterface>, Integer> priorityMap =
new HashMap<Class<? extends NumberInterface>, Integer>() {{
put(NaiveNumber.class, 0);
put(PreciseNumber.class, 1);
}};
/** /**
* Checks whether the given params will work for the given function. * Checks whether the given params will work for the given function.
* *

View File

@@ -26,6 +26,7 @@ public class Operator {
* Creates a new operator with the given parameters. * Creates a new operator with the given parameters.
* *
* @param associativity the associativity of the operator. * @param associativity the associativity of the operator.
* @param operatorType the type of this operator, like binary infix or unary postfix.
* @param precedence the precedence of the operator. * @param precedence the precedence of the operator.
* @param function the function that the operator calls. * @param function the function that the operator calls.
*/ */

View File

@@ -4,5 +4,5 @@ package org.nwapw.abacus.function;
* The type of an operator, describing how it should behave. * The type of an operator, describing how it should behave.
*/ */
public enum OperatorType { public enum OperatorType {
BINARY_INFIX, UNARY_POSTFIX BINARY_INFIX, UNARY_POSTFIX, UNARY_PREFIX
} }

View File

@@ -6,15 +6,31 @@ import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
/**
* The main application class for JavaFX responsible for loading
* and displaying the fxml file.
*/
public class AbacusApplication extends Application { public class AbacusApplication extends Application {
/**
* The controller currently managing the application.
*/
private AbacusController controller;
@Override @Override
public void start(Stage primaryStage) throws Exception { public void start(Stage primaryStage) throws Exception {
Parent parent = FXMLLoader.load(getClass().getResource("/abacus.fxml")); FXMLLoader loader = new FXMLLoader(getClass().getResource("/abacus.fxml"));
Parent parent = loader.load();
controller = loader.getController();
Scene mainScene = new Scene(parent, 320, 480); Scene mainScene = new Scene(parent, 320, 480);
primaryStage.setScene(mainScene); primaryStage.setScene(mainScene);
primaryStage.setTitle("Abacus"); primaryStage.setTitle("Abacus");
primaryStage.show(); primaryStage.show();
} }
@Override
public void stop() throws Exception {
super.stop();
controller.performStop();
}
} }

View File

@@ -1,21 +1,76 @@
package org.nwapw.abacus.fx; package org.nwapw.abacus.fx;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import javafx.util.Callback; import javafx.util.Callback;
import javafx.util.StringConverter;
import org.nwapw.abacus.Abacus; import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.number.ComputationInterruptedException;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
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.TreeNode; import org.nwapw.abacus.tree.TreeNode;
import java.io.File;
import java.io.IOException;
import java.util.Set;
public class AbacusController {
/**
* The controller for the abacus FX UI, responsible
* for all the user interaction.
*/
public class AbacusController implements PluginListener {
/**
* The file used for saving and loading configuration.
*/
public static final File CONFIG_FILE = new File("config.toml");
/**
* The title for the apply alert dialog.
*/
private static final String APPLY_MSG_TITLE = "\"Apply\" Needed";
/**
* The text for the header of the apply alert dialog.
*/
private static final String APPLY_MSG_HEADER = "The settings have not been applied.";
/**
* The text for the dialog that is shown if settings haven't been applied.
*/
private static final String APPLY_MSG_TEXT = "You have made changes to the configuration, however, you haven't pressed \"Apply\". " +
"The changes to the configuration will not be present in the calculator until \"Apply\" is pressed.";
/**
* Constant string that is displayed if the text could not be lexed or parsed.
*/
private static final String ERR_SYNTAX = "Syntax Error"; private static final String ERR_SYNTAX = "Syntax Error";
/**
* Constant string that is displayed if the tree could not be reduced.
*/
private static final String ERR_EVAL = "Evaluation Error"; private static final String ERR_EVAL = "Evaluation Error";
/**
* Constant string that is displayed if the calculations are stopped before they are done.
*/
private static final String ERR_STOP = "Stopped";
/**
* Constant string that is displayed if the calculations are interrupted by an exception.
*/
private static final String ERR_EXCEPTION = "Exception Thrown";
@FXML
private TabPane coreTabPane;
@FXML
private Tab calculateTab;
@FXML
private Tab settingsTab;
@FXML @FXML
private TableView<HistoryModel> historyTable; private TableView<HistoryModel> historyTable;
@FXML @FXML
@@ -30,48 +85,254 @@ public class AbacusController {
private TextField inputField; private TextField inputField;
@FXML @FXML
private Button inputButton; private Button inputButton;
@FXML
private Button stopButton;
@FXML
private ComboBox<String> numberImplementationBox;
@FXML
private ListView<ToggleablePlugin> enabledPluginView;
@FXML
private TextField computationLimitField;
/**
* The list of history entries, created by the users.
*/
private ObservableList<HistoryModel> historyData; private ObservableList<HistoryModel> historyData;
/**
* The abacus instance used for calculations and all
* other main processing code.
*/
private ObservableList<String> numberImplementationOptions;
/**
* The list of plugin objects that can be toggled on and off,
* and, when reloaded, get added to the plugin manager's black list.
*/
private ObservableList<ToggleablePlugin> enabledPlugins;
/**
* The abacus instance used for changing the plugin configuration.
*/
private Abacus abacus; private Abacus abacus;
/**
* Boolean which represents whether changes were made to the configuration.
*/
private boolean changesMade;
/**
* Whether an alert about changes to the configuration was already shown.
*/
private boolean reloadAlertShown;
/**
* The alert shown when a press to "apply" is needed.
*/
private Alert reloadAlert;
/**
* The runnable that takes care of killing computations that take too long.
*/
private final Runnable TIMER_RUNNABLE = () -> {
try {
Configuration abacusConfig = abacus.getConfiguration();
if(abacusConfig.getComputationDelay() == 0) return;
Thread.sleep((long) (abacusConfig.getComputationDelay() * 1000));
performStop();
} catch (InterruptedException e) { }
};
/**
* The runnable used to perform the calculation.
*/
private final Runnable CALCULATION_RUNNABLE = new Runnable() {
private String attemptCalculation(){
try {
TreeNode constructedTree = abacus.parseString(inputField.getText());
if (constructedTree == null) {
return ERR_SYNTAX;
}
NumberInterface evaluatedNumber = abacus.evaluateTree(constructedTree);
if (evaluatedNumber == null) {
return ERR_EVAL;
}
String resultingString = evaluatedNumber.toString();
historyData.add(new HistoryModel(inputField.getText(), constructedTree.toString(), resultingString));
return resultingString;
} catch (ComputationInterruptedException exception) {
return ERR_STOP;
} catch (RuntimeException exception){
exception.printStackTrace();
return ERR_EXCEPTION;
}
}
@Override
public void run() {
String calculation = attemptCalculation();
Platform.runLater(() -> {
outputText.setText(calculation);
inputField.setText("");
inputButton.setDisable(false);
stopButton.setDisable(true);
});
}
};
/**
* The thread that is waiting to pause the calculation.
*/
private Thread computationLimitThread;
/**
* The thread in which the computation runs.
*/
private Thread calculationThread;
/**
* Alerts the user if the changes they made
* have not yet been applied.
*/
private void alertIfApplyNeeded(boolean ignorePrevious) {
if (changesMade && (!reloadAlertShown || ignorePrevious)) {
reloadAlertShown = true;
reloadAlert.showAndWait();
}
}
@FXML @FXML
public void initialize(){ public void initialize() {
Callback<TableColumn<HistoryModel, String>, TableCell<HistoryModel, String>> cellFactory = Callback<TableColumn<HistoryModel, String>, TableCell<HistoryModel, String>> cellFactory =
param -> new CopyableCell<>(); param -> new CopyableCell<>();
Callback<ListView<ToggleablePlugin>, ListCell<ToggleablePlugin>> pluginCellFactory =
param -> new CheckBoxListCell<>(ToggleablePlugin::enabledProperty, new StringConverter<ToggleablePlugin>() {
@Override
public String toString(ToggleablePlugin object) {
return object.getClassName().substring(object.getClassName().lastIndexOf('.') + 1);
}
@Override
public ToggleablePlugin fromString(String string) {
return new ToggleablePlugin(true, string);
}
});
abacus = new Abacus();
historyData = FXCollections.observableArrayList(); historyData = FXCollections.observableArrayList();
historyTable.setItems(historyData); historyTable.setItems(historyData);
numberImplementationOptions = FXCollections.observableArrayList();
numberImplementationBox.setItems(numberImplementationOptions);
numberImplementationBox.getSelectionModel().selectedIndexProperty().addListener(e -> changesMade = true);
historyTable.getSelectionModel().setCellSelectionEnabled(true); historyTable.getSelectionModel().setCellSelectionEnabled(true);
enabledPlugins = FXCollections.observableArrayList();
enabledPluginView.setItems(enabledPlugins);
enabledPluginView.setCellFactory(pluginCellFactory);
inputColumn.setCellFactory(cellFactory); inputColumn.setCellFactory(cellFactory);
inputColumn.setCellValueFactory(cell -> cell.getValue().inputProperty()); inputColumn.setCellValueFactory(cell -> cell.getValue().inputProperty());
parsedColumn.setCellFactory(cellFactory); parsedColumn.setCellFactory(cellFactory);
parsedColumn.setCellValueFactory(cell -> cell.getValue().parsedProperty()); parsedColumn.setCellValueFactory(cell -> cell.getValue().parsedProperty());
outputColumn.setCellFactory(cellFactory); outputColumn.setCellFactory(cellFactory);
outputColumn.setCellValueFactory(cell -> cell.getValue().outputProperty()); outputColumn.setCellValueFactory(cell -> cell.getValue().outputProperty());
coreTabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue.equals(settingsTab)) alertIfApplyNeeded(true);
});
abacus = new Abacus(new Configuration(CONFIG_FILE));
PluginManager abacusPluginManager = abacus.getPluginManager();
abacusPluginManager.addListener(this);
abacusPluginManager.addInstantiated(new StandardPlugin(abacus.getPluginManager()));
try {
ClassFinder.loadJars("plugins").forEach(abacusPluginManager::addClass);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
abacusPluginManager.reload();
computationLimitField.setText(Double.toString(abacus.getConfiguration().getComputationDelay()));
computationLimitField.textProperty().addListener((observable, oldValue, newValue) -> {
if(!newValue.matches("(\\d+(\\.\\d*)?)?")) {
computationLimitField.setText(oldValue);
} else {
changesMade = true;
}
});
changesMade = false;
reloadAlertShown = false;
reloadAlert = new Alert(Alert.AlertType.WARNING);
reloadAlert.setTitle(APPLY_MSG_TITLE);
reloadAlert.setHeaderText(APPLY_MSG_HEADER);
reloadAlert.setContentText(APPLY_MSG_TEXT);
} }
@FXML @FXML
private void performCalculation(){ public void performCalculation() {
inputButton.setDisable(true); inputButton.setDisable(true);
TreeNode constructedTree = abacus.parseString(inputField.getText()); stopButton.setDisable(false);
if(constructedTree == null){ calculationThread = new Thread(CALCULATION_RUNNABLE);
outputText.setText(ERR_SYNTAX); calculationThread.start();
inputButton.setDisable(false); computationLimitThread = new Thread(TIMER_RUNNABLE);
return; computationLimitThread.start();
} }
NumberInterface evaluatedNumber = abacus.evaluateTree(constructedTree);
if(evaluatedNumber == null){
outputText.setText(ERR_EVAL);
inputButton.setDisable(false);
return;
}
outputText.setText(evaluatedNumber.toString());
historyData.add(new HistoryModel(inputField.getText(), constructedTree.toString(), evaluatedNumber.toString()));
inputButton.setDisable(false); @FXML
inputField.setText(""); public void performStop(){
if(calculationThread != null) {
calculationThread.interrupt();
calculationThread = null;
}
if(computationLimitThread != null){
computationLimitThread.interrupt();
computationLimitThread = null;
}
}
@FXML
public void performSaveAndReload() {
performSave();
performReload();
changesMade = false;
reloadAlertShown = false;
}
@FXML
public void performReload() {
alertIfApplyNeeded(true);
abacus.getPluginManager().reload();
}
@FXML
public void performSave() {
Configuration configuration = abacus.getConfiguration();
configuration.setNumberImplementation(numberImplementationBox.getSelectionModel().getSelectedItem());
Set<String> disabledPlugins = configuration.getDisabledPlugins();
disabledPlugins.clear();
for (ToggleablePlugin pluginEntry : enabledPlugins) {
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);
changesMade = false;
reloadAlertShown = false;
}
@Override
public void onLoad(PluginManager manager) {
Configuration configuration = abacus.getConfiguration();
Set<String> disabledPlugins = configuration.getDisabledPlugins();
numberImplementationOptions.addAll(abacus.getPluginManager().getAllNumberImplementations());
String actualImplementation = configuration.getNumberImplementation();
String toSelect = (numberImplementationOptions.contains(actualImplementation)) ? actualImplementation : "<default>";
numberImplementationBox.getSelectionModel().select(toSelect);
for (Class<?> pluginClass : abacus.getPluginManager().getLoadedPluginClasses()) {
String fullName = pluginClass.getName();
ToggleablePlugin plugin = new ToggleablePlugin(!disabledPlugins.contains(fullName), fullName);
plugin.enabledProperty().addListener(e -> changesMade = true);
enabledPlugins.add(plugin);
}
}
@Override
public void onUnload(PluginManager manager) {
enabledPlugins.clear();
numberImplementationOptions.clear();
} }
} }

View File

@@ -6,11 +6,21 @@ import javafx.scene.input.MouseEvent;
import java.awt.*; import java.awt.*;
import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.StringSelection;
/**
* A cell that copies its value to the clipboard
* when double clicked.
*
* @param <S> The type of the table view generic type.
* @param <T> The type of the value contained in the cell.
*/
public class CopyableCell<S, T> extends TableCell<S, T> { public class CopyableCell<S, T> extends TableCell<S, T> {
public CopyableCell(){ /**
* Creates a new copyable cell.
*/
public CopyableCell() {
addEventFilter(MouseEvent.MOUSE_CLICKED, event -> { addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
if(event.getClickCount() == 2){ if (event.getClickCount() == 2) {
Toolkit.getDefaultToolkit().getSystemClipboard() Toolkit.getDefaultToolkit().getSystemClipboard()
.setContents(new StringSelection(getText()), null); .setContents(new StringSelection(getText()), null);
} }

View File

@@ -3,13 +3,35 @@ package org.nwapw.abacus.fx;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
/**
* The data model used for storing history entries.
*/
public class HistoryModel { public class HistoryModel {
/**
* The property used for displaying the column
* for the user input.
*/
private final StringProperty input; private final StringProperty input;
/**
* The property used for displaying the column
* that contains the parsed input.
*/
private final StringProperty parsed; private final StringProperty parsed;
/**
* The property used for displaying the column
* that contains the program output.
*/
private final StringProperty output; private final StringProperty output;
public HistoryModel(String input, String parsed, String output){ /**
* Creates a new history model with the given variables.
*
* @param input the user input
* @param parsed the parsed input
* @param output the program output.
*/
public HistoryModel(String input, String parsed, String output) {
this.input = new SimpleStringProperty(); this.input = new SimpleStringProperty();
this.parsed = new SimpleStringProperty(); this.parsed = new SimpleStringProperty();
this.output = new SimpleStringProperty(); this.output = new SimpleStringProperty();
@@ -18,23 +40,56 @@ public class HistoryModel {
this.output.setValue(output); this.output.setValue(output);
} }
/**
* Gets the input property.
*
* @return the input property.
*/
public StringProperty inputProperty() { public StringProperty inputProperty() {
return input; return input;
} }
/**
* Gets the input.
*
* @return the input.
*/
public String getInput() { public String getInput() {
return input.get(); return input.get();
} }
/**
* Gets the parsed input property.
*
* @return the parsed input property.
*/
public StringProperty parsedProperty() { public StringProperty parsedProperty() {
return parsed; return parsed;
} }
/**
* Gets the parsed input.
*
* @return the parsed input.
*/
public String getParsed() { public String getParsed() {
return parsed.get(); return parsed.get();
} }
/**
* Gets the output property.
*
* @return the output property.
*/
public StringProperty outputProperty() { public StringProperty outputProperty() {
return output; return output;
} }
/**
* Gets the program output.
*
* @return the output.
*/
public String getOutput() { public String getOutput() {
return output.get(); return output.get();
} }

View File

@@ -0,0 +1,60 @@
package org.nwapw.abacus.fx;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
/**
* Class that represents an entry in the plugin check box list.
* The changes from this property are written to the config on application.
*/
public class ToggleablePlugin {
/**
* The property that determines whether the plugin will be enabled.
*/
private final BooleanProperty enabled;
/**
* The name of the class this entry toggles.
*/
private final String className;
/**
* Creates a new toggleable plugin with the given properties.
*
* @param enabled the enabled / disabled state at the beginning.
* @param className the name of the class this plugin toggles.
*/
public ToggleablePlugin(boolean enabled, String className) {
this.enabled = new SimpleBooleanProperty();
this.enabled.setValue(enabled);
this.className = className;
}
/**
* Gets the enabled property of this plugin.
*
* @return the enabled property.
*/
public BooleanProperty enabledProperty() {
return enabled;
}
/**
* Checks if this plugin entry should be enabled.
*
* @return whether this plugin will be enabled.
*/
public boolean isEnabled() {
return enabled.get();
}
/**
* Gets the class name this plugin toggles.
*
* @return the class name that should be disabled.
*/
public String getClassName() {
return className;
}
}

View File

@@ -15,7 +15,7 @@ public class ValueNode<T> extends PatternNode<T> {
/** /**
* Creates a new node that matches the given character. * Creates a new node that matches the given character.
* *
* @param value * @param value the character value of the node.
*/ */
public ValueNode(char value) { public ValueNode(char value) {
this.value = value; this.value = value;

View File

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

View File

@@ -3,7 +3,7 @@ package org.nwapw.abacus.number;
/** /**
* An implementation of NumberInterface using a double. * An implementation of NumberInterface using a double.
*/ */
public class NaiveNumber implements NumberInterface { public class NaiveNumber extends NumberInterface {
/** /**
* The number zero. * The number zero.
@@ -26,6 +26,7 @@ public class NaiveNumber implements NumberInterface {
public NaiveNumber(String value) { public NaiveNumber(String value) {
this(Double.parseDouble(value)); this(Double.parseDouble(value));
} }
/** /**
* Creates a new NaiveNumber with the given value. * Creates a new NaiveNumber with the given value.
* *
@@ -41,32 +42,32 @@ public class NaiveNumber implements NumberInterface {
} }
@Override @Override
public NumberInterface multiply(NumberInterface multiplier) { public NumberInterface multiplyInternal(NumberInterface multiplier) {
return new NaiveNumber(value * ((NaiveNumber) multiplier).value); return new NaiveNumber(value * ((NaiveNumber) multiplier).value);
} }
@Override @Override
public NumberInterface divide(NumberInterface divisor) { public NumberInterface divideInternal(NumberInterface divisor) {
return new NaiveNumber(value / ((NaiveNumber) divisor).value); return new NaiveNumber(value / ((NaiveNumber) divisor).value);
} }
@Override @Override
public NumberInterface add(NumberInterface summand) { public NumberInterface addInternal(NumberInterface summand) {
return new NaiveNumber(value + ((NaiveNumber) summand).value); return new NaiveNumber(value + ((NaiveNumber) summand).value);
} }
@Override @Override
public NumberInterface subtract(NumberInterface subtrahend) { public NumberInterface subtractInternal(NumberInterface subtrahend) {
return new NaiveNumber(value - ((NaiveNumber) subtrahend).value); return new NaiveNumber(value - ((NaiveNumber) subtrahend).value);
} }
@Override @Override
public NumberInterface negate() { public NumberInterface negateInternal() {
return new NaiveNumber(-value); return new NaiveNumber(-value);
} }
@Override @Override
public NumberInterface intPow(int exponent) { public NumberInterface intPowInternal(int exponent) {
if (exponent == 0) { if (exponent == 0) {
return NaiveNumber.ONE; return NaiveNumber.ONE;
} }
@@ -94,7 +95,27 @@ public class NaiveNumber implements NumberInterface {
} }
@Override @Override
public NumberInterface promoteTo(Class<? extends NumberInterface> toClass) { public NumberInterface ceilingInternal() {
return new NaiveNumber(Math.ceil(value));
}
@Override
public NumberInterface floorInternal() {
return new NaiveNumber(Math.floor(value));
}
@Override
public NumberInterface fractionalPartInternal() {
return new NaiveNumber(value - Math.floor(value));
}
@Override
public int intValue() {
return (int) value;
}
@Override
public NumberInterface promoteToInternal(Class<? extends NumberInterface> toClass) {
if (toClass == this.getClass()) return this; if (toClass == this.getClass()) return this;
else if (toClass == PreciseNumber.class) { else if (toClass == PreciseNumber.class) {
return new PreciseNumber(Double.toString(value)); return new PreciseNumber(Double.toString(value));

View File

@@ -3,14 +3,22 @@ package org.nwapw.abacus.number;
/** /**
* An interface used to represent a number. * An interface used to represent a number.
*/ */
public interface NumberInterface { public abstract class NumberInterface {
/**
* Check if the thread was interrupted and
* throw an exception to end the computation.
*/
private static void checkInterrupted(){
if(Thread.currentThread().isInterrupted())
throw new ComputationInterruptedException();
}
/** /**
* The maximum precision to which this number operates. * The maximum precision to which this number operates.
* *
* @return the precision. * @return the precision.
*/ */
int getMaxPrecision(); public abstract int getMaxPrecision();
/** /**
* Multiplies this number by another, returning * Multiplies this number by another, returning
@@ -19,7 +27,21 @@ public interface NumberInterface {
* @param multiplier the multiplier * @param multiplier the multiplier
* @return the result of the multiplication. * @return the result of the multiplication.
*/ */
NumberInterface multiply(NumberInterface multiplier); protected abstract NumberInterface multiplyInternal(NumberInterface multiplier);
/**
* Multiplies this number by another, returning
* a new number instance. Also, checks if the
* thread has been interrupted, and if so, throws
* an exception.
*
* @param multiplier the multiplier
* @return the result of the multiplication.
*/
public final NumberInterface multiply(NumberInterface multiplier){
checkInterrupted();
return multiplyInternal(multiplier);
}
/** /**
* Divides this number by another, returning * Divides this number by another, returning
@@ -28,7 +50,21 @@ public interface NumberInterface {
* @param divisor the divisor * @param divisor the divisor
* @return the result of the division. * @return the result of the division.
*/ */
NumberInterface divide(NumberInterface divisor); protected abstract NumberInterface divideInternal(NumberInterface divisor);
/**
* Divides this number by another, returning
* a new number instance. Also, checks if the
* thread has been interrupted, and if so, throws
* an exception.
*
* @param divisor the divisor
* @return the result of the division.
*/
public final NumberInterface divide(NumberInterface divisor){
checkInterrupted();
return divideInternal(divisor);
}
/** /**
* Adds this number to another, returning * Adds this number to another, returning
@@ -37,7 +73,21 @@ public interface NumberInterface {
* @param summand the summand * @param summand the summand
* @return the result of the summation. * @return the result of the summation.
*/ */
NumberInterface add(NumberInterface summand); protected abstract NumberInterface addInternal(NumberInterface summand);
/**
* Adds this number to another, returning
* a new number instance. Also, checks if the
* thread has been interrupted, and if so, throws
* an exception.
*
* @param summand the summand
* @return the result of the summation.
*/
public final NumberInterface add(NumberInterface summand){
checkInterrupted();
return addInternal(summand);
}
/** /**
* Subtracts another number from this number, * Subtracts another number from this number,
@@ -46,7 +96,21 @@ public interface NumberInterface {
* @param subtrahend the subtrahend. * @param subtrahend the subtrahend.
* @return the result of the subtraction. * @return the result of the subtraction.
*/ */
NumberInterface subtract(NumberInterface subtrahend); protected abstract NumberInterface subtractInternal(NumberInterface subtrahend);
/**
* Subtracts another number from this number,
* a new number instance. Also, checks if the
* thread has been interrupted, and if so, throws
* an exception.
*
* @param subtrahend the subtrahend.
* @return the result of the subtraction.
*/
public final NumberInterface subtract(NumberInterface subtrahend){
checkInterrupted();
return subtractInternal(subtrahend);
}
/** /**
* Returns a new instance of this number with * Returns a new instance of this number with
@@ -54,7 +118,21 @@ public interface NumberInterface {
* *
* @return the new instance. * @return the new instance.
*/ */
NumberInterface negate(); protected abstract NumberInterface negateInternal();
/**
* Returns a new instance of this number with
* the sign flipped. Also, checks if the
* thread has been interrupted, and if so, throws
* an exception.
*
* @return the new instance.
*/
public final NumberInterface negate(){
checkInterrupted();
return negateInternal();
}
/** /**
* Raises this number to an integer power. * Raises this number to an integer power.
@@ -62,7 +140,20 @@ public interface NumberInterface {
* @param exponent the exponent to which to take the number. * @param exponent the exponent to which to take the number.
* @return the resulting value. * @return the resulting value.
*/ */
NumberInterface intPow(int exponent); protected abstract NumberInterface intPowInternal(int exponent);
/**
* Raises this number to an integer power. Also, checks if the
* thread has been interrupted, and if so, throws
* an exception.
*
* @param exponent the exponent to which to take the number.
* @return the resulting value.
*/
public final NumberInterface intPow(int exponent){
checkInterrupted();
return intPowInternal(exponent);
}
/** /**
* Compares this number to another. * Compares this number to another.
@@ -70,14 +161,78 @@ public interface NumberInterface {
* @param number the number to compare to. * @param number the number to compare to.
* @return same as Integer.compare(); * @return same as Integer.compare();
*/ */
int compareTo(NumberInterface number); public abstract int compareTo(NumberInterface number);
/** /**
* Same as Math.signum(). * Same as Math.signum().
* *
* @return 1 if this number is positive, -1 if this number is negative, 0 if this number is 0. * @return 1 if this number is positive, -1 if this number is negative, 0 if this number is 0.
*/ */
int signum(); public abstract int signum();
/**
* Returns the least integer greater than or equal to the number.
*
* @return the least integer >= the number, if int can hold the value.
*/
protected abstract NumberInterface ceilingInternal();
/**
* Returns the least integer greater than or equal to the number.
* Also, checks if the thread has been interrupted, and if so, throws
* an exception.
*
* @return the least integer bigger or equal to the number, if int can hold the value.
*/
public final NumberInterface ceiling(){
checkInterrupted();
return ceilingInternal();
}
/**
* Return the greatest integer less than or equal to the number.
*
* @return the greatest integer smaller or equal the number, if int can hold the value.
*/
protected abstract NumberInterface floorInternal();
/**
* Return the greatest integer less than or equal to the number.
* Also, checks if the thread has been interrupted, and if so, throws
* an exception.
*
* @return the greatest int >= the number, if int can hold the value.
*/
public final NumberInterface floor(){
checkInterrupted();
return floorInternal();
}
/**
* Returns the fractional part of the number.
*
* @return the fractional part of the number.
*/
protected abstract NumberInterface fractionalPartInternal();
/**
* Returns the fractional part of the number.
* Also, checks if the thread has been interrupted,
* and if so, throws an exception.
* @return the fractional part of the number.
*/
public final NumberInterface fractionalPart(){
checkInterrupted();
return fractionalPartInternal();
}
/**
* Returns the integer representation of this number, discarding any fractional part,
* if int can hold the value.
*
* @return the integer value of this number.
*/
public abstract int intValue();
/** /**
* Promotes this class to another number class. * Promotes this class to another number class.
@@ -85,6 +240,19 @@ public interface NumberInterface {
* @param toClass the class to promote to. * @param toClass the class to promote to.
* @return the resulting new instance. * @return the resulting new instance.
*/ */
NumberInterface promoteTo(Class<? extends NumberInterface> toClass); 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.
*/
public final NumberInterface promoteTo(Class<? extends NumberInterface> toClass) {
checkInterrupted();
return promoteToInternal(toClass);
}
} }

View File

@@ -3,20 +3,24 @@ package org.nwapw.abacus.number;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
public class PreciseNumber implements NumberInterface { /**
* A number that uses a BigDecimal to store its value,
* leading to infinite possible precision.
*/
public class PreciseNumber extends NumberInterface {
/** /**
* The number one. * The number one.
*/ */
static final PreciseNumber ONE = new PreciseNumber(BigDecimal.ONE); public static final PreciseNumber ONE = new PreciseNumber(BigDecimal.ONE);
/** /**
* The number zero. * The number zero.
*/ */
static final PreciseNumber ZERO = new PreciseNumber(BigDecimal.ZERO); public static final PreciseNumber ZERO = new PreciseNumber(BigDecimal.ZERO);
/** /**
* The number ten. * The number ten.
*/ */
static final PreciseNumber TEN = new PreciseNumber(BigDecimal.TEN); public static final PreciseNumber TEN = new PreciseNumber(BigDecimal.TEN);
/** /**
* The value of the PreciseNumber. * The value of the PreciseNumber.
@@ -44,31 +48,31 @@ public class PreciseNumber implements NumberInterface {
@Override @Override
public int getMaxPrecision() { public int getMaxPrecision() {
return 54; return 65;
} }
@Override @Override
public NumberInterface multiply(NumberInterface multiplier) { public NumberInterface multiplyInternal(NumberInterface multiplier) {
return new PreciseNumber(value.multiply(((PreciseNumber) multiplier).value)); return new PreciseNumber(this.value.multiply(((PreciseNumber) multiplier).value));
} }
@Override @Override
public NumberInterface divide(NumberInterface divisor) { public NumberInterface divideInternal(NumberInterface divisor) {
return new PreciseNumber(value.divide(((PreciseNumber) divisor).value, this.getMaxPrecision(), RoundingMode.HALF_UP)); return new PreciseNumber(value.divide(((PreciseNumber) divisor).value, this.getMaxPrecision(), RoundingMode.HALF_UP));
} }
@Override @Override
public NumberInterface add(NumberInterface summand) { public NumberInterface addInternal(NumberInterface summand) {
return new PreciseNumber(value.add(((PreciseNumber) summand).value)); return new PreciseNumber(value.add(((PreciseNumber) summand).value));
} }
@Override @Override
public NumberInterface subtract(NumberInterface subtrahend) { public NumberInterface subtractInternal(NumberInterface subtrahend) {
return new PreciseNumber(value.subtract(((PreciseNumber) subtrahend).value)); return new PreciseNumber(value.subtract(((PreciseNumber) subtrahend).value));
} }
@Override @Override
public NumberInterface intPow(int exponent) { public NumberInterface intPowInternal(int exponent) {
if (exponent == 0) { if (exponent == 0) {
return PreciseNumber.ONE; return PreciseNumber.ONE;
} }
@@ -95,12 +99,47 @@ public class PreciseNumber implements NumberInterface {
} }
@Override @Override
public NumberInterface negate() { public NumberInterface ceilingInternal() {
String str = value.toPlainString();
int decimalIndex = str.indexOf('.');
if (decimalIndex != -1) {
return this.floor().add(ONE);
}
return this;
}
@Override
public NumberInterface floorInternal() {
String str = value.toPlainString();
int decimalIndex = str.indexOf('.');
if (decimalIndex != -1) {
return new PreciseNumber(str.substring(0, decimalIndex));
}
return this;
}
@Override
public NumberInterface fractionalPartInternal() {
String str = value.toPlainString();
int decimalIndex = str.indexOf('.');
if (decimalIndex != -1) {
return new PreciseNumber(str.substring(decimalIndex + 1));
}
return ZERO;
}
@Override
public int intValue() {
return value.intValue();
}
@Override
public NumberInterface negateInternal() {
return new PreciseNumber(value.negate()); return new PreciseNumber(value.negate());
} }
@Override @Override
public NumberInterface promoteTo(Class<? extends NumberInterface> toClass) { public NumberInterface promoteToInternal(Class<? extends NumberInterface> toClass) {
if (toClass == this.getClass()) { if (toClass == this.getClass()) {
return this; return this;
} }
@@ -109,7 +148,7 @@ public class PreciseNumber implements NumberInterface {
@Override @Override
public String toString() { public String toString() {
BigDecimal rounded = value.setScale(getMaxPrecision() - 4, RoundingMode.HALF_UP); BigDecimal rounded = value.setScale(getMaxPrecision() - 15, RoundingMode.HALF_UP);
return rounded.stripTrailingZeros().toPlainString(); return rounded.stripTrailingZeros().toPlainString();
} }
} }

View File

@@ -55,9 +55,12 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
public List<Match<TokenType>> intoPostfix(List<Match<TokenType>> from) { public List<Match<TokenType>> intoPostfix(List<Match<TokenType>> from) {
ArrayList<Match<TokenType>> output = new ArrayList<>(); ArrayList<Match<TokenType>> output = new ArrayList<>();
Stack<Match<TokenType>> tokenStack = new Stack<>(); Stack<Match<TokenType>> tokenStack = new Stack<>();
TokenType previousType;
TokenType matchType = null;
while (!from.isEmpty()) { while (!from.isEmpty()) {
Match<TokenType> match = from.remove(0); Match<TokenType> match = from.remove(0);
TokenType matchType = match.getType(); previousType = matchType;
matchType = match.getType();
if (matchType == TokenType.NUM) { if (matchType == TokenType.NUM) {
output.add(match); output.add(match);
} else if (matchType == TokenType.FUNCTION) { } else if (matchType == TokenType.FUNCTION) {
@@ -74,13 +77,19 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
continue; continue;
} }
while (!tokenStack.empty()) { if (tokenString.equals("-") && (previousType == null || previousType == TokenType.OP ||
previousType == TokenType.OPEN_PARENTH)) {
from.add(0, new Match<>("`", TokenType.OP));
continue;
}
while (!tokenStack.empty() && type == OperatorType.BINARY_INFIX) {
Match<TokenType> otherMatch = tokenStack.peek(); Match<TokenType> otherMatch = tokenStack.peek();
TokenType otherMatchType = otherMatch.getType(); TokenType otherMatchType = otherMatch.getType();
if (!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break; if (!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break;
if (otherMatchType == TokenType.OP) { if (otherMatchType == TokenType.OP) {
int otherPrecedence = precedenceMap.get(match.getContent()); int otherPrecedence = precedenceMap.get(otherMatch.getContent());
if (otherPrecedence < precedence || if (otherPrecedence < precedence ||
(associativity == OperatorAssociativity.RIGHT && otherPrecedence == precedence)) { (associativity == OperatorAssociativity.RIGHT && otherPrecedence == precedence)) {
break; break;
@@ -103,8 +112,8 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
} }
while (!tokenStack.empty()) { while (!tokenStack.empty()) {
Match<TokenType> match = tokenStack.peek(); Match<TokenType> match = tokenStack.peek();
TokenType matchType = match.getType(); TokenType newMatchType = match.getType();
if (!(matchType == TokenType.OP || matchType == TokenType.FUNCTION)) return null; if (!(newMatchType == TokenType.OP || newMatchType == TokenType.FUNCTION)) return null;
output.add(tokenStack.pop()); output.add(tokenStack.pop());
} }
return output; return output;
@@ -127,11 +136,11 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
TreeNode right = constructRecursive(matches); TreeNode right = constructRecursive(matches);
TreeNode left = constructRecursive(matches); TreeNode left = constructRecursive(matches);
if (left == null || right == null) return null; if (left == null || right == null) return null;
else return new BinaryInfixNode(operator, left, right); else return new BinaryNode(operator, left, right);
} else { } else {
TreeNode applyTo = constructRecursive(matches); TreeNode applyTo = constructRecursive(matches);
if (applyTo == null) return null; if (applyTo == null) return null;
else return new UnaryPrefixNode(operator, applyTo); else return new UnaryNode(operator, applyTo);
} }
} else if (matchType == TokenType.NUM) { } else if (matchType == TokenType.NUM) {
return new NumberNode(abacus.numberFromString(match.getContent())); return new NumberNode(abacus.numberFromString(match.getContent()));
@@ -153,8 +162,10 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
@Override @Override
public TreeNode constructTree(List<Match<TokenType>> tokens) { public TreeNode constructTree(List<Match<TokenType>> tokens) {
tokens = intoPostfix(new ArrayList<>(tokens)); tokens = intoPostfix(new ArrayList<>(tokens));
if(tokens == null) return null;
Collections.reverse(tokens); Collections.reverse(tokens);
return constructRecursive(tokens); TreeNode constructedTree = constructRecursive(tokens);
return tokens.size() == 0 ? constructedTree : null;
} }
@Override @Override

View File

@@ -0,0 +1,81 @@
package org.nwapw.abacus.plugin;
import org.nwapw.abacus.number.NumberInterface;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* A class that holds data about a number implementation.
*/
public abstract class NumberImplementation {
/**
* The list of paths through which this implementation can be promoted.
*/
protected Map<Class<? extends NumberInterface>, Function<NumberInterface, NumberInterface>> promotionPaths;
/**
* The implementation class for this implementation.
*/
private Class<? extends NumberInterface> implementation;
/**
* The priority of converting into this number implementation.
*/
private int priority;
/**
* Creates a new number implementation with the given data.
*
* @param implementation the implementation class.
* @param priority the priority, higher means more likely to be converted into.
*/
public NumberImplementation(Class<? extends NumberInterface> implementation, int priority) {
this.implementation = implementation;
this.priority = priority;
promotionPaths = new HashMap<>();
}
/**
* Gets the list of all promotion paths this implementation can take.
*
* @return the map of documentation paths.
*/
public final Map<Class<? extends NumberInterface>, Function<NumberInterface, NumberInterface>> getPromotionPaths() {
return promotionPaths;
}
/**
* Gets the implementation class used by this implementation.
*
* @return the implementation class.
*/
public final Class<? extends NumberInterface> getImplementation() {
return implementation;
}
/**
* Gets the priority of this number implementation.
*
* @return the priority.
*/
public final int getPriority() {
return priority;
}
/**
* Abstract function to create a new instance from a string.
*
* @param string the string to create a number from.
* @return the resulting number.
*/
public abstract NumberInterface instanceForString(String string);
/**
* Get the instance of pi with the given implementation.
*
* @return pi
*/
public abstract NumberInterface instanceForPi();
}

View File

@@ -26,9 +26,9 @@ public abstract class Plugin {
*/ */
private Map<String, Operator> operators; private Map<String, Operator> operators;
/** /**
* A hash map of operators mapped to their string names. * The map of the number implementations this plugin provides.
*/ */
private Map<String, Class<? extends NumberInterface>> numbers; private Map<String, NumberImplementation> numberImplementations;
/** /**
* The plugin manager in which to search for functions * The plugin manager in which to search for functions
* not inside this package, * not inside this package,
@@ -51,7 +51,7 @@ public abstract class Plugin {
this.manager = manager; this.manager = manager;
functions = new HashMap<>(); functions = new HashMap<>();
operators = new HashMap<>(); operators = new HashMap<>();
numbers = new HashMap<>(); numberImplementations = new HashMap<>();
enabled = false; enabled = false;
} }
@@ -74,12 +74,12 @@ public abstract class Plugin {
} }
/** /**
* Gets the list of all numbers provided by this plugin. * Gets the list of number implementations provided by this plugin.
* *
* @return the list of registered numbers. * @return the list of registered number implementations.
*/ */
public final Set<String> providedNumbers() { public final Set<String> providedNumberImplementations() {
return numbers.keySet(); return numberImplementations.keySet();
} }
/** /**
@@ -103,13 +103,13 @@ public abstract class Plugin {
} }
/** /**
* Gets the class under the given name. * Gets the number implementation under the given name.
* *
* @param numberName the name of the class. * @param name the name of the number implementation to look up.
* @return the class, or null if the plugin doesn't provide it. * @return the number implementation associated with that name, or null if the plugin doesn't provide it.
*/ */
public final Class<? extends NumberInterface> getNumber(String numberName) { public final NumberImplementation getNumberImplementation(String name) {
return numbers.get(numberName); return numberImplementations.get(name);
} }
/** /**
@@ -158,16 +158,14 @@ public abstract class Plugin {
} }
/** /**
* To be used in load(). Registers a number class * To be used in load(). Registers a new number implementation with the plugin.
* with the plugin internally, which makes it possible * This makes it accessible to the plugin manager.
* for the user to select it as an "implementation" for the
* number that they would like to use.
* *
* @param name the name to register it under. * @param name the name of the implementation.
* @param toRegister the class to register. * @param implementation the actual implementation class to register.
*/ */
protected final void registerNumber(String name, Class<? extends NumberInterface> toRegister) { protected final void registerNumberImplementation(String name, NumberImplementation implementation) {
numbers.put(name, toRegister); numberImplementations.put(name, implementation);
} }
/** /**
@@ -194,6 +192,31 @@ public abstract class Plugin {
return manager.operatorFor(name); return manager.operatorFor(name);
} }
/**
* Searches the PluginManager for the given number implementation
* name. This can be used by the plugins internally in order to find
* implementations that they do not provide.
*
* @param name the name for which to search.
* @return the resulting number implementation, or null if none was found.
*/
protected final NumberImplementation numberImplementationFor(String name) {
return manager.numberImplementationFor(name);
}
/**
* Searches the plugin manager for a Pi value for the given number implementation.
* This is done so that number implementations with various degrees of precision
* can provide their own pi values, without losing said precision by
* promoting NaiveNumbers.
*
* @param forClass the class to which to find the pi instance.
* @return the pi value for the given class.
*/
protected final NumberInterface getPi(Class<? extends NumberInterface> forClass) {
return manager.piFor(forClass);
}
/** /**
* Abstract method to be overridden by plugin implementation, in which the plugins * Abstract method to be overridden by plugin implementation, in which the plugins
* are supposed to register the functions they provide and do any other * are supposed to register the functions they provide and do any other

View File

@@ -1,5 +1,6 @@
package org.nwapw.abacus.plugin; package org.nwapw.abacus.plugin;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.Function; import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.function.Operator; import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
@@ -32,10 +33,19 @@ public class PluginManager {
*/ */
private Map<String, Operator> cachedOperators; private Map<String, Operator> cachedOperators;
/** /**
* List of registered number implementations that have * The list of number implementations that have
* been cached, that is, found in a plugin and returned. * been cached, that is, found in a plugin and returned.
*/ */
private Map<String, Class<? extends NumberInterface>> cachedNumbers; private Map<String, NumberImplementation> cachedNumberImplementations;
/**
* The list of number implementations that have been
* found by their implementation class.
*/
private Map<Class<? extends NumberInterface>, NumberImplementation> cachedInterfaceImplementations;
/**
* The pi values for each implementation class that have already been computer.
*/
private Map<Class<? extends NumberInterface>, NumberInterface> cachedPi;
/** /**
* List of all functions loaded by the plugins. * List of all functions loaded by the plugins.
*/ */
@@ -45,26 +55,36 @@ public class PluginManager {
*/ */
private Set<String> allOperators; private Set<String> allOperators;
/** /**
* List of all numbers loaded by the plugins. * List of all the number implementations loaded by the plugins.
*/ */
private Set<String> allNumbers; private Set<String> allNumberImplementations;
/** /**
* The list of plugin listeners attached to this instance. * The list of plugin listeners attached to this instance.
*/ */
private Set<PluginListener> listeners; private Set<PluginListener> listeners;
/**
* The abacus instance used to access other
* components of the application.
*/
private Abacus abacus;
/** /**
* Creates a new plugin manager. * Creates a new plugin manager.
*
* @param abacus the abacus instance.
*/ */
public PluginManager() { public PluginManager(Abacus abacus) {
this.abacus = abacus;
loadedPluginClasses = new HashSet<>(); loadedPluginClasses = new HashSet<>();
plugins = new HashSet<>(); plugins = new HashSet<>();
cachedFunctions = new HashMap<>(); cachedFunctions = new HashMap<>();
cachedOperators = new HashMap<>(); cachedOperators = new HashMap<>();
cachedNumbers = new HashMap<>(); cachedNumberImplementations = new HashMap<>();
cachedInterfaceImplementations = new HashMap<>();
cachedPi = new HashMap<>();
allFunctions = new HashSet<>(); allFunctions = new HashSet<>();
allOperators = new HashSet<>(); allOperators = new HashSet<>();
allNumbers = new HashSet<>(); allNumberImplementations = new HashSet<>();
listeners = new HashSet<>(); listeners = new HashSet<>();
} }
@@ -80,12 +100,13 @@ public class PluginManager {
* @param getFunction the function to get the T value under the given name * @param getFunction the function to get the T value under the given name
* @param name the name to search for * @param name the name to search for
* @param <T> the type of element being search * @param <T> the type of element being search
* @param <K> the type of key that the cache is indexed by.
* @return the retrieved element, or null if it was not found. * @return the retrieved element, or null if it was not found.
*/ */
private static <T> T searchCached(Collection<Plugin> plugins, Map<String, T> cache, private static <T, K> T searchCached(Collection<Plugin> plugins, Map<K, T> cache,
java.util.function.Function<Plugin, Set<String>> setFunction, java.util.function.Function<Plugin, Set<K>> setFunction,
java.util.function.BiFunction<Plugin, String, T> getFunction, java.util.function.BiFunction<Plugin, K, T> getFunction,
String name) { K name) {
if (cache.containsKey(name)) return cache.get(name); if (cache.containsKey(name)) return cache.get(name);
T loadedValue = null; T loadedValue = null;
@@ -121,13 +142,54 @@ public class PluginManager {
} }
/** /**
* Gets a numer implementation under the given name. * Gets the number implementation under the given name.
* *
* @param name the name of the implementation. * @param name the name of the implementation.
* @return the implementation class * @return the implementation.
*/ */
public Class<? extends NumberInterface> numberFor(String name) { public NumberImplementation numberImplementationFor(String name) {
return searchCached(plugins, cachedNumbers, Plugin::providedNumbers, Plugin::getNumber, name); return searchCached(plugins, cachedNumberImplementations, Plugin::providedNumberImplementations,
Plugin::getNumberImplementation, name);
}
/**
* Gets the number implementation for the given implementation class.
*
* @param name the class for which to find the implementation.
* @return the implementation.
*/
public NumberImplementation interfaceImplementationFor(Class<? extends NumberInterface> name) {
if (cachedInterfaceImplementations.containsKey(name)) return cachedInterfaceImplementations.get(name);
NumberImplementation toReturn = null;
outside:
for (Plugin plugin : plugins) {
for (String implementationName : plugin.providedNumberImplementations()) {
NumberImplementation implementation = plugin.getNumberImplementation(implementationName);
if (implementation.getImplementation().equals(name)) {
toReturn = implementation;
break outside;
}
}
}
cachedInterfaceImplementations.put(name, toReturn);
return toReturn;
}
/**
* Gets the mathematical constant pi for the given implementation class.
*
* @param forClass the class for which to find pi.
* @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;
} }
/** /**
@@ -160,11 +222,16 @@ public class PluginManager {
* Loads all the plugins in the PluginManager. * Loads all the plugins in the PluginManager.
*/ */
public void load() { public void load() {
for (Plugin plugin : plugins) plugin.enable(); Set<String> disabledPlugins = abacus.getConfiguration().getDisabledPlugins();
for (Plugin plugin : plugins) { for (Plugin plugin : plugins) {
if (disabledPlugins.contains(plugin.getClass().getName())) continue;
plugin.enable();
}
for (Plugin plugin : plugins) {
if (disabledPlugins.contains(plugin.getClass().getName())) continue;
allFunctions.addAll(plugin.providedFunctions()); allFunctions.addAll(plugin.providedFunctions());
allOperators.addAll(plugin.providedOperators()); allOperators.addAll(plugin.providedOperators());
allNumbers.addAll(plugin.providedNumbers()); allNumberImplementations.addAll(plugin.providedNumberImplementations());
} }
listeners.forEach(e -> e.onLoad(this)); listeners.forEach(e -> e.onLoad(this));
} }
@@ -173,10 +240,20 @@ public class PluginManager {
* Unloads all the plugins in the PluginManager. * Unloads all the plugins in the PluginManager.
*/ */
public void unload() { public void unload() {
for (Plugin plugin : plugins) plugin.disable(); listeners.forEach(e -> e.onUnload(this));
Set<String> disabledPlugins = abacus.getConfiguration().getDisabledPlugins();
for (Plugin plugin : plugins) {
if (disabledPlugins.contains(plugin.getClass().getName())) continue;
plugin.disable();
}
cachedFunctions.clear();
cachedOperators.clear();
cachedNumberImplementations.clear();
cachedInterfaceImplementations.clear();
cachedPi.clear();
allFunctions.clear(); allFunctions.clear();
allOperators.clear(); allOperators.clear();
allNumbers.clear(); allNumberImplementations.clear();
listeners.forEach(e -> e.onUnload(this)); listeners.forEach(e -> e.onUnload(this));
} }
@@ -185,7 +262,7 @@ public class PluginManager {
*/ */
public void reload() { public void reload() {
unload(); unload();
reload(); load();
} }
/** /**
@@ -207,12 +284,12 @@ public class PluginManager {
} }
/** /**
* Gets all the number implementations loaded by the Plugin Manager * Gets all the number implementations loaded by the Plugin Manager.
* *
* @return the set of all implementations that were loaded * @return the set of all implementations that were loaded.
*/ */
public Set<String> getAllNumbers() { public Set<String> getAllNumberImplementations() {
return allNumbers; return allNumberImplementations;
} }
/** /**
@@ -233,4 +310,13 @@ public class PluginManager {
listeners.remove(listener); listeners.remove(listener);
} }
/**
* Gets a list of all the plugin class files that have been
* added to the plugin manager.
*
* @return the list of all the added plugin classes.
*/
public Set<Class<?>> getLoadedPluginClasses() {
return loadedPluginClasses;
}
} }

View File

@@ -8,6 +8,8 @@ import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber; import org.nwapw.abacus.number.PreciseNumber;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.function.BiFunction; import java.util.function.BiFunction;
/** /**
@@ -46,6 +48,21 @@ public class StandardPlugin extends Plugin {
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].subtract(params[1]); 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() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].negate();
} }
}); });
/** /**
@@ -72,16 +89,12 @@ public class StandardPlugin extends Plugin {
public static final Operator OP_DIVIDE = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() { public static final Operator OP_DIVIDE = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() {
@Override @Override
protected boolean matchesParams(NumberInterface[] params) { protected boolean matchesParams(NumberInterface[] params) {
return params.length >= 1; return params.length == 2 && params[1].compareTo(NaiveNumber.ZERO.promoteTo(params[1].getClass())) != 0;
} }
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface product = params[0]; return params[0].divide(params[1]);
for (int i = 1; i < params.length; i++) {
product = product.multiply(params[i]);
}
return product;
} }
}); });
/** /**
@@ -91,7 +104,9 @@ public class StandardPlugin extends Plugin {
//private HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> storedList = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>(); //private HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> storedList = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>();
@Override @Override
protected boolean matchesParams(NumberInterface[] params) { protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1; return params.length == 1
&& params[0].fractionalPart().compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0
&& params[0].signum() >= 0;
} }
@Override @Override
@@ -113,20 +128,6 @@ public class StandardPlugin extends Plugin {
}*/ }*/
} }
}); });
/**
* The caret / pow operator, ^
*/
public static final Operator OP_CARET = new Operator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return FUNCTION_EXP.apply(FUNCTION_LN.apply(params[0]).multiply(params[1]));
}
});
/** /**
* The absolute value function, abs(-3) = 3 * The absolute value function, abs(-3) = 3
*/ */
@@ -142,39 +143,19 @@ public class StandardPlugin extends Plugin {
} }
}; };
/** /**
* The exponential function, exp(1) = e^1 = 2.71... * The natural log function.
*/
public static final Function FUNCTION_EXP = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
boolean takeReciprocal = params[0].signum() == -1;
params[0] = FUNCTION_ABS.apply(params[0]);
NumberInterface sum = sumSeries(params[0], StandardPlugin::getExpSeriesTerm, getNTermsExp(getMaxError(params[0]), params[0]));
if (takeReciprocal) {
sum = NaiveNumber.ONE.promoteTo(sum.getClass()).divide(sum);
}
return sum;
}
};
/**
* The natural log function, ln(exp(1)) = 1
*/ */
public static final Function FUNCTION_LN = new Function() { public static final Function FUNCTION_LN = new Function() {
@Override @Override
protected boolean matchesParams(NumberInterface[] params) { protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1; return params.length == 1 && params[0].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) > 0;
} }
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface param = params[0]; NumberInterface param = params[0];
int powersOf2 = 0; int powersOf2 = 0;
while (FUNCTION_ABS.apply(param.subtract(NaiveNumber.ONE.promoteTo(param.getClass()))).compareTo((new NaiveNumber(0.1)).promoteTo(param.getClass())) >= 0) { while (FUNCTION_ABS.apply(param.subtract(NaiveNumber.ONE.promoteTo(param.getClass()))).compareTo(new NaiveNumber(0.1).promoteTo(param.getClass())) >= 0) {
if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() == 1) { if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() == 1) {
param = param.divide(new NaiveNumber(2).promoteTo(param.getClass())); param = param.divide(new NaiveNumber(2).promoteTo(param.getClass()));
powersOf2++; powersOf2++;
@@ -185,7 +166,7 @@ public class StandardPlugin extends Plugin {
} else { } else {
param = param.multiply(new NaiveNumber(2).promoteTo(param.getClass())); param = param.multiply(new NaiveNumber(2).promoteTo(param.getClass()));
powersOf2--; powersOf2--;
if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() != 1) { if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() != -1) {
break; break;
//No infinite loop for you. //No infinite loop for you.
} }
@@ -201,13 +182,15 @@ public class StandardPlugin extends Plugin {
* @return the partial sum. * @return the partial sum.
*/ */
private NumberInterface getLogPartialSum(NumberInterface x) { private NumberInterface getLogPartialSum(NumberInterface x) {
NumberInterface maxError = getMaxError(x); NumberInterface maxError = getMaxError(x);
x = x.subtract(NaiveNumber.ONE.promoteTo(x.getClass())); //Terms used are for log(x+1). x = x.subtract(NaiveNumber.ONE.promoteTo(x.getClass())); //Terms used are for log(x+1).
NumberInterface currentTerm = x, sum = x; NumberInterface currentNumerator = x, currentTerm = x, sum = x;
int n = 1; int n = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) { while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) {
n++; n++;
currentTerm = currentTerm.multiply(x).multiply((new NaiveNumber(n - 1)).promoteTo(x.getClass())).divide((new NaiveNumber(n)).promoteTo(x.getClass())).negate(); currentNumerator = currentNumerator.multiply(x).negate();
currentTerm = currentNumerator.divide(new NaiveNumber(n).promoteTo(x.getClass()));
sum = sum.add(currentTerm); sum = sum.add(currentTerm);
} }
return sum; return sum;
@@ -238,7 +221,7 @@ public class StandardPlugin extends Plugin {
} }
}; };
/** /**
* The square root function, sqrt(4) = 2 * The square root function.
*/ */
public static final Function FUNCTION_SQRT = new Function() { public static final Function FUNCTION_SQRT = new Function() {
@Override @Override
@@ -251,44 +234,219 @@ public class StandardPlugin extends Plugin {
return OP_CARET.getFunction().apply(params[0], ((new NaiveNumber(0.5)).promoteTo(params[0].getClass()))); return OP_CARET.getFunction().apply(params[0], ((new NaiveNumber(0.5)).promoteTo(params[0].getClass())));
} }
}; };
/**
* The implementation for double-based naive numbers.
*/
public static final NumberImplementation IMPLEMENTATION_NAIVE = new NumberImplementation(NaiveNumber.class, 0) {
@Override
public NumberInterface instanceForString(String string) {
return new NaiveNumber(string);
}
@Override
public NumberInterface instanceForPi() {
return new NaiveNumber(Math.PI);
}
};
/**
* The implementation for the infinite-precision BigDecimal.
*/
public static final NumberImplementation IMPLEMENTATION_PRECISE = new NumberImplementation(PreciseNumber.class, 0) {
@Override
public NumberInterface instanceForString(String string) {
return new PreciseNumber(string);
}
@Override
public NumberInterface instanceForPi() {
NumberInterface C = FUNCTION_SQRT.apply(new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
NumberInterface M = PreciseNumber.ONE;
NumberInterface L = new PreciseNumber("13591409");
NumberInterface X = M;
NumberInterface sum = L;
int termsNeeded = C.getMaxPrecision() / 13 + 1;
NumberInterface lSummand = new PreciseNumber("545140134");
NumberInterface xMultiplier = new PreciseNumber("262537412")
.multiply(new PreciseNumber("1000000000"))
.add(new PreciseNumber("640768000"))
.negate();
for (int i = 0; i < termsNeeded; i++) {
M = M
.multiply(new NaiveNumber(12 * i + 2).promoteTo(PreciseNumber.class))
.multiply(new NaiveNumber(12 * i + 6).promoteTo(PreciseNumber.class))
.multiply(new NaiveNumber(12 * i + 10).promoteTo(PreciseNumber.class))
.divide(new NaiveNumber(Math.pow(i + 1, 3)).promoteTo(PreciseNumber.class));
L = L.add(lSummand);
X = X.multiply(xMultiplier);
sum = sum.add(M.multiply(L).divide(X));
}
return C.divide(sum);
}
};
private static final HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
/**
* The exponential function, exp(1) = e^1 = 2.71...
*/
public static final Function FUNCTION_EXP = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface maxError = getMaxError(params[0]);
int n = 0;
if (params[0].signum() <= 0) {
NumberInterface currentTerm = NaiveNumber.ONE.promoteTo(params[0].getClass()), sum = currentTerm;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) {
n++;
currentTerm = currentTerm.multiply(params[0]).divide((new NaiveNumber(n)).promoteTo(params[0].getClass()));
sum = sum.add(currentTerm);
}
return sum;
} 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 = NaiveNumber.ONE.promoteTo(params[0].getClass());
NumberInterface nextNumerator = params[0];
NumberInterface left = params[0].multiply((new NaiveNumber(3)).promoteTo(params[0].getClass()).intPow(params[0].ceiling().intValue())), right = maxError;
do {
sum = sum.add(nextNumerator.divide(factorial(params[0].getClass(), n + 1)));
n++;
nextNumerator = nextNumerator.multiply(params[0]);
left = left.multiply(params[0]);
NumberInterface nextN = (new NaiveNumber(n + 1)).promoteTo(params[0].getClass());
right = right.multiply(nextN);
//System.out.println(left + ", " + right);
}
while (left.compareTo(right) > 0);
//System.out.println(n+1);
return sum;
}
}
};
/**
* The caret / pow operator, ^
*/
public static final Operator OP_CARET = new Operator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2
&& !(params[0].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0
&& params[1].compareTo(NaiveNumber.ZERO.promoteTo(params[1].getClass())) == 0);
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
if (params[0].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0)
return NaiveNumber.ZERO.promoteTo(params[0].getClass());
else if (params[1].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0)
return NaiveNumber.ONE.promoteTo(params[1].getClass());
return FUNCTION_EXP.apply(FUNCTION_LN.apply(FUNCTION_ABS.apply(params[0])).multiply(params[1]));
}
});
/**
* The sine function (the argument is interpreted in radians).
*/
public final Function functionSin = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface pi = getPi(params[0].getClass());
NumberInterface twoPi = pi.multiply(new NaiveNumber(2).promoteTo(pi.getClass()));
NumberInterface theta = getSmallAngle(params[0], pi);
//System.out.println(theta);
if (theta.compareTo(pi.multiply(new NaiveNumber(1.5).promoteTo(twoPi.getClass()))) >= 0) {
theta = theta.subtract(twoPi);
} else if (theta.compareTo(pi.divide(new NaiveNumber(2).promoteTo(pi.getClass()))) > 0) {
theta = pi.subtract(theta);
}
//System.out.println(theta);
return sinTaylor(theta);
}
};
/**
* The cosine function (the argument is in radians).
*/
public final Function functionCos = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(getPi(params[0].getClass()).divide(new NaiveNumber(2).promoteTo(params[0].getClass()))
.subtract(params[0]));
}
};
/**
* The tangent function (the argument is in radians).
*/
public final Function functionTan = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(params[0]).divide(functionCos.apply(params[0]));
}
};
/**
* The secant function (the argument is in radians).
*/
public final Function functionSec = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return NaiveNumber.ONE.promoteTo(params[0].getClass()).divide(functionCos.apply(params[0]));
}
};
/**
* The cosecant function (the argument is in radians).
*/
public final Function functionCsc = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return NaiveNumber.ONE.promoteTo(params[0].getClass()).divide(functionSin.apply(params[0]));
}
};
/**
* The cotangent function (the argument is in radians).
*/
public final Function functionCot = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionCos.apply(params[0]).divide(functionSin.apply(params[0]));
}
};
public StandardPlugin(PluginManager manager) { public StandardPlugin(PluginManager manager) {
super(manager); super(manager);
} }
/**
* Returns the nth term of the Taylor series (centered at 0) of e^x
*
* @param n the term required (n >= 0).
* @param x the real number at which the series is evaluated.
* @return the nth term of the series.
*/
private static NumberInterface getExpSeriesTerm(int n, NumberInterface x) {
return x.intPow(n).divide(OP_FACTORIAL.getFunction().apply((new NaiveNumber(n)).promoteTo(x.getClass())));
}
/**
* Returns the number of terms needed to evaluate the exponential function (at x)
* such that the error is at most maxError.
*
* @param maxError Maximum error permissible (This should probably be positive.)
* @param x where the function is evaluated.
* @return the number of terms needed to evaluated the exponential function.
*/
private static int getNTermsExp(NumberInterface maxError, NumberInterface x) {
//We need n such that |x^(n+1)| <= (n+1)! * maxError
//The variables LHS and RHS refer to the above inequality.
int n = 0;
x = FUNCTION_ABS.apply(x);
NumberInterface LHS = x, RHS = maxError;
while (LHS.compareTo(RHS) > 0) {
n++;
LHS = LHS.multiply(x);
RHS = RHS.multiply(new NaiveNumber(n + 1).promoteTo(RHS.getClass()));
}
return n;
}
/** /**
* Returns a partial sum of a series whose terms are given by the nthTermFunction, evaluated at x. * Returns a partial sum of a series whose terms are given by the nthTermFunction, evaluated at x.
* *
@@ -315,13 +473,72 @@ public class StandardPlugin extends Plugin {
return (new NaiveNumber(10)).promoteTo(number.getClass()).intPow(-number.getMaxPrecision()); return (new NaiveNumber(10)).promoteTo(number.getClass()).intPow(-number.getMaxPrecision());
} }
/**
* 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.
* @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(NaiveNumber.ONE.promoteTo(numberClass));
FACTORIAL_LISTS.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass));
}
ArrayList<NumberInterface> list = FACTORIAL_LISTS.get(numberClass);
if (n >= list.size()) {
while (list.size() < n + 16) {
list.add(list.get(list.size() - 1).multiply(new NaiveNumber(list.size()).promoteTo(numberClass)));
}
}
return list.get(n);
}
/**
* Returns the value of the Taylor series for sin (centered at 0) at x.
*
* @param x where the series is evaluated.
* @return the value of the series
*/
private static NumberInterface sinTaylor(NumberInterface x) {
NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x;
NumberInterface maxError = getMaxError(x);
int n = 1;
do {
n += 2;
power = power.multiply(multiplier);
currentTerm = power.divide(factorial(x.getClass(), n));
sum = sum.add(currentTerm);
} while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0);
return sum;
}
/**
* Returns an equivalent angle in the interval [0, 2pi)
*
* @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(new NaiveNumber("2").promoteTo(phi.getClass()));
NumberInterface theta = FUNCTION_ABS.apply(phi).subtract(twoPi
.multiply(FUNCTION_ABS.apply(phi).divide(twoPi).floor())); //Now theta is in [0, 2pi).
if (phi.signum() < 0) {
theta = twoPi.subtract(theta);
}
return theta;
}
@Override @Override
public void onEnable() { public void onEnable() {
registerNumber("naive", NaiveNumber.class); registerNumberImplementation("naive", IMPLEMENTATION_NAIVE);
registerNumber("precise", PreciseNumber.class); registerNumberImplementation("precise", IMPLEMENTATION_PRECISE);
registerOperator("+", OP_ADD); registerOperator("+", OP_ADD);
registerOperator("-", OP_SUBTRACT); registerOperator("-", OP_SUBTRACT);
registerOperator("`", OP_NEGATE);
registerOperator("*", OP_MULTIPLY); registerOperator("*", OP_MULTIPLY);
registerOperator("/", OP_DIVIDE); registerOperator("/", OP_DIVIDE);
registerOperator("^", OP_CARET); registerOperator("^", OP_CARET);
@@ -331,11 +548,16 @@ public class StandardPlugin extends Plugin {
registerFunction("exp", FUNCTION_EXP); registerFunction("exp", FUNCTION_EXP);
registerFunction("ln", FUNCTION_LN); registerFunction("ln", FUNCTION_LN);
registerFunction("sqrt", FUNCTION_SQRT); registerFunction("sqrt", FUNCTION_SQRT);
registerFunction("sin", functionSin);
registerFunction("cos", functionCos);
registerFunction("tan", functionTan);
registerFunction("sec", functionSec);
registerFunction("csc", functionCsc);
registerFunction("cot", functionCot);
} }
@Override @Override
public void onDisable() { public void onDisable() {
} }
} }

View File

@@ -3,7 +3,7 @@ package org.nwapw.abacus.tree;
/** /**
* A tree node that represents an operation being applied to two operands. * A tree node that represents an operation being applied to two operands.
*/ */
public class BinaryInfixNode extends TreeNode { public class BinaryNode extends TreeNode {
/** /**
* The operation being applied. * The operation being applied.
@@ -18,7 +18,7 @@ public class BinaryInfixNode extends TreeNode {
*/ */
private TreeNode right; private TreeNode right;
private BinaryInfixNode() { private BinaryNode() {
} }
/** /**
@@ -27,7 +27,7 @@ public class BinaryInfixNode extends TreeNode {
* *
* @param operation the operation. * @param operation the operation.
*/ */
public BinaryInfixNode(String operation) { public BinaryNode(String operation) {
this(operation, null, null); this(operation, null, null);
} }
@@ -39,7 +39,7 @@ public class BinaryInfixNode extends TreeNode {
* @param left the left node of the expression. * @param left the left node of the expression.
* @param right the right node of the expression. * @param right the right node of the expression.
*/ */
public BinaryInfixNode(String operation, TreeNode left, TreeNode right) { public BinaryNode(String operation, TreeNode left, TreeNode right) {
this.operation = operation; this.operation = operation;
this.left = left; this.left = left;
this.right = right; this.right = right;

View File

@@ -28,15 +28,15 @@ public class NumberReducer implements Reducer<NumberInterface> {
public NumberInterface reduceNode(TreeNode node, Object... children) { public NumberInterface reduceNode(TreeNode node, Object... children) {
if (node instanceof NumberNode) { if (node instanceof NumberNode) {
return ((NumberNode) node).getNumber(); return ((NumberNode) node).getNumber();
} else if (node instanceof BinaryInfixNode) { } else if (node instanceof BinaryNode) {
NumberInterface left = (NumberInterface) children[0]; NumberInterface left = (NumberInterface) children[0];
NumberInterface right = (NumberInterface) children[1]; NumberInterface right = (NumberInterface) children[1];
Function function = abacus.getPluginManager().operatorFor(((BinaryInfixNode) node).getOperation()).getFunction(); Function function = abacus.getPluginManager().operatorFor(((BinaryNode) node).getOperation()).getFunction();
if (function == null) return null; if (function == null) return null;
return function.apply(left, right); return function.apply(left, right);
} else if (node instanceof UnaryPrefixNode) { } else if (node instanceof UnaryNode) {
NumberInterface child = (NumberInterface) children[0]; NumberInterface child = (NumberInterface) children[0];
Function functionn = abacus.getPluginManager().operatorFor(((UnaryPrefixNode) node).getOperation()).getFunction(); Function functionn = abacus.getPluginManager().operatorFor(((UnaryNode) node).getOperation()).getFunction();
if (functionn == null) return null; if (functionn == null) return null;
return functionn.apply(child); return functionn.apply(child);
} else if (node instanceof FunctionNode) { } else if (node instanceof FunctionNode) {

View File

@@ -1,6 +1,6 @@
package org.nwapw.abacus.tree; package org.nwapw.abacus.tree;
public class UnaryPrefixNode extends TreeNode { public class UnaryNode extends TreeNode {
/** /**
* The operation this node will apply. * The operation this node will apply.
@@ -16,7 +16,7 @@ public class UnaryPrefixNode extends TreeNode {
* *
* @param operation the operation for this node. * @param operation the operation for this node.
*/ */
public UnaryPrefixNode(String operation) { public UnaryNode(String operation) {
this(operation, null); this(operation, null);
} }
@@ -26,7 +26,7 @@ public class UnaryPrefixNode extends TreeNode {
* @param operation the operation for this node. * @param operation the operation for this node.
* @param applyTo the node to apply the function to. * @param applyTo the node to apply the function to.
*/ */
public UnaryPrefixNode(String operation, TreeNode applyTo) { public UnaryNode(String operation, TreeNode applyTo) {
this.operation = operation; this.operation = operation;
this.applyTo = applyTo; this.applyTo = applyTo;
} }

View File

@@ -250,6 +250,6 @@ public class Window extends JFrame {
@Override @Override
public void setVisible(boolean b) { public void setVisible(boolean b) {
super.setVisible(b); super.setVisible(b);
if(b) inputField.requestFocusInWindow(); if (b) inputField.requestFocusInWindow();
} }
} }

View File

@@ -2,15 +2,14 @@
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.BorderPane?> <?import javafx.scene.layout.*?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Text?> <?import javafx.scene.text.Text?>
<BorderPane xmlns="http://javafx.com/javafx" <BorderPane xmlns:fx="http://javafx.com/fxml"
xmlns:fx="http://javafx.com/fxml" xmlns="http://javafx.com/javafx"
fx:controller="org.nwapw.abacus.fx.AbacusController"> fx:controller="org.nwapw.abacus.fx.AbacusController">
<center> <center>
<TabPane> <TabPane fx:id="coreTabPane">
<Tab text="Calculator" closable="false"> <Tab fx:id="calculateTab" text="Calculator" closable="false">
<BorderPane> <BorderPane>
<center> <center>
<TableView fx:id="historyTable"> <TableView fx:id="historyTable">
@@ -35,11 +34,32 @@
<TextField fx:id="inputField" onAction="#performCalculation"/> <TextField fx:id="inputField" onAction="#performCalculation"/>
<Button fx:id="inputButton" text="Calculate" maxWidth="Infinity" <Button fx:id="inputButton" text="Calculate" maxWidth="Infinity"
onAction="#performCalculation"/> onAction="#performCalculation"/>
<Button fx:id="stopButton" text="Stop" maxWidth="Infinity"
onAction="#performStop" disable="true"/>
</VBox> </VBox>
</bottom> </bottom>
</BorderPane> </BorderPane>
</Tab> </Tab>
<Tab text="Settings" closable="false"/> <Tab fx:id="settingsTab" text="Settings" closable="false">
<GridPane hgap="10" vgap="10">
<padding>
<Insets left="10" right="10" top="10" bottom="10"/>
</padding>
<Label text="Number Implementation" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
<ComboBox fx:id="numberImplementationBox" GridPane.columnIndex="1" GridPane.rowIndex="0"/>
<ListView fx:id="enabledPluginView"
GridPane.rowIndex="1" GridPane.columnIndex="0"
GridPane.columnSpan="2" maxHeight="100"/>
<Text GridPane.columnIndex="0" GridPane.rowIndex="2" text="Computation Limit"/>
<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="Reload Plugins" onAction="#performReload"/>
<Button text="Apply and Reload" onAction="#performSaveAndReload"/>
</FlowPane>
</GridPane>
</Tab>
</TabPane> </TabPane>
</center> </center>

View File

@@ -0,0 +1,104 @@
package org.nwapw.abacus.tests;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.number.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[]{}));
@BeforeClass
public static void prepareTests(){
abacus.getPluginManager().addInstantiated(new StandardPlugin(abacus.getPluginManager()));
abacus.getPluginManager().load();
}
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);
Assert.assertNotNull(result);
Assert.assertTrue(result.toString().startsWith(output));
}
private void testEvalError(String input, String parseOutput){
TreeNode parsedTree = abacus.parseString(input);
Assert.assertNotNull(parsedTree);
Assert.assertEquals(parsedTree.toString(), parseOutput);
Assert.assertNull(abacus.evaluateTree(parsedTree));
}
@Test
public void testAddition(){
testOutput("9.5+10", "(9.5+10)", "19.5");
}
@Test
public void testSubtraction(){
testOutput("9.5-10", "(9.5-10)", "-0.5");
}
@Test
public void testMultiplication(){
testOutput("9.5*10", "(9.5*10)", "95");
}
@Test
public void testDivision(){
testOutput("9.5/2", "(9.5/2)", "4.75");
}
@Test
public void testNegation(){
testOutput("-9.5", "(9.5)`", "-9.5");
}
@Test
public void testFactorial(){
testOutput("7!", "(7)!", "5040");
}
@Test
public void testAbs(){
testOutput("abs(-1)", "abs((1)`)", "1");
testOutput("abs(1)", "abs(1)", "1");
}
@Test
public void testLn(){
testEvalError("ln(-1)", "ln((1)`)");
testOutput("ln2", "ln(2)", "0.6931471805599453094172321214581765680755");
}
@Test
public void testSqrt(){
testOutput("sqrt0", "sqrt(0)", "0");
testOutput("sqrt4", "sqrt(4)", "2");
testOutput("sqrt2", "sqrt(2)", "1.4142135623730950488016887242096980785696");
}
@Test
public void testExp(){
testOutput("exp0", "exp(0)", "1");
testOutput("exp1", "exp(1)", "2.718281828459045235360287471352662497757247");
testOutput("exp300", "exp(300)", "19424263952412559365842088360176992193662086");
testOutput("exp300", "exp(300)", "19424263952412559365842088360176992193662086");
}
@Test
public void testPow(){
testOutput("0^2", "(0^2)", "0");
testOutput("2^0", "(2^0)", "1");
testOutput("2^1", "(2^1)", "2");
testOutput("2^-1", "(2^(1)`)", "0.5");
testOutput("2^50", "(2^50)", "112589990684262");
}
}

View File

@@ -4,6 +4,7 @@ import org.junit.Assert;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.nwapw.abacus.Abacus; import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.function.Function; import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.function.Operator; import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.function.OperatorAssociativity; import org.nwapw.abacus.function.OperatorAssociativity;
@@ -18,7 +19,7 @@ import java.util.List;
public class TokenizerTests { public class TokenizerTests {
private static Abacus abacus = new Abacus(); private static Abacus abacus = new Abacus(new Configuration(0, "precise", new String[]{}));
private static LexerTokenizer lexerTokenizer = new LexerTokenizer(); private static LexerTokenizer lexerTokenizer = new LexerTokenizer();
private static Function subtractFunction = new Function() { private static Function subtractFunction = new Function() {
@Override @Override