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

Compare commits

...

101 Commits

Author SHA1 Message Date
Arthur Drobot
7d5efa1fe6 Add fromInt function into StandardPlugin and replace existing instantiations and promotions of NaiveNumbers with integer values with fromInt calls. 2017-08-07 10:33:16 -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
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
Arthur Drobot
95845a1585 Add tan, sec, csc, cot in terms of what we currently have. 2017-08-03 10:30:42 -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
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
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
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
7cb04a1222 Switch to the new UI. 2017-08-01 09:53:38 -07:00
0a97eeb442 Resize table columns. 2017-08-01 09:25:13 -07:00
05d0755526 Implement a cell that copies input when clicked, and add it to table. 2017-07-31 22:53:42 -07:00
0b97a935bf Enable cell selection to later allow for data copying. 2017-07-31 22:34:49 -07:00
211e963db0 Populate the history table. 2017-07-31 22:29:44 -07:00
d8145acc8f Implement the history data model. 2017-07-31 17:49:57 -07:00
63b8162a9b Format the fxml. 2017-07-31 17:18:08 -07:00
c655c63233 Link up the evaluation and the UI buttons. 2017-07-31 17:17:56 -07:00
27ff1a47b5 Add the inputs to the calculator tab. 2017-07-31 17:16:26 -07:00
2941252f7d Add a tabbed pane as the main focus of the window. 2017-07-31 16:56:38 -07:00
44ed0199d4 Add initialization code to AbacusController 2017-07-31 16:52:34 -07:00
5b582a7dbe Add FXML loading application. 2017-07-31 16:50:39 -07:00
a02086e791 Create new fxml file and controller for it. 2017-07-31 16:48:04 -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 1577 additions and 523 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,16 @@
package org.nwapw.abacus; package org.nwapw.abacus;
import org.nwapw.abacus.config.ConfigurationObject; import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.number.NaiveNumber; import org.nwapw.abacus.fx.AbacusApplication;
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 org.nwapw.abacus.window.Window;
import javax.swing.*;
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
@@ -26,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
@@ -47,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.
@@ -56,36 +46,23 @@ 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) {
try { AbacusApplication.launch(AbacusApplication.class, args);
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | UnsupportedLookAndFeelException | IllegalAccessException e) {
e.printStackTrace();
}
new Window(new Abacus()).setVisible(true);
} }
/** /**
@@ -121,7 +98,7 @@ public class Abacus {
* *
* @return the configuration object. * @return the configuration object.
*/ */
public ConfigurationObject getConfiguration() { public Configuration getConfiguration() {
return configuration; return configuration;
} }
@@ -154,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,133 @@
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 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 numberImplementation the number implementation, like "naive" or "precise"
* @param disabledPlugins the list of disabled plugins.
*/
public Configuration(String numberImplementation, String[] disabledPlugins) {
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.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;
}
} }

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

@@ -0,0 +1,24 @@
package org.nwapw.abacus.fx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
/**
* The main application class for JavaFX responsible for loading
* and displaying the fxml file.
*/
public class AbacusApplication extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Parent parent = FXMLLoader.load(getClass().getResource("/abacus.fxml"));
Scene mainScene = new Scene(parent, 320, 480);
primaryStage.setScene(mainScene);
primaryStage.setTitle("Abacus");
primaryStage.show();
}
}

View File

@@ -0,0 +1,299 @@
package org.nwapw.abacus.fx;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.text.Text;
import javafx.util.Callback;
import javafx.util.StringConverter;
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.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 java.io.File;
import java.io.IOException;
import java.util.Set;
/**
* 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";
/**
* Constant string that is displayed if the tree could not be reduced.
*/
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
private TableView<HistoryModel> historyTable;
@FXML
private TableColumn<HistoryModel, String> inputColumn;
@FXML
private TableColumn<HistoryModel, String> parsedColumn;
@FXML
private TableColumn<HistoryModel, String> outputColumn;
@FXML
private Text outputText;
@FXML
private TextField inputField;
@FXML
private Button inputButton;
@FXML
private Button stopButton;
@FXML
private ComboBox<String> numberImplementationBox;
@FXML
private ListView<ToggleablePlugin> enabledPluginView;
/**
* The list of history entries, created by the users.
*/
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;
/**
* 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 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 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
public void initialize() {
Callback<TableColumn<HistoryModel, String>, TableCell<HistoryModel, String>> cellFactory =
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);
}
});
historyData = FXCollections.observableArrayList();
historyTable.setItems(historyData);
numberImplementationOptions = FXCollections.observableArrayList();
numberImplementationBox.setItems(numberImplementationOptions);
numberImplementationBox.getSelectionModel().selectedIndexProperty().addListener(e -> changesMade = true);
historyTable.getSelectionModel().setCellSelectionEnabled(true);
enabledPlugins = FXCollections.observableArrayList();
enabledPluginView.setItems(enabledPlugins);
enabledPluginView.setCellFactory(pluginCellFactory);
inputColumn.setCellFactory(cellFactory);
inputColumn.setCellValueFactory(cell -> cell.getValue().inputProperty());
parsedColumn.setCellFactory(cellFactory);
parsedColumn.setCellValueFactory(cell -> cell.getValue().parsedProperty());
outputColumn.setCellFactory(cellFactory);
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();
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
private void performCalculation() {
inputButton.setDisable(true);
stopButton.setDisable(false);
calculationThread = new Thread(CALCULATION_RUNNABLE);
calculationThread.start();
}
@FXML
private void performStop(){
if(calculationThread != null)
calculationThread.interrupt();
}
@FXML
private void performSaveAndReload() {
performSave();
performReload();
changesMade = false;
reloadAlertShown = false;
}
@FXML
private void performReload() {
alertIfApplyNeeded(true);
abacus.getPluginManager().reload();
}
@FXML
private 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());
}
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

@@ -0,0 +1,36 @@
package org.nwapw.abacus.fx;
import javafx.scene.control.TableCell;
import javafx.scene.input.MouseEvent;
import java.awt.*;
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> {
/**
* Creates a new copyable cell.
*/
public CopyableCell() {
addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
if (event.getClickCount() == 2) {
Toolkit.getDefaultToolkit().getSystemClipboard()
.setContents(new StringSelection(getText()), null);
}
});
}
@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
setText((empty || item == null) ? null : item.toString());
setGraphic(null);
}
}

View File

@@ -0,0 +1,97 @@
package org.nwapw.abacus.fx;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* The data model used for storing history entries.
*/
public class HistoryModel {
/**
* The property used for displaying the column
* for the user input.
*/
private final StringProperty input;
/**
* The property used for displaying the column
* that contains the parsed input.
*/
private final StringProperty parsed;
/**
* The property used for displaying the column
* that contains the program output.
*/
private final StringProperty 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.parsed = new SimpleStringProperty();
this.output = new SimpleStringProperty();
this.input.setValue(input);
this.parsed.setValue(parsed);
this.output.setValue(output);
}
/**
* Gets the input property.
*
* @return the input property.
*/
public StringProperty inputProperty() {
return input;
}
/**
* Gets the input.
*
* @return the input.
*/
public String getInput() {
return input.get();
}
/**
* Gets the parsed input property.
*
* @return the parsed input property.
*/
public StringProperty parsedProperty() {
return parsed;
}
/**
* Gets the parsed input.
*
* @return the parsed input.
*/
public String getParsed() {
return parsed.get();
}
/**
* Gets the output property.
*
* @return the output property.
*/
public StringProperty outputProperty() {
return output;
}
/**
* Gets the program output.
*
* @return the output.
*/
public String getOutput() {
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,27 +95,27 @@ public class NaiveNumber implements NumberInterface {
} }
@Override @Override
public NumberInterface ceiling() { public NumberInterface ceilingInternal() {
return new NaiveNumber(Math.ceil(value)); return new NaiveNumber(Math.ceil(value));
} }
@Override @Override
public NumberInterface floor() { public NumberInterface floorInternal() {
return new NaiveNumber(Math.floor(value)); return new NaiveNumber(Math.floor(value));
} }
@Override @Override
public NumberInterface fractionalPart() { public NumberInterface fractionalPartInternal() {
return new NaiveNumber(value - Math.floor(value)); return new NaiveNumber(value - Math.floor(value));
} }
@Override @Override
public int intValue() { public int intValue() {
return (int)value; return (int) value;
} }
@Override @Override
public NumberInterface promoteTo(Class<? extends NumberInterface> toClass) { 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,39 +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. * Returns the least integer greater than or equal to the number.
*
* @return the least integer >= the number, if int can hold the value. * @return the least integer >= the number, if int can hold the value.
*/ */
NumberInterface ceiling(); 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 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. * @return the greatest int >= the number, if int can hold the value.
*/ */
NumberInterface floor(); public final NumberInterface floor(){
checkInterrupted();
return floorInternal();
}
/** /**
* Returns the fractional part of the number. * Returns the fractional part of the number.
*
* @return the fractional part of the number. * @return the fractional part of the number.
*/ */
NumberInterface fractionalPart(); 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, * Returns the integer representation of this number, discarding any fractional part,
* if int can hold the value. * if int can hold the value.
* @return *
* @return the integer value of this number.
*/ */
int intValue(); public abstract int intValue();
/** /**
* Promotes this class to another number class. * Promotes this class to another number class.
@@ -110,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

@@ -1,23 +1,26 @@
package org.nwapw.abacus.number; package org.nwapw.abacus.number;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
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.
@@ -49,27 +52,27 @@ public class PreciseNumber implements NumberInterface {
} }
@Override @Override
public NumberInterface multiply(NumberInterface multiplier) { public NumberInterface multiplyInternal(NumberInterface multiplier) {
return new PreciseNumber(this.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;
} }
@@ -96,30 +99,30 @@ public class PreciseNumber implements NumberInterface {
} }
@Override @Override
public NumberInterface ceiling() { public NumberInterface ceilingInternal() {
String str = value.toPlainString(); String str = value.toPlainString();
int decimalIndex = str.indexOf('.'); int decimalIndex = str.indexOf('.');
if(decimalIndex != -1){ if (decimalIndex != -1) {
return this.floor().add(ONE); return this.floor().add(ONE);
} }
return this; return this;
} }
@Override @Override
public NumberInterface floor() { public NumberInterface floorInternal() {
String str = value.toPlainString(); String str = value.toPlainString();
int decimalIndex = str.indexOf('.'); int decimalIndex = str.indexOf('.');
if(decimalIndex != -1){ if (decimalIndex != -1) {
return new PreciseNumber(str.substring(0, decimalIndex)); return new PreciseNumber(str.substring(0, decimalIndex));
} }
return this; return this;
} }
@Override @Override
public NumberInterface fractionalPart() { public NumberInterface fractionalPartInternal() {
String str = value.toPlainString(); String str = value.toPlainString();
int decimalIndex = str.indexOf('.'); int decimalIndex = str.indexOf('.');
if(decimalIndex != -1){ if (decimalIndex != -1) {
return new PreciseNumber(str.substring(decimalIndex + 1)); return new PreciseNumber(str.substring(decimalIndex + 1));
} }
return ZERO; return ZERO;
@@ -131,12 +134,12 @@ public class PreciseNumber implements NumberInterface {
} }
@Override @Override
public NumberInterface negate() { 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;
} }

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,13 +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;
/**
* A hash map of constant providers for each number type.
*/
private Map<Class<?>, java.util.function.Function<String, NumberInterface>> constantProviders;
/** /**
* 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,
@@ -55,8 +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<>();
constantProviders = new HashMap<>();
enabled = false; enabled = false;
} }
@@ -79,20 +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();
}
/**
* Gets the list of all constant providers provided by this plugin.
* @return the list of constant providers.
*/
public final Set<Class<?>> providedConstantProviders() {
return constantProviders.keySet();
} }
/** /**
@@ -116,23 +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);
}
/**
* Gets the constant provider for the given class.
*
* @param pluginClass the class for which to provide constants.
* @return the provider, or null, if the plugin doesn't provide it.
*/
public final java.util.function.Function<String, NumberInterface> getConstantProvider(Class<?> pluginClass){
return constantProviders.get(pluginClass);
} }
/** /**
@@ -154,8 +131,6 @@ public abstract class Plugin {
onDisable(); onDisable();
functions.clear(); functions.clear();
operators.clear(); operators.clear();
numbers.clear();
constantProviders.clear();
enabled = false; enabled = false;
} }
@@ -183,29 +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);
}
/**
* To be used in load(). Registers a constant provider
* with the plugin internally, which makes it possible
* for the calculations to look up constants for each different
* number type.
* @param providerFor the class the provider works with.
* @param constantProvider the provider to register.
*/
protected final void registerConstantProvider(Class<?> providerFor,
java.util.function.Function<String, NumberInterface> constantProvider) {
constantProviders.put(providerFor, constantProvider);
} }
/** /**
@@ -233,27 +193,28 @@ public abstract class Plugin {
} }
/** /**
* Searches the PluginManager for the given number implementation. * Searches the PluginManager for the given number implementation
* This can be used by the plugins internally in order to * name. This can be used by the plugins internally in order to find
* find classes by name that they do not provide. * implementations that they do not provide.
* *
* @param name the name for which to search * @param name the name for which to search.
* @return the resulting number class. * @return the resulting number implementation, or null if none was found.
*/ */
protected final Class<? extends NumberInterface> numberFor(String name) { protected final NumberImplementation numberImplementationFor(String name) {
return manager.numberFor(name); return manager.numberImplementationFor(name);
} }
/** /**
* Searches the PluginManager for the given constant provider. * Searches the plugin manager for a Pi value for the given number implementation.
* This can be used by the plugins internally in order * This is done so that number implementations with various degrees of precision
* to find constant providers for number provider they do not provide. * can provide their own pi values, without losing said precision by
* promoting NaiveNumbers.
* *
* @param forClass the class for which to get a generator for. * @param forClass the class to which to find the pi instance.
* @return the resulting generator * @return the pi value for the given class.
*/ */
protected final java.util.function.Function<String, NumberInterface> constantProviderFor(Class<?> forClass){ protected final NumberInterface getPi(Class<? extends NumberInterface> forClass) {
return manager.constantProviderFor(forClass); return manager.piFor(forClass);
} }
/** /**

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,15 +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;
/** /**
* List of registered constant providers for every * The list of number implementations that have been
* number class. * found by their implementation class.
*/ */
private Map<Class<?>, java.util.function.Function<String, NumberInterface>> cachedConstantProviders; 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.
*/ */
@@ -50,32 +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;
/**
* List of all the constant providers loaded by the plugins.
*/
private Set<Class<?>> allConstantProviders;
/** /**
* 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<>();
cachedConstantProviders = 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<>();
allConstantProviders = new HashSet<>();
listeners = new HashSet<>(); listeners = new HashSet<>();
} }
@@ -133,22 +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 constant provider for the given class. * Gets the number implementation for the given implementation class.
* @param forClass the class to get the provider for. *
* @return the provider. * @param name the class for which to find the implementation.
* @return the implementation.
*/ */
public java.util.function.Function<String, NumberInterface> constantProviderFor(Class<?> forClass){ public NumberImplementation interfaceImplementationFor(Class<? extends NumberInterface> name) {
return searchCached(plugins, cachedConstantProviders, Plugin::providedConstantProviders, Plugin::getConstantProvider, forClass); 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;
} }
/** /**
@@ -181,12 +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());
allConstantProviders.addAll(plugin.providedConstantProviders());
} }
listeners.forEach(e -> e.onLoad(this)); listeners.forEach(e -> e.onLoad(this));
} }
@@ -195,11 +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();
allConstantProviders.clear();
listeners.forEach(e -> e.onUnload(this)); listeners.forEach(e -> e.onUnload(this));
} }
@@ -208,7 +262,7 @@ public class PluginManager {
*/ */
public void reload() { public void reload() {
unload(); unload();
reload(); load();
} }
/** /**
@@ -230,20 +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;
}
/**
* Gets all the constant providers loaded by the Plugin Manager.
* @return the set of all constant providers that were loaded.
*/
public Set<Class<?>> getAllConstantProviders() {
return allConstantProviders;
} }
/** /**
@@ -264,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,7 +8,6 @@ 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.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.function.BiFunction; import java.util.function.BiFunction;
@@ -19,8 +18,10 @@ import java.util.function.BiFunction;
*/ */
public class StandardPlugin extends Plugin { public class StandardPlugin extends Plugin {
private static HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> factorialLists = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>(); /**
static HashMap<Class<? extends NumberInterface>, NumberInterface> piValues = new HashMap<Class<? extends NumberInterface>, NumberInterface>(); * Stores objects of NumberInterface with integer values for reuse.
*/
private final static HashMap<Class<? extends NumberInterface>, HashMap<Integer, NumberInterface>> integerValues = new HashMap<>();
/** /**
* The addition operator, + * The addition operator, +
@@ -52,6 +53,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();
} }
}); });
/** /**
@@ -78,16 +94,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;
} }
}); });
/** /**
@@ -97,13 +109,15 @@ 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
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
if (params[0].signum() == 0) { if (params[0].signum() == 0) {
return (new NaiveNumber(1)).promoteTo(params[0].getClass()); return fromInt(params[0].getClass(), 1);
} }
NumberInterface factorial = params[0]; NumberInterface factorial = params[0];
NumberInterface multiplier = params[0]; NumberInterface multiplier = params[0];
@@ -119,20 +133,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
*/ */
@@ -147,74 +147,31 @@ public class StandardPlugin extends Plugin {
return params[0].multiply((new NaiveNumber(params[0].signum())).promoteTo(params[0].getClass())); return params[0].multiply((new NaiveNumber(params[0].signum())).promoteTo(params[0].getClass()));
} }
}; };
/**
* 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 natural log function. * The natural log function.
*/ */
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(fromInt(param.getClass(), 2));
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.
} }
} else { } else {
param = param.multiply(new NaiveNumber(2).promoteTo(param.getClass())); param = param.multiply(fromInt(param.getClass(), 2));
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.
} }
@@ -230,6 +187,7 @@ 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 currentNumerator = x, currentTerm = x, sum = x; NumberInterface currentNumerator = x, currentTerm = x, sum = x;
@@ -250,17 +208,17 @@ public class StandardPlugin extends Plugin {
*/ */
private NumberInterface getLog2(NumberInterface number) { private NumberInterface getLog2(NumberInterface number) {
NumberInterface maxError = getMaxError(number); NumberInterface maxError = getMaxError(number);
//NumberInterface errorBound = (new NaiveNumber(1)).promoteTo(number.getClass()); //NumberInterface errorBound = fromInt(number.getClass(), 1);
//We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n) //We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n)
//In the following, a=1/3^n, b=1/4^n, c = 1/n. //In the following, a=1/3^n, b=1/4^n, c = 1/n.
//a is also an error bound. //a is also an error bound.
NumberInterface a = (new NaiveNumber(1)).promoteTo(number.getClass()), b = a, c = a; NumberInterface a = fromInt(number.getClass(), 1), b = a, c = a;
NumberInterface sum = NaiveNumber.ZERO.promoteTo(number.getClass()); NumberInterface sum = NaiveNumber.ZERO.promoteTo(number.getClass());
int n = 0; int n = 0;
while (a.compareTo(maxError) >= 1) { while (a.compareTo(maxError) >= 1) {
n++; n++;
a = a.divide((new NaiveNumber(3)).promoteTo(number.getClass())); a = a.divide(fromInt(number.getClass(), 3));
b = b.divide((new NaiveNumber(4)).promoteTo(number.getClass())); b = b.divide(fromInt(number.getClass(), 4));
c = NaiveNumber.ONE.promoteTo(number.getClass()).divide((new NaiveNumber(n)).promoteTo(number.getClass())); c = NaiveNumber.ONE.promoteTo(number.getClass()).divide((new NaiveNumber(n)).promoteTo(number.getClass()));
sum = sum.add(a.add(b).multiply(c)); sum = sum.add(a.add(b).multiply(c));
} }
@@ -281,11 +239,123 @@ 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(fromInt(params[0].getClass(), 3).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). * The sine function (the argument is interpreted in radians).
*/ */
public static final Function FUNCTION_SIN = new Function() { public final Function functionSin = new Function() {
@Override @Override
protected boolean matchesParams(NumberInterface[] params) { protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1; return params.length == 1;
@@ -294,19 +364,89 @@ public class StandardPlugin extends Plugin {
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface pi = getPi(params[0].getClass()); NumberInterface pi = getPi(params[0].getClass());
NumberInterface twoPi = pi.multiply(new NaiveNumber(2).promoteTo(pi.getClass())); NumberInterface twoPi = pi.multiply(fromInt(pi.getClass(), 2));
NumberInterface theta = getSmallAngle(params[0]); NumberInterface theta = getSmallAngle(params[0], pi);
//System.out.println(theta); //System.out.println(theta);
if(theta.compareTo(pi.multiply(new NaiveNumber(1.5).promoteTo(twoPi.getClass()))) >= 0){ if (theta.compareTo(pi.multiply(new NaiveNumber(1.5).promoteTo(twoPi.getClass()))) >= 0) {
theta = theta.subtract(twoPi); theta = theta.subtract(twoPi);
} } else if (theta.compareTo(pi.divide(fromInt(pi.getClass(), 2))) > 0) {
else if(theta.compareTo(pi.divide(new NaiveNumber(2).promoteTo(pi.getClass()))) > 0){
theta = pi.subtract(theta); theta = pi.subtract(theta);
} }
//System.out.println(theta); //System.out.println(theta);
return sinTaylor(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(fromInt(params[0].getClass(), 2))
.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);
@@ -335,50 +475,27 @@ public class StandardPlugin extends Plugin {
* @return the maximum error. * @return the maximum error.
*/ */
private static NumberInterface getMaxError(NumberInterface number) { private static NumberInterface getMaxError(NumberInterface number) {
return (new NaiveNumber(10)).promoteTo(number.getClass()).intPow(-number.getMaxPrecision()); return fromInt(number.getClass(), 10).intPow(-number.getMaxPrecision());
}
@Override
public void onEnable() {
registerNumber("naive", NaiveNumber.class);
registerNumber("precise", PreciseNumber.class);
registerOperator("+", OP_ADD);
registerOperator("-", OP_SUBTRACT);
registerOperator("*", OP_MULTIPLY);
registerOperator("/", OP_DIVIDE);
registerOperator("^", OP_CARET);
registerOperator("!", OP_FACTORIAL);
registerFunction("abs", FUNCTION_ABS);
registerFunction("exp", FUNCTION_EXP);
registerFunction("ln", FUNCTION_LN);
registerFunction("sqrt", FUNCTION_SQRT);
registerFunction("sin", FUNCTION_SIN);
}
@Override
public void onDisable() {
} }
/** /**
* A factorial function that uses memoization for each number class; it efficiently * A factorial function that uses memoization for each number class; it efficiently
* computes factorials of non-negative integers. * computes factorials of non-negative integers.
*
* @param numberClass type of number to return. * @param numberClass type of number to return.
* @param n non-negative integer. * @param n non-negative integer.
* @return a number of numClass with value n factorial. * @return a number of numClass with value n factorial.
*/ */
public static NumberInterface factorial(Class<? extends NumberInterface> numberClass, int n){ public static NumberInterface factorial(Class<? extends NumberInterface> numberClass, int n) {
if(!factorialLists.containsKey(numberClass)){ if (!FACTORIAL_LISTS.containsKey(numberClass)) {
factorialLists.put(numberClass, new ArrayList<NumberInterface>()); FACTORIAL_LISTS.put(numberClass, new ArrayList<>());
factorialLists.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass)); FACTORIAL_LISTS.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass));
factorialLists.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass)); FACTORIAL_LISTS.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass));
} }
ArrayList<NumberInterface> list = factorialLists.get(numberClass); ArrayList<NumberInterface> list = FACTORIAL_LISTS.get(numberClass);
if(n >= list.size()){ if (n >= list.size()) {
while(list.size() < n + 16){ while (list.size() < n + 16) {
list.add(list.get(list.size()-1).multiply(new NaiveNumber(list.size()).promoteTo(numberClass))); list.add(list.get(list.size() - 1).multiply(new NaiveNumber(list.size()).promoteTo(numberClass)));
} }
} }
return list.get(n); return list.get(n);
@@ -386,14 +503,15 @@ public class StandardPlugin extends Plugin {
/** /**
* Returns the value of the Taylor series for sin (centered at 0) at x. * Returns the value of the Taylor series for sin (centered at 0) at x.
*
* @param x where the series is evaluated. * @param x where the series is evaluated.
* @return the value of the series * @return the value of the series
*/ */
private static NumberInterface sinTaylor(NumberInterface x){ private static NumberInterface sinTaylor(NumberInterface x) {
NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x; NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x;
NumberInterface maxError = getMaxError(x); NumberInterface maxError = getMaxError(x);
int n = 1; int n = 1;
do{ do {
n += 2; n += 2;
power = power.multiply(multiplier); power = power.multiply(multiplier);
currentTerm = power.divide(factorial(x.getClass(), n)); currentTerm = power.divide(factorial(x.getClass(), n));
@@ -402,53 +520,65 @@ public class StandardPlugin extends Plugin {
return sum; return sum;
} }
/**
* Returns an approximation of Pi, with appropriate accuracy for given number class.
* @param numClass type of number.
* @return A number of class numClass, with value approximately Pi = 3.1415...
*/
public static NumberInterface getPi(Class<? extends NumberInterface> numClass){
if(!piValues.containsKey(numClass)){
//https://en.wikipedia.org/wiki/Chudnovsky_algorithm
NumberInterface C = FUNCTION_SQRT.apply(new NaiveNumber(10005).promoteTo(numClass)).multiply(new NaiveNumber(426880).promoteTo(numClass));
NumberInterface M = NaiveNumber.ONE.promoteTo(numClass);
NumberInterface L = new NaiveNumber(13591409).promoteTo(numClass);
NumberInterface X = M;
NumberInterface sum = L;
int termsNeeded = C.getMaxPrecision()/13 + 1;
NumberInterface lSummand = new NaiveNumber(545140134).promoteTo(L.getClass());
NumberInterface xMultiplier = new NaiveNumber(262537412).promoteTo(X.getClass())
.multiply(new NaiveNumber(1000000000).promoteTo(X.getClass()))
.add(new NaiveNumber(640768000).promoteTo(X.getClass()))
.negate();
for(int i = 0; i < termsNeeded; i++){
M = M
.multiply(new NaiveNumber(12*i+2).promoteTo(M.getClass()))
.multiply(new NaiveNumber(12*i+6).promoteTo(M.getClass()))
.multiply(new NaiveNumber(12*i+10).promoteTo(M.getClass()))
.divide(new NaiveNumber(Math.pow(i+1,3)).promoteTo(M.getClass()));
L = L.add(lSummand);
X = X.multiply(xMultiplier);
sum = sum.add(M.multiply(L).divide(X));
}
piValues.put(numClass, C.divide(sum));
}
return piValues.get(numClass);
}
/** /**
* Returns an equivalent angle in the interval [0, 2pi) * Returns an equivalent angle in the interval [0, 2pi)
*
* @param phi an angle (in radians). * @param phi an angle (in radians).
* @return theta in [0, 2pi) that differs from phi by a multiple of 2pi. * @return theta in [0, 2pi) that differs from phi by a multiple of 2pi.
*/ */
private static NumberInterface getSmallAngle(NumberInterface phi){ private static NumberInterface getSmallAngle(NumberInterface phi, NumberInterface pi) {
NumberInterface twoPi = getPi(phi.getClass()).multiply(new NaiveNumber("2").promoteTo(phi.getClass())); NumberInterface twoPi = pi.multiply(new NaiveNumber("2").promoteTo(phi.getClass()));
NumberInterface theta = FUNCTION_ABS.apply(phi).subtract(twoPi NumberInterface theta = FUNCTION_ABS.apply(phi).subtract(twoPi
.multiply(FUNCTION_ABS.apply(phi).divide(twoPi).floor())); //Now theta is in [0, 2pi). .multiply(FUNCTION_ABS.apply(phi).divide(twoPi).floor())); //Now theta is in [0, 2pi).
if(phi.signum() < 0){ if (phi.signum() < 0) {
theta = twoPi.subtract(theta); theta = twoPi.subtract(theta);
} }
return theta; return theta;
} }
/**
* Returns a number of class numType with value n.
* @param numType class of number to return.
* @param n value of returned number.
* @return numClass instance with value n.
*/
private static NumberInterface fromInt(Class<? extends NumberInterface> numType, int n){
if(!integerValues.containsKey(numType)){
integerValues.put(numType, new HashMap<>());
}
if(!integerValues.get(numType).containsKey(n)){
integerValues.get(numType).put(n, new NaiveNumber(n).promoteTo(numType));
}
return integerValues.get(numType).get(n);
}
@Override
public void onEnable() {
registerNumberImplementation("naive", IMPLEMENTATION_NAIVE);
registerNumberImplementation("precise", IMPLEMENTATION_PRECISE);
registerOperator("+", OP_ADD);
registerOperator("-", OP_SUBTRACT);
registerOperator("`", OP_NEGATE);
registerOperator("*", OP_MULTIPLY);
registerOperator("/", OP_DIVIDE);
registerOperator("^", OP_CARET);
registerOperator("!", OP_FACTORIAL);
registerFunction("abs", FUNCTION_ABS);
registerFunction("exp", FUNCTION_EXP);
registerFunction("ln", FUNCTION_LN);
registerFunction("sqrt", FUNCTION_SQRT);
registerFunction("sin", functionSin);
registerFunction("cos", functionCos);
registerFunction("tan", functionTan);
registerFunction("sec", functionSec);
registerFunction("csc", functionCsc);
registerFunction("cot", functionCot);
}
@Override
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

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Text?>
<BorderPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.nwapw.abacus.fx.AbacusController">
<center>
<TabPane fx:id="coreTabPane">
<Tab fx:id="calculateTab" text="Calculator" closable="false">
<BorderPane>
<center>
<TableView fx:id="historyTable">
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<columns>
<TableColumn fx:id="inputColumn" text="Input" sortable="false"/>
<TableColumn fx:id="parsedColumn" text="Parsed" sortable="false"/>
<TableColumn fx:id="outputColumn" text="Output" sortable="false"/>
</columns>
</TableView>
</center>
<bottom>
<VBox>
<ScrollPane prefHeight="50" vbarPolicy="NEVER">
<padding>
<Insets top="10" bottom="10" left="10" right="10"/>
</padding>
<Text fx:id="outputText"/>
</ScrollPane>
<TextField fx:id="inputField" onAction="#performCalculation"/>
<Button fx:id="inputButton" text="Calculate" maxWidth="Infinity"
onAction="#performCalculation"/>
<Button fx:id="stopButton" text="Stop" maxWidth="Infinity"
onAction="#performStop" disable="true"/>
</VBox>
</bottom>
</BorderPane>
</Tab>
<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"/>
<FlowPane GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="2" 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>
</center>
</BorderPane>

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("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("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