mirror of
https://github.com/DanilaFe/abacus
synced 2026-01-25 16:15:19 +00:00
Compare commits
15 Commits
gradle
...
new-parser
| Author | SHA1 | Date | |
|---|---|---|---|
| 274826cc09 | |||
| bfee4ec322 | |||
| bd1f7b8786 | |||
| 90c6625108 | |||
| a99b6b647f | |||
| d12d53032b | |||
| ff31dd6e47 | |||
| 9454620489 | |||
| 1160768ee5 | |||
| 1ce9fc6b1c | |||
| acf3d85584 | |||
| 6c80d8fe93 | |||
| c230675855 | |||
| bd44307f2b | |||
| a949a27da4 |
22
README.md
22
README.md
@@ -1,2 +1,24 @@
|
|||||||
# abacus
|
# abacus
|
||||||
Summer project for NWAPW.
|
Summer project for NWAPW.
|
||||||
|
Created by Arthur Drobot, Danila Fedorin and Riley Jones.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
- [x] Basic number class
|
||||||
|
- [x] Implementation of basic functions
|
||||||
|
- [x] Implementation of `exp`, `ln`, `sqrt` using the basic functions and Taylor Series
|
||||||
|
- [x] Plugin loading from JAR files
|
||||||
|
- [x] Regular expression pattern construction and matching
|
||||||
|
- [x] Infix and postfix operators
|
||||||
|
- [ ] __Correct__ handling of postfix operators (`12+!3` parses to `12!+3`, which is wrong)
|
||||||
|
- [ ] User-defined precision
|
||||||
|
|
||||||
|
## Project Proposal
|
||||||
|
>There is currently no calculator that is up to par with a sophisticated programmer's needs. The standard system ones are awful, not respecting the order of operations and having only a few basic functions programmed into them. The web ones are tied to the Internet and don't function offline. Physical ones like the TI-84 come close in terms of functionality, but they make the user have to switch between the computer and the device.
|
||||||
|
>
|
||||||
|
>My proposal is a more ergonomic calculator for advanced users. Of course, for a calculator, being able to do the actual math is a requirement. However, in this project I also would like to include other features that would make it much more pleasant to use. The first of these features is a wide collection of built in functions, designed with usefulness and consistency in mind. The second is scripting capabilities - most simply using Lua and its provided library. By allowing the users to script in a standardized language that isn't TI-BASIC, the calculator could simplify a variety of tasks and not have to clutter up the default provided functions with overly specific things. Lastly, it's important for the calculator to have a good design that doesn't get in the way of its use, on the two major desktop platforms (macOS and Windows).
|
||||||
|
>
|
||||||
|
>With these features I believe that this is a calculator that I would use (and frequently find myself wanting to use). It also seems to have a diverse array of tasks, such as UI design, implementing the math functions to be fast and optimized (fast inverse square root, anyone?), parsing code, and working with Lua integration.
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compile 'com.moandjiezana.toml:toml4j:0.7.1'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +1,171 @@
|
|||||||
package org.nwapw.abacus;
|
package org.nwapw.abacus;
|
||||||
|
|
||||||
import org.nwapw.abacus.plugin.PluginManager;
|
import org.nwapw.abacus.config.ConfigurationObject;
|
||||||
//import org.nwapw.abacus.plugin.StandardPlugin;
|
import org.nwapw.abacus.number.NaiveNumber;
|
||||||
import org.nwapw.abacus.plugin.StandardPlugin;
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
import org.nwapw.abacus.window.Window;
|
import org.nwapw.abacus.parsing.LexerTokenizer;
|
||||||
|
import org.nwapw.abacus.parsing.ShuntingYardParser;
|
||||||
|
import org.nwapw.abacus.parsing.TreeBuilder;
|
||||||
import org.nwapw.abacus.plugin.ClassFinder;
|
import org.nwapw.abacus.plugin.ClassFinder;
|
||||||
|
import org.nwapw.abacus.plugin.PluginManager;
|
||||||
|
import org.nwapw.abacus.plugin.StandardPlugin;
|
||||||
|
import org.nwapw.abacus.tree.NumberReducer;
|
||||||
|
import org.nwapw.abacus.tree.TreeNode;
|
||||||
|
import org.nwapw.abacus.window.Window;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main calculator class. This is responsible
|
||||||
|
* for piecing together all of the components, allowing
|
||||||
|
* their interaction with each other.
|
||||||
|
*/
|
||||||
public class Abacus {
|
public class Abacus {
|
||||||
|
|
||||||
private Window mainUi;
|
/**
|
||||||
private PluginManager manager;
|
* The default implementation to use for the number representation.
|
||||||
|
*/
|
||||||
|
public static final Class<? extends NumberInterface> DEFAULT_NUMBER = NaiveNumber.class;
|
||||||
|
/**
|
||||||
|
* The file used for saving and loading configuration.
|
||||||
|
*/
|
||||||
|
public static final File CONFIG_FILE = new File("config.toml");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main Abacus UI.
|
||||||
|
*/
|
||||||
|
private Window mainUi;
|
||||||
|
/**
|
||||||
|
* The plugin manager responsible for
|
||||||
|
* loading and unloading plugins,
|
||||||
|
* and getting functions from them.
|
||||||
|
*/
|
||||||
|
private PluginManager pluginManager;
|
||||||
|
/**
|
||||||
|
* The reducer used to evaluate the tree.
|
||||||
|
*/
|
||||||
|
private NumberReducer numberReducer;
|
||||||
|
/**
|
||||||
|
* The configuration loaded from a file.
|
||||||
|
*/
|
||||||
|
private ConfigurationObject configuration;
|
||||||
|
/**
|
||||||
|
* The tree builder used to construct a tree
|
||||||
|
* from a string.
|
||||||
|
*/
|
||||||
|
private TreeBuilder treeBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of the Abacus calculator.
|
||||||
|
*/
|
||||||
public Abacus(){
|
public Abacus(){
|
||||||
init();
|
pluginManager = new PluginManager(this);
|
||||||
|
mainUi = new Window(this);
|
||||||
|
numberReducer = new NumberReducer(this);
|
||||||
|
configuration = new ConfigurationObject(CONFIG_FILE);
|
||||||
|
configuration.save(CONFIG_FILE);
|
||||||
|
LexerTokenizer lexerTokenizer = new LexerTokenizer();
|
||||||
|
ShuntingYardParser shuntingYardParser = new ShuntingYardParser(this);
|
||||||
|
treeBuilder = new TreeBuilder<>(lexerTokenizer, shuntingYardParser);
|
||||||
|
|
||||||
|
pluginManager.addListener(lexerTokenizer);
|
||||||
|
pluginManager.addListener(shuntingYardParser);
|
||||||
|
pluginManager.addInstantiated(new StandardPlugin(pluginManager));
|
||||||
|
try {
|
||||||
|
ClassFinder.loadJars("plugins")
|
||||||
|
.forEach(plugin -> pluginManager.addClass(plugin));
|
||||||
|
} catch (IOException | ClassNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
pluginManager.load();
|
||||||
|
|
||||||
|
mainUi.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
/**
|
||||||
|
* Gets the current tree builder.
|
||||||
|
* @return the main tree builder in this abacus instance.
|
||||||
|
*/
|
||||||
|
public TreeBuilder getTreeBuilder() {
|
||||||
|
return treeBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current plugin manager,
|
||||||
|
* @return the plugin manager in this abacus instance.
|
||||||
|
*/
|
||||||
|
public PluginManager getPluginManager() {
|
||||||
|
return pluginManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current UI.
|
||||||
|
* @return the UI window in this abacus instance.
|
||||||
|
*/
|
||||||
|
public Window getMainUi() {
|
||||||
|
return mainUi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the reducer that is responsible for transforming
|
||||||
|
* an expression into a number.
|
||||||
|
* @return the number reducer in this abacus instance.
|
||||||
|
*/
|
||||||
|
public NumberReducer getNumberReducer() {
|
||||||
|
return numberReducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the configuration object associated with this instance.
|
||||||
|
* @return the configuration object.
|
||||||
|
*/
|
||||||
|
public ConfigurationObject getConfiguration() {
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a string into a tree structure using the main
|
||||||
|
* tree builder.
|
||||||
|
* @param input the input string to parse
|
||||||
|
* @return the resulting tree, null if the tree builder or the produced tree are null.
|
||||||
|
*/
|
||||||
|
public TreeNode parseString(String input){
|
||||||
|
return treeBuilder.fromString(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates the given tree using the main
|
||||||
|
* number reducer.
|
||||||
|
* @param tree the tree to reduce, must not be null.
|
||||||
|
* @return the resulting number, or null of the reduction failed.
|
||||||
|
*/
|
||||||
|
public NumberInterface evaluateTree(TreeNode tree){
|
||||||
|
return tree.reduce(numberReducer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NumberInterface numberFromString(String numberString){
|
||||||
|
Class<? extends NumberInterface> toInstantiate =
|
||||||
|
pluginManager.numberFor(configuration.getNumberImplementation());
|
||||||
|
if(toInstantiate == null) toInstantiate = DEFAULT_NUMBER;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return toInstantiate.getConstructor(String.class).newInstance(numberString);
|
||||||
|
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args){
|
||||||
try {
|
try {
|
||||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||||
} catch (ClassNotFoundException | InstantiationException | UnsupportedLookAndFeelException | IllegalAccessException e) {
|
} catch (ClassNotFoundException | InstantiationException | UnsupportedLookAndFeelException | IllegalAccessException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
manager = new PluginManager();
|
|
||||||
manager.addInstantiated(new StandardPlugin(manager));
|
|
||||||
try {
|
|
||||||
ClassFinder.loadJars("plugins")
|
|
||||||
.forEach(plugin -> manager.addClass(plugin));
|
|
||||||
} catch (IOException | ClassNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
mainUi = new Window(manager);
|
|
||||||
mainUi.setVisible(true);
|
|
||||||
manager.load();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args){
|
|
||||||
new Abacus();
|
new Abacus();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/org/nwapw/abacus/config/Configuration.java
Normal file
14
src/org/nwapw/abacus/config/Configuration.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package org.nwapw.abacus.config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializable class that will be used to load TOML
|
||||||
|
* configurations.
|
||||||
|
*/
|
||||||
|
public class Configuration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of number this calculator should use.
|
||||||
|
*/
|
||||||
|
public String numberType;
|
||||||
|
|
||||||
|
}
|
||||||
105
src/org/nwapw/abacus/config/ConfigurationObject.java
Normal file
105
src/org/nwapw/abacus/config/ConfigurationObject.java
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -102,7 +102,7 @@ public class Lexer<T> {
|
|||||||
if(index < from.length() && node.matches(from.charAt(index))) {
|
if(index < from.length() && node.matches(from.charAt(index))) {
|
||||||
node.addOutputsInto(futureSet);
|
node.addOutputsInto(futureSet);
|
||||||
} else if(node instanceof EndNode){
|
} else if(node instanceof EndNode){
|
||||||
matches.add(new Match<>(startAt, index, ((EndNode<T>) node).getPatternId()));
|
matches.add(new Match<>(from.substring(startAt, index), ((EndNode<T>) node).getPatternId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ public class Lexer<T> {
|
|||||||
}
|
}
|
||||||
matches.sort((a, b) -> compare.compare(a.getType(), b.getType()));
|
matches.sort((a, b) -> compare.compare(a.getType(), b.getType()));
|
||||||
if(compare != null) {
|
if(compare != null) {
|
||||||
matches.sort(Comparator.comparingInt(a -> a.getTo() - a.getFrom()));
|
matches.sort(Comparator.comparingInt(a -> a.getContent().length()));
|
||||||
}
|
}
|
||||||
return matches.isEmpty() ? null : matches.get(matches.size() - 1);
|
return matches.isEmpty() ? null : matches.get(matches.size() - 1);
|
||||||
}
|
}
|
||||||
@@ -132,9 +132,10 @@ public class Lexer<T> {
|
|||||||
ArrayList<Match<T>> matches = new ArrayList<>();
|
ArrayList<Match<T>> matches = new ArrayList<>();
|
||||||
Match<T> lastMatch = null;
|
Match<T> lastMatch = null;
|
||||||
while(index < from.length() && (lastMatch = lexOne(from, index, compare)) != null){
|
while(index < from.length() && (lastMatch = lexOne(from, index, compare)) != null){
|
||||||
if(lastMatch.getTo() == lastMatch.getFrom()) return null;
|
int length = lastMatch.getContent().length();
|
||||||
|
if(length == 0) return null;
|
||||||
matches.add(lastMatch);
|
matches.add(lastMatch);
|
||||||
index += lastMatch.getTo() - lastMatch.getFrom();
|
index += length;
|
||||||
}
|
}
|
||||||
if(lastMatch == null) return null;
|
if(lastMatch == null) return null;
|
||||||
return matches;
|
return matches;
|
||||||
|
|||||||
@@ -7,13 +7,9 @@ package org.nwapw.abacus.lexing.pattern;
|
|||||||
public class Match<T> {
|
public class Match<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The bottom range of the string, inclusive.
|
* The content of this match.
|
||||||
*/
|
*/
|
||||||
private int from;
|
private String content;
|
||||||
/**
|
|
||||||
* The top range of the string, exclusive.
|
|
||||||
*/
|
|
||||||
private int to;
|
|
||||||
/**
|
/**
|
||||||
* The pattern type this match matched.
|
* The pattern type this match matched.
|
||||||
*/
|
*/
|
||||||
@@ -21,30 +17,20 @@ public class Match<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new match with the given parameters.
|
* Creates a new match with the given parameters.
|
||||||
* @param from the bottom range of the string.
|
* @param content the content of this match.
|
||||||
* @param to the top range of the string.
|
|
||||||
* @param type the type of the match.
|
* @param type the type of the match.
|
||||||
*/
|
*/
|
||||||
public Match(int from, int to, T type){
|
public Match(String content, T type){
|
||||||
this.from = from;
|
this.content = content;
|
||||||
this.to = to;
|
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the bottom range bound of the string.
|
* Gets the content of this match.
|
||||||
* @return the bottom range bound of the string.
|
* @return the content.
|
||||||
*/
|
*/
|
||||||
public int getFrom() {
|
public String getContent() {
|
||||||
return from;
|
return content;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the top range bound of the string.
|
|
||||||
* @return the top range bound of the string.
|
|
||||||
*/
|
|
||||||
public int getTo() {
|
|
||||||
return to;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ public class NaiveNumber implements NumberInterface {
|
|||||||
*/
|
*/
|
||||||
private double value;
|
private double value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new NaiveNumber with the given string.
|
||||||
|
* @param value the value, which will be parsed as a double.
|
||||||
|
*/
|
||||||
|
public NaiveNumber(String value) {
|
||||||
|
this(Double.parseDouble(value));
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Creates a new NaiveNumber with the given value.
|
* Creates a new NaiveNumber with the given value.
|
||||||
* @param value the value to use.
|
* @param value the value to use.
|
||||||
@@ -28,7 +35,7 @@ public class NaiveNumber implements NumberInterface {
|
|||||||
public static final NaiveNumber ONE = new NaiveNumber(1);
|
public static final NaiveNumber ONE = new NaiveNumber(1);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int precision() {
|
public int getMaxPrecision() {
|
||||||
return 18;
|
return 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ package org.nwapw.abacus.number;
|
|||||||
public interface NumberInterface {
|
public interface NumberInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The precision to which this number operates.
|
* The maximum precision to which this number operates.
|
||||||
* @return the precision.
|
* @return the precision.
|
||||||
*/
|
*/
|
||||||
int precision();
|
int getMaxPrecision();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multiplies this number by another, returning
|
* Multiplies this number by another, returning
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package org.nwapw.abacus.number;
|
package org.nwapw.abacus.number;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.math.MathContext;
|
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
public class PreciseNumber implements NumberInterface{
|
public class PreciseNumber implements NumberInterface{
|
||||||
@@ -43,7 +41,7 @@ public class PreciseNumber implements NumberInterface{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int precision() {
|
public int getMaxPrecision() {
|
||||||
return 54;
|
return 54;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +52,7 @@ public class PreciseNumber implements NumberInterface{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NumberInterface divide(NumberInterface divisor) {
|
public NumberInterface divide(NumberInterface divisor) {
|
||||||
return new PreciseNumber(value.divide(((PreciseNumber) divisor).value, this.precision(), RoundingMode.HALF_UP));
|
return new PreciseNumber(value.divide(((PreciseNumber) divisor).value, this.getMaxPrecision(), RoundingMode.HALF_UP));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -109,7 +107,7 @@ public class PreciseNumber implements NumberInterface{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
BigDecimal rounded = value.setScale(precision() - 4, RoundingMode.HALF_UP);
|
BigDecimal rounded = value.setScale(getMaxPrecision() - 4, RoundingMode.HALF_UP);
|
||||||
return rounded.stripTrailingZeros().toPlainString();
|
return rounded.stripTrailingZeros().toPlainString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
67
src/org/nwapw/abacus/parsing/LexerTokenizer.java
Normal file
67
src/org/nwapw/abacus/parsing/LexerTokenizer.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package org.nwapw.abacus.parsing;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.lexing.Lexer;
|
||||||
|
import org.nwapw.abacus.lexing.pattern.Match;
|
||||||
|
import org.nwapw.abacus.lexing.pattern.Pattern;
|
||||||
|
import org.nwapw.abacus.plugin.PluginListener;
|
||||||
|
import org.nwapw.abacus.plugin.PluginManager;
|
||||||
|
import org.nwapw.abacus.tree.TokenType;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tokenzier that uses the lexer class and registered function and operator
|
||||||
|
* names to turn input into tokens in O(n) time.
|
||||||
|
*/
|
||||||
|
public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparator used to sort the tokens produced by the lexer.
|
||||||
|
*/
|
||||||
|
protected static final Comparator<TokenType> TOKEN_SORTER = Comparator.comparingInt(e -> e.priority);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lexer instance used to turn strings into matches.
|
||||||
|
*/
|
||||||
|
private Lexer<TokenType> lexer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new lexer tokenizer.
|
||||||
|
*/
|
||||||
|
public LexerTokenizer(){
|
||||||
|
lexer = new Lexer<TokenType>() {{
|
||||||
|
register(" ", TokenType.WHITESPACE);
|
||||||
|
register(",", TokenType.COMMA);
|
||||||
|
register("[0-9]*(\\.[0-9]+)?", TokenType.NUM);
|
||||||
|
register("\\(", TokenType.OPEN_PARENTH);
|
||||||
|
register("\\)", TokenType.CLOSE_PARENTH);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Match<TokenType>> tokenizeString(String string) {
|
||||||
|
return lexer.lexAll(string, 0, TOKEN_SORTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad(PluginManager manager) {
|
||||||
|
for(String operator : manager.getAllOperators()){
|
||||||
|
lexer.register(Pattern.sanitize(operator), TokenType.OP);
|
||||||
|
}
|
||||||
|
for(String function : manager.getAllFunctions()){
|
||||||
|
lexer.register(Pattern.sanitize(function), TokenType.FUNCTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnload(PluginManager manager) {
|
||||||
|
for(String operator : manager.getAllOperators()){
|
||||||
|
lexer.unregister(Pattern.sanitize(operator), TokenType.OP);
|
||||||
|
}
|
||||||
|
for(String function : manager.getAllFunctions()){
|
||||||
|
lexer.unregister(Pattern.sanitize(function), TokenType.FUNCTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
20
src/org/nwapw/abacus/parsing/Parser.java
Normal file
20
src/org/nwapw/abacus/parsing/Parser.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package org.nwapw.abacus.parsing;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.TreeNode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An itnerface that provides the ability to convert a list of tokens
|
||||||
|
* into a parse tree.
|
||||||
|
* @param <T> the type of tokens accepted by this parser.
|
||||||
|
*/
|
||||||
|
public interface Parser<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a tree out of the given tokens.
|
||||||
|
* @param tokens the tokens to construct a tree from.
|
||||||
|
* @return the constructed tree, or null on error.
|
||||||
|
*/
|
||||||
|
public TreeNode constructTree(List<T> tokens);
|
||||||
|
}
|
||||||
@@ -1,96 +1,56 @@
|
|||||||
package org.nwapw.abacus.tree;
|
package org.nwapw.abacus.parsing;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.Abacus;
|
||||||
|
import org.nwapw.abacus.function.Operator;
|
||||||
import org.nwapw.abacus.function.OperatorAssociativity;
|
import org.nwapw.abacus.function.OperatorAssociativity;
|
||||||
import org.nwapw.abacus.function.OperatorType;
|
import org.nwapw.abacus.function.OperatorType;
|
||||||
import org.nwapw.abacus.lexing.Lexer;
|
|
||||||
import org.nwapw.abacus.lexing.pattern.Match;
|
import org.nwapw.abacus.lexing.pattern.Match;
|
||||||
import org.nwapw.abacus.lexing.pattern.Pattern;
|
import org.nwapw.abacus.plugin.PluginListener;
|
||||||
import org.nwapw.abacus.number.NaiveNumber;
|
import org.nwapw.abacus.plugin.PluginManager;
|
||||||
import org.nwapw.abacus.number.PreciseNumber;
|
import org.nwapw.abacus.tree.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The builder responsible for turning strings into trees.
|
* A parser that uses shunting yard to rearranged matches into postfix
|
||||||
|
* and then convert them into a parse tree.
|
||||||
*/
|
*/
|
||||||
public class TreeBuilder {
|
public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The lexer used to get the input tokens.
|
* The Abacus instance used to create number instances.
|
||||||
*/
|
*/
|
||||||
private Lexer<TokenType> lexer;
|
private Abacus abacus;
|
||||||
/**
|
/**
|
||||||
* The map of operator precedences.
|
* Map of operator precedences, loaded from the plugin operators.
|
||||||
*/
|
*/
|
||||||
private Map<String, Integer> precedenceMap;
|
private Map<String, Integer> precedenceMap;
|
||||||
/**
|
/**
|
||||||
* The map of operator associativity.
|
* Map of operator associativity, loaded from the plugin operators.
|
||||||
*/
|
*/
|
||||||
private Map<String, OperatorAssociativity> associativityMap;
|
private Map<String, OperatorAssociativity> associativityMap;
|
||||||
/**
|
/**
|
||||||
* The map of operator types.
|
* Map of operator types, loaded from plugin operators.
|
||||||
*/
|
*/
|
||||||
private Map<String, OperatorType> typeMap;
|
private Map<String, OperatorType> typeMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Comparator used to sort token types.
|
* Creates a new Shunting Yard parser with the given Abacus instance.
|
||||||
|
* @param abacus the abacus instance.
|
||||||
*/
|
*/
|
||||||
protected static Comparator<TokenType> tokenSorter = Comparator.comparingInt(e -> e.priority);
|
public ShuntingYardParser(Abacus abacus){
|
||||||
|
this.abacus = abacus;
|
||||||
/**
|
|
||||||
* Creates a new TreeBuilder.
|
|
||||||
*/
|
|
||||||
public TreeBuilder(){
|
|
||||||
lexer = new Lexer<TokenType>(){{
|
|
||||||
register(" ", TokenType.WHITESPACE);
|
|
||||||
register(",", TokenType.COMMA);
|
|
||||||
register("[0-9]*(\\.[0-9]+)?", TokenType.NUM);
|
|
||||||
register("\\(", TokenType.OPEN_PARENTH);
|
|
||||||
register("\\)", TokenType.CLOSE_PARENTH);
|
|
||||||
}};
|
|
||||||
precedenceMap = new HashMap<>();
|
precedenceMap = new HashMap<>();
|
||||||
associativityMap = new HashMap<>();
|
associativityMap = new HashMap<>();
|
||||||
typeMap = new HashMap<>();
|
typeMap = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a function with the TreeBuilder.
|
|
||||||
* @param function the function to register.
|
|
||||||
*/
|
|
||||||
public void registerFunction(String function){
|
|
||||||
lexer.register(Pattern.sanitize(function), TokenType.FUNCTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers an operator with the TreeBuilder.
|
|
||||||
* @param operator the operator to register.
|
|
||||||
* @param precedence the precedence of the operator.
|
|
||||||
* @param associativity the associativity of the operator.
|
|
||||||
*/
|
|
||||||
public void registerOperator(String operator, OperatorAssociativity associativity,
|
|
||||||
OperatorType operatorType, int precedence){
|
|
||||||
lexer.register(Pattern.sanitize(operator), TokenType.OP);
|
|
||||||
precedenceMap.put(operator, precedence);
|
|
||||||
associativityMap.put(operator, associativity);
|
|
||||||
typeMap.put(operator, operatorType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tokenizes a string, converting it into matches
|
|
||||||
* @param string the string to tokenize.
|
|
||||||
* @return the list of tokens produced.
|
|
||||||
*/
|
|
||||||
public List<Match<TokenType>> tokenize(String string){
|
|
||||||
return lexer.lexAll(string, 0, tokenSorter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rearranges tokens into a postfix list, using Shunting Yard.
|
* Rearranges tokens into a postfix list, using Shunting Yard.
|
||||||
* @param source the source string.
|
|
||||||
* @param from the tokens to be rearranged.
|
* @param from the tokens to be rearranged.
|
||||||
* @return the resulting list of rearranged tokens.
|
* @return the resulting list of rearranged tokens.
|
||||||
*/
|
*/
|
||||||
public List<Match<TokenType>> intoPostfix(String source, 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<>();
|
||||||
while(!from.isEmpty()){
|
while(!from.isEmpty()){
|
||||||
@@ -99,10 +59,10 @@ public class TreeBuilder {
|
|||||||
if(matchType == TokenType.NUM) {
|
if(matchType == TokenType.NUM) {
|
||||||
output.add(match);
|
output.add(match);
|
||||||
} else if(matchType == TokenType.FUNCTION) {
|
} else if(matchType == TokenType.FUNCTION) {
|
||||||
output.add(new Match<>(0, 0, TokenType.INTERNAL_FUNCTION_END));
|
output.add(new Match<>("" , TokenType.INTERNAL_FUNCTION_END));
|
||||||
tokenStack.push(match);
|
tokenStack.push(match);
|
||||||
} else if(matchType == TokenType.OP){
|
} else if(matchType == TokenType.OP){
|
||||||
String tokenString = source.substring(match.getFrom(), match.getTo());
|
String tokenString = match.getContent();
|
||||||
OperatorType type = typeMap.get(tokenString);
|
OperatorType type = typeMap.get(tokenString);
|
||||||
int precedence = precedenceMap.get(tokenString);
|
int precedence = precedenceMap.get(tokenString);
|
||||||
OperatorAssociativity associativity = associativityMap.get(tokenString);
|
OperatorAssociativity associativity = associativityMap.get(tokenString);
|
||||||
@@ -118,7 +78,7 @@ public class TreeBuilder {
|
|||||||
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(source.substring(otherMatch.getFrom(), otherMatch.getTo()));
|
int otherPrecedence = precedenceMap.get(match.getContent());
|
||||||
if(otherPrecedence < precedence ||
|
if(otherPrecedence < precedence ||
|
||||||
(associativity == OperatorAssociativity.RIGHT && otherPrecedence == precedence)) {
|
(associativity == OperatorAssociativity.RIGHT && otherPrecedence == precedence)) {
|
||||||
break;
|
break;
|
||||||
@@ -150,34 +110,33 @@ public class TreeBuilder {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a tree recursively from a list of tokens.
|
* Constructs a tree recursively from a list of tokens.
|
||||||
* @param source the source string.
|
|
||||||
* @param matches the list of tokens from the source string.
|
* @param matches the list of tokens from the source string.
|
||||||
* @return the construct tree expression.
|
* @return the construct tree expression.
|
||||||
*/
|
*/
|
||||||
public TreeNode fromStringRecursive(String source, List<Match<TokenType>> matches){
|
public TreeNode constructRecursive(List<Match<TokenType>> matches){
|
||||||
if(matches.size() == 0) return null;
|
if(matches.size() == 0) return null;
|
||||||
Match<TokenType> match = matches.remove(0);
|
Match<TokenType> match = matches.remove(0);
|
||||||
TokenType matchType = match.getType();
|
TokenType matchType = match.getType();
|
||||||
if(matchType == TokenType.OP){
|
if(matchType == TokenType.OP){
|
||||||
String operator = source.substring(match.getFrom(), match.getTo());
|
String operator = match.getContent();
|
||||||
OperatorType type = typeMap.get(operator);
|
OperatorType type = typeMap.get(operator);
|
||||||
if(type == OperatorType.BINARY_INFIX){
|
if(type == OperatorType.BINARY_INFIX){
|
||||||
TreeNode right = fromStringRecursive(source, matches);
|
TreeNode right = constructRecursive(matches);
|
||||||
TreeNode left = fromStringRecursive(source, 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 BinaryInfixNode(operator, left, right);
|
||||||
} else {
|
} else {
|
||||||
TreeNode applyTo = fromStringRecursive(source, matches);
|
TreeNode applyTo = constructRecursive(matches);
|
||||||
if(applyTo == null) return null;
|
if(applyTo == null) return null;
|
||||||
else return new UnaryPrefixNode(operator, applyTo);
|
else return new UnaryPrefixNode(operator, applyTo);
|
||||||
}
|
}
|
||||||
} else if(matchType == TokenType.NUM){
|
} else if(matchType == TokenType.NUM){
|
||||||
return new NumberNode(new NaiveNumber(Double.parseDouble(source.substring(match.getFrom(), match.getTo()))));
|
return new NumberNode(abacus.numberFromString(match.getContent()));
|
||||||
} else if(matchType == TokenType.FUNCTION){
|
} else if(matchType == TokenType.FUNCTION){
|
||||||
String functionName = source.substring(match.getFrom(), match.getTo());
|
String functionName = match.getContent();
|
||||||
FunctionNode node = new FunctionNode(functionName);
|
FunctionNode node = new FunctionNode(functionName);
|
||||||
while(!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END){
|
while(!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END){
|
||||||
TreeNode argument = fromStringRecursive(source, matches);
|
TreeNode argument = constructRecursive(matches);
|
||||||
if(argument == null) return null;
|
if(argument == null) return null;
|
||||||
node.prependChild(argument);
|
node.prependChild(argument);
|
||||||
}
|
}
|
||||||
@@ -188,20 +147,27 @@ public class TreeBuilder {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Creates a tree node from a string.
|
public TreeNode constructTree(List<Match<TokenType>> tokens) {
|
||||||
* @param string the string to create a node from.
|
tokens = intoPostfix(new ArrayList<>(tokens));
|
||||||
* @return the resulting tree.
|
Collections.reverse(tokens);
|
||||||
*/
|
return constructRecursive(tokens);
|
||||||
public TreeNode fromString(String string){
|
|
||||||
List<Match<TokenType>> matches = tokenize(string);
|
|
||||||
if(matches == null) return null;
|
|
||||||
matches.removeIf(m -> m.getType() == TokenType.WHITESPACE);
|
|
||||||
matches = intoPostfix(string, matches);
|
|
||||||
if(matches == null) return null;
|
|
||||||
|
|
||||||
Collections.reverse(matches);
|
|
||||||
return fromStringRecursive(string, matches);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad(PluginManager manager) {
|
||||||
|
for(String operator : manager.getAllOperators()){
|
||||||
|
Operator operatorInstance = manager.operatorFor(operator);
|
||||||
|
precedenceMap.put(operator, operatorInstance.getPrecedence());
|
||||||
|
associativityMap.put(operator, operatorInstance.getAssociativity());
|
||||||
|
typeMap.put(operator, operatorInstance.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnload(PluginManager manager) {
|
||||||
|
precedenceMap.clear();
|
||||||
|
associativityMap.clear();
|
||||||
|
typeMap.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
18
src/org/nwapw/abacus/parsing/Tokenizer.java
Normal file
18
src/org/nwapw/abacus/parsing/Tokenizer.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package org.nwapw.abacus.parsing;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that provides the ability to convert a string into a list of tokens.
|
||||||
|
* @param <T> the type of the tokens produced.
|
||||||
|
*/
|
||||||
|
public interface Tokenizer<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string into tokens.
|
||||||
|
* @param string the string to convert.
|
||||||
|
* @return the list of tokens, or null on error.
|
||||||
|
*/
|
||||||
|
public List<T> tokenizeString(String string);
|
||||||
|
|
||||||
|
}
|
||||||
23
src/org/nwapw/abacus/parsing/TreeBuilder.java
Normal file
23
src/org/nwapw/abacus/parsing/TreeBuilder.java
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package org.nwapw.abacus.parsing;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.TreeNode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TreeBuilder<T> {
|
||||||
|
|
||||||
|
private Tokenizer<T> tokenizer;
|
||||||
|
private Parser<T> parser;
|
||||||
|
|
||||||
|
public TreeBuilder(Tokenizer<T> tokenizer, Parser<T> parser){
|
||||||
|
this.tokenizer = tokenizer;
|
||||||
|
this.parser = parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeNode fromString(String input){
|
||||||
|
List<T> tokens = tokenizer.tokenizeString(input);
|
||||||
|
if(tokens == null) return null;
|
||||||
|
return parser.constructTree(tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -24,7 +25,7 @@ public class ClassFinder {
|
|||||||
* @throws IOException thrown if an error occurred scanning the plugin folder.
|
* @throws IOException thrown if an error occurred scanning the plugin folder.
|
||||||
* @throws ClassNotFoundException thrown if the class listed in the file doesn't get loaded.
|
* @throws ClassNotFoundException thrown if the class listed in the file doesn't get loaded.
|
||||||
*/
|
*/
|
||||||
public static ArrayList<Class<?>> loadJars(String filePath) throws IOException, ClassNotFoundException {
|
public static List<Class<?>> loadJars(String filePath) throws IOException, ClassNotFoundException {
|
||||||
return loadJars(new File(filePath));
|
return loadJars(new File(filePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ public class ClassFinder {
|
|||||||
* @throws IOException thrown if an error occurred scanning the plugin folder.
|
* @throws IOException thrown if an error occurred scanning the plugin folder.
|
||||||
* @throws ClassNotFoundException thrown if the class listed in the file doesn't get loaded.
|
* @throws ClassNotFoundException thrown if the class listed in the file doesn't get loaded.
|
||||||
*/
|
*/
|
||||||
public static ArrayList<Class<?>> loadJars(File pluginFolderPath) throws IOException, ClassNotFoundException {
|
public static List<Class<?>> loadJars(File pluginFolderPath) throws IOException, ClassNotFoundException {
|
||||||
ArrayList<Class<?>> toReturn = new ArrayList<>();
|
ArrayList<Class<?>> toReturn = new ArrayList<>();
|
||||||
if(!pluginFolderPath.exists()) return toReturn;
|
if(!pluginFolderPath.exists()) return toReturn;
|
||||||
ArrayList<File> files = Files.walk(pluginFolderPath.toPath())
|
ArrayList<File> files = Files.walk(pluginFolderPath.toPath())
|
||||||
@@ -55,7 +56,7 @@ public class ClassFinder {
|
|||||||
* @throws IOException thrown if there was an error reading the file
|
* @throws IOException thrown if there was an error reading the file
|
||||||
* @throws ClassNotFoundException thrown if the class could not be loaded.
|
* @throws ClassNotFoundException thrown if the class could not be loaded.
|
||||||
*/
|
*/
|
||||||
public static ArrayList<Class<?>> loadJar(File jarLocation) throws IOException, ClassNotFoundException {
|
public static List<Class<?>> loadJar(File jarLocation) throws IOException, ClassNotFoundException {
|
||||||
ArrayList<Class<?>> loadedClasses = new ArrayList<>();
|
ArrayList<Class<?>> loadedClasses = new ArrayList<>();
|
||||||
String path = jarLocation.getPath();
|
String path = jarLocation.getPath();
|
||||||
URL[] urls = new URL[]{new URL("jar:file:" + path + "!/")};
|
URL[] urls = new URL[]{new URL("jar:file:" + path + "!/")};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.nwapw.abacus.plugin;
|
|||||||
|
|
||||||
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 java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -24,6 +25,10 @@ public abstract class Plugin {
|
|||||||
* A hash map of operators mapped to their string names.
|
* A hash map of operators mapped to their string names.
|
||||||
*/
|
*/
|
||||||
private Map<String, Operator> operators;
|
private Map<String, Operator> operators;
|
||||||
|
/**
|
||||||
|
* A hash map of operators mapped to their string names.
|
||||||
|
*/
|
||||||
|
private Map<String, Class<? extends NumberInterface>> numbers;
|
||||||
/**
|
/**
|
||||||
* 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,
|
||||||
@@ -44,6 +49,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<>();
|
||||||
enabled = false;
|
enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +69,14 @@ public abstract class Plugin {
|
|||||||
return operators.keySet();
|
return operators.keySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of all numbers provided by this plugin.
|
||||||
|
* @return the list of registered numbers.
|
||||||
|
*/
|
||||||
|
public final Set<String> providedNumbers(){
|
||||||
|
return numbers.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a function under the given function name.
|
* Gets a function under the given function name.
|
||||||
* @param functionName the name of the function to get
|
* @param functionName the name of the function to get
|
||||||
@@ -81,6 +95,15 @@ public abstract class Plugin {
|
|||||||
return operators.get(operatorName);
|
return operators.get(operatorName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the class under the given name.
|
||||||
|
* @param numberName the name of the class.
|
||||||
|
* @return the class, or null if the plugin doesn't provide it.
|
||||||
|
*/
|
||||||
|
public final Class<? extends NumberInterface> getNumber(String numberName){
|
||||||
|
return numbers.get(numberName);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables the function, loading the necessary instances
|
* Enables the function, loading the necessary instances
|
||||||
* of functions.
|
* of functions.
|
||||||
@@ -124,6 +147,18 @@ public abstract class Plugin {
|
|||||||
operators.put(name, operator);
|
operators.put(name, operator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be used in load(). Registers a number class
|
||||||
|
* with the plugin internally, which makes it possible
|
||||||
|
* 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 toRegister the class to register.
|
||||||
|
*/
|
||||||
|
protected final void registerNumber(String name, Class<? extends NumberInterface> toRegister){
|
||||||
|
numbers.put(name, toRegister);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches the PluginManager for the given function name.
|
* Searches the PluginManager for the given function name.
|
||||||
* This can be used by the plugins internally in order to call functions
|
* This can be used by the plugins internally in order to call functions
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
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 java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -26,10 +28,15 @@ public class PluginManager {
|
|||||||
*/
|
*/
|
||||||
private Map<String, Function> cachedFunctions;
|
private Map<String, Function> cachedFunctions;
|
||||||
/**
|
/**
|
||||||
* List of operators tha have been cached,
|
* List of operators that have been cached,
|
||||||
* that is, found in a plugin and returned.
|
* that is, found in a plugin and returned.
|
||||||
*/
|
*/
|
||||||
private Map<String, Operator> cachedOperators;
|
private Map<String, Operator> cachedOperators;
|
||||||
|
/**
|
||||||
|
* List of registered number implementations that have
|
||||||
|
* been cached, that is, found in a plugin and returned.
|
||||||
|
*/
|
||||||
|
private Map<String, Class<? extends NumberInterface>> cachedNumbers;
|
||||||
/**
|
/**
|
||||||
* List of all functions loaded by the plugins.
|
* List of all functions loaded by the plugins.
|
||||||
*/
|
*/
|
||||||
@@ -38,21 +45,33 @@ public class PluginManager {
|
|||||||
* List of all operators loaded by the plugins.
|
* List of all operators loaded by the plugins.
|
||||||
*/
|
*/
|
||||||
private Set<String> allOperators;
|
private Set<String> allOperators;
|
||||||
|
/**
|
||||||
|
* List of all numbers loaded by the plugins.
|
||||||
|
*/
|
||||||
|
private Set<String> allNumbers;
|
||||||
/**
|
/**
|
||||||
* 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 instance of Abacus that is used to interact with its other
|
||||||
|
* components.
|
||||||
|
*/
|
||||||
|
private Abacus abacus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new plugin manager.
|
* Creates a new plugin manager.
|
||||||
*/
|
*/
|
||||||
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<>();
|
||||||
allFunctions = new HashSet<>();
|
allFunctions = new HashSet<>();
|
||||||
allOperators = new HashSet<>();
|
allOperators = new HashSet<>();
|
||||||
|
allNumbers = new HashSet<>();
|
||||||
listeners = new HashSet<>();
|
listeners = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +123,15 @@ public class PluginManager {
|
|||||||
return searchCached(plugins, cachedOperators, Plugin::providedOperators, Plugin::getOperator, name);
|
return searchCached(plugins, cachedOperators, Plugin::providedOperators, Plugin::getOperator, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a numer implementation under the given name.
|
||||||
|
* @param name the name of the implementation.
|
||||||
|
* @return the implementation class
|
||||||
|
*/
|
||||||
|
public Class<? extends NumberInterface> numberFor(String name){
|
||||||
|
return searchCached(plugins, cachedNumbers, Plugin::providedNumbers, Plugin::getNumber, name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an instance of Plugin that already has been instantiated.
|
* Adds an instance of Plugin that already has been instantiated.
|
||||||
* @param plugin the plugin to add.
|
* @param plugin the plugin to add.
|
||||||
@@ -136,6 +164,7 @@ public class PluginManager {
|
|||||||
for(Plugin plugin : plugins){
|
for(Plugin plugin : plugins){
|
||||||
allFunctions.addAll(plugin.providedFunctions());
|
allFunctions.addAll(plugin.providedFunctions());
|
||||||
allOperators.addAll(plugin.providedOperators());
|
allOperators.addAll(plugin.providedOperators());
|
||||||
|
allNumbers.addAll(plugin.providedNumbers());
|
||||||
}
|
}
|
||||||
listeners.forEach(e -> e.onLoad(this));
|
listeners.forEach(e -> e.onLoad(this));
|
||||||
}
|
}
|
||||||
@@ -147,6 +176,7 @@ public class PluginManager {
|
|||||||
for(Plugin plugin : plugins) plugin.disable();
|
for(Plugin plugin : plugins) plugin.disable();
|
||||||
allFunctions.clear();
|
allFunctions.clear();
|
||||||
allOperators.clear();
|
allOperators.clear();
|
||||||
|
allNumbers.clear();
|
||||||
listeners.forEach(e -> e.onUnload(this));
|
listeners.forEach(e -> e.onUnload(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +204,14 @@ public class PluginManager {
|
|||||||
return allOperators;
|
return allOperators;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the number implementations loaded by the Plugin Manager
|
||||||
|
* @return the set of all implementations that were loaded
|
||||||
|
*/
|
||||||
|
public Set<String> getAllNumbers() {
|
||||||
|
return allNumbers;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a plugin change listener to this plugin manager.
|
* Adds a plugin change listener to this plugin manager.
|
||||||
* @param listener the listener to add.
|
* @param listener the listener to add.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.nwapw.abacus.function.OperatorAssociativity;
|
|||||||
import org.nwapw.abacus.function.OperatorType;
|
import org.nwapw.abacus.function.OperatorType;
|
||||||
import org.nwapw.abacus.number.NaiveNumber;
|
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.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
@@ -21,6 +22,9 @@ public class StandardPlugin extends Plugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
|
registerNumber("naive", NaiveNumber.class);
|
||||||
|
registerNumber("precise", PreciseNumber.class);
|
||||||
|
|
||||||
registerOperator("+", new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0, new Function() {
|
registerOperator("+", new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0, new Function() {
|
||||||
@Override
|
@Override
|
||||||
protected boolean matchesParams(NumberInterface[] params) {
|
protected boolean matchesParams(NumberInterface[] params) {
|
||||||
@@ -292,7 +296,7 @@ public class StandardPlugin extends Plugin {
|
|||||||
* @return the maximum error.
|
* @return the maximum error.
|
||||||
*/
|
*/
|
||||||
private NumberInterface getMaxError(NumberInterface number){
|
private NumberInterface getMaxError(NumberInterface number){
|
||||||
return (new NaiveNumber(10)).promoteTo(number.getClass()).intPow(-number.precision());
|
return (new NaiveNumber(10)).promoteTo(number.getClass()).intPow(-number.getMaxPrecision());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package org.nwapw.abacus.tree;
|
package org.nwapw.abacus.tree;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.Abacus;
|
||||||
import org.nwapw.abacus.function.Function;
|
import org.nwapw.abacus.function.Function;
|
||||||
import org.nwapw.abacus.number.NumberInterface;
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
import org.nwapw.abacus.plugin.PluginManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reducer implementation that turns a tree into a single number.
|
* A reducer implementation that turns a tree into a single number.
|
||||||
@@ -13,14 +13,14 @@ public class NumberReducer implements Reducer<NumberInterface> {
|
|||||||
/**
|
/**
|
||||||
* The plugin manager from which to draw the functions.
|
* The plugin manager from which to draw the functions.
|
||||||
*/
|
*/
|
||||||
private PluginManager manager;
|
private Abacus abacus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new number reducer with the given plugin manager.
|
* Creates a new number reducer.
|
||||||
* @param manager the plugin manager.
|
* @param abacus the calculator instance.
|
||||||
*/
|
*/
|
||||||
public NumberReducer(PluginManager manager){
|
public NumberReducer(Abacus abacus){
|
||||||
this.manager = manager;
|
this.abacus = abacus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -30,12 +30,12 @@ public class NumberReducer implements Reducer<NumberInterface> {
|
|||||||
} else if(node instanceof BinaryInfixNode){
|
} else if(node instanceof BinaryInfixNode){
|
||||||
NumberInterface left = (NumberInterface) children[0];
|
NumberInterface left = (NumberInterface) children[0];
|
||||||
NumberInterface right = (NumberInterface) children[1];
|
NumberInterface right = (NumberInterface) children[1];
|
||||||
Function function = manager.operatorFor(((BinaryInfixNode) node).getOperation()).getFunction();
|
Function function = abacus.getPluginManager().operatorFor(((BinaryInfixNode) 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 UnaryPrefixNode) {
|
||||||
NumberInterface child = (NumberInterface) children[0];
|
NumberInterface child = (NumberInterface) children[0];
|
||||||
Function functionn = manager.operatorFor(((UnaryPrefixNode) node).getOperation()).getFunction();
|
Function functionn = abacus.getPluginManager().operatorFor(((UnaryPrefixNode) 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){
|
||||||
@@ -43,7 +43,7 @@ public class NumberReducer implements Reducer<NumberInterface> {
|
|||||||
for(int i = 0; i < convertedChildren.length; i++){
|
for(int i = 0; i < convertedChildren.length; i++){
|
||||||
convertedChildren[i] = (NumberInterface) children[i];
|
convertedChildren[i] = (NumberInterface) children[i];
|
||||||
}
|
}
|
||||||
Function function = manager.functionFor(((FunctionNode) node).getFunction());
|
Function function = abacus.getPluginManager().functionFor(((FunctionNode) node).getFunction());
|
||||||
if(function == null) return null;
|
if(function == null) return null;
|
||||||
return function.apply(convertedChildren);
|
return function.apply(convertedChildren);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
package org.nwapw.abacus.window;
|
package org.nwapw.abacus.window;
|
||||||
|
|
||||||
import org.nwapw.abacus.function.Operator;
|
import org.nwapw.abacus.Abacus;
|
||||||
import org.nwapw.abacus.number.NumberInterface;
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
import org.nwapw.abacus.plugin.PluginListener;
|
|
||||||
import org.nwapw.abacus.plugin.PluginManager;
|
|
||||||
import org.nwapw.abacus.tree.NumberReducer;
|
|
||||||
import org.nwapw.abacus.tree.TreeBuilder;
|
|
||||||
import org.nwapw.abacus.tree.TreeNode;
|
import org.nwapw.abacus.tree.TreeNode;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
@@ -18,7 +14,7 @@ import java.awt.event.MouseEvent;
|
|||||||
/**
|
/**
|
||||||
* The main UI window for the calculator.
|
* The main UI window for the calculator.
|
||||||
*/
|
*/
|
||||||
public class Window extends JFrame implements PluginListener {
|
public class Window extends JFrame {
|
||||||
|
|
||||||
private static final String CALC_STRING = "Calculate";
|
private static final String CALC_STRING = "Calculate";
|
||||||
private static final String SYNTAX_ERR_STRING = "Syntax Error";
|
private static final String SYNTAX_ERR_STRING = "Syntax Error";
|
||||||
@@ -47,18 +43,10 @@ public class Window extends JFrame implements PluginListener {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The plugin manager used to retrieve functions.
|
* The instance of the Abacus class, used
|
||||||
|
* for interaction with plugins and configuration.
|
||||||
*/
|
*/
|
||||||
private PluginManager manager;
|
private Abacus abacus;
|
||||||
/**
|
|
||||||
* The builder used to construct the parse trees.
|
|
||||||
*/
|
|
||||||
private TreeBuilder builder;
|
|
||||||
/**
|
|
||||||
* The reducer used to evaluate the tree.
|
|
||||||
*/
|
|
||||||
private NumberReducer reducer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The last output by the calculator.
|
* The last output by the calculator.
|
||||||
*/
|
*/
|
||||||
@@ -130,15 +118,14 @@ public class Window extends JFrame implements PluginListener {
|
|||||||
* Action listener that causes the input to be evaluated.
|
* Action listener that causes the input to be evaluated.
|
||||||
*/
|
*/
|
||||||
private ActionListener evaluateListener = (event) -> {
|
private ActionListener evaluateListener = (event) -> {
|
||||||
if(builder == null) return;
|
TreeNode parsedExpression = abacus.parseString(inputField.getText());
|
||||||
TreeNode parsedExpression = builder.fromString(inputField.getText());
|
|
||||||
if(parsedExpression == null){
|
if(parsedExpression == null){
|
||||||
lastOutputArea.setText(SYNTAX_ERR_STRING);
|
lastOutputArea.setText(SYNTAX_ERR_STRING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
NumberInterface numberInterface = parsedExpression.reduce(reducer);
|
NumberInterface numberInterface = abacus.evaluateTree(parsedExpression);
|
||||||
if(numberInterface == null){
|
if(numberInterface == null) {
|
||||||
lastOutputArea.setText(EVAL_ERR_STRING);;
|
lastOutputArea.setText(EVAL_ERR_STRING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastOutput = numberInterface.toString();
|
lastOutput = numberInterface.toString();
|
||||||
@@ -159,13 +146,11 @@ public class Window extends JFrame implements PluginListener {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new window with the given manager.
|
* Creates a new window with the given manager.
|
||||||
* @param manager the manager to use.
|
* @param abacus the calculator instance to interact with other components.
|
||||||
*/
|
*/
|
||||||
public Window(PluginManager manager){
|
public Window(Abacus abacus){
|
||||||
this();
|
this();
|
||||||
this.manager = manager;
|
this.abacus = abacus;
|
||||||
manager.addListener(this);
|
|
||||||
reducer = new NumberReducer(manager);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -261,23 +246,4 @@ public class Window extends JFrame implements PluginListener {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoad(PluginManager manager) {
|
|
||||||
builder = new TreeBuilder();
|
|
||||||
for(String function : manager.getAllFunctions()){
|
|
||||||
builder.registerFunction(function);
|
|
||||||
}
|
|
||||||
for(String operator : manager.getAllOperators()){
|
|
||||||
Operator operatorObject = manager.operatorFor(operator);
|
|
||||||
builder.registerOperator(operator,
|
|
||||||
operatorObject.getAssociativity(),
|
|
||||||
operatorObject.getType(),
|
|
||||||
operatorObject.getPrecedence());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUnload(PluginManager manager) {
|
|
||||||
builder = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user