diff --git a/src/org/nwapw/abacus/Abacus.java b/src/org/nwapw/abacus/Abacus.java index 6397157..08f4ba5 100644 --- a/src/org/nwapw/abacus/Abacus.java +++ b/src/org/nwapw/abacus/Abacus.java @@ -25,6 +25,7 @@ public class Abacus { manager.addInstantiated(new StandardPlugin(manager)); mainUi = new Window(manager); mainUi.setVisible(true); + manager.load(); } public static void main(String[] args){ diff --git a/src/org/nwapw/abacus/function/Operator.java b/src/org/nwapw/abacus/function/Operator.java new file mode 100644 index 0000000..bd12495 --- /dev/null +++ b/src/org/nwapw/abacus/function/Operator.java @@ -0,0 +1,57 @@ +package org.nwapw.abacus.function; + +/** + * A class that represents a single infix operator. + */ +public abstract class Operator { + + /** + * The associativity of the operator. + */ + private OperatorAssociativity associativity; + /** + * The precedence of the operator. + */ + private int precedence; + /** + * The function that is called by this operator. + */ + private Function function; + + /** + * Creates a new operator with the given parameters. + * @param associativity the associativity of the operator. + * @param precedence the precedence of the operator. + * @param function the function that the operator calls. + */ + public Operator(OperatorAssociativity associativity, int precedence, Function function){ + this.associativity = associativity; + this.precedence = precedence; + this.function = function; + } + + /** + * Gets the operator's associativity. + * @return the associativity. + */ + public OperatorAssociativity getAssociativity() { + return associativity; + } + + /** + * Gets the operator's precedence. + * @return the precedence. + */ + public int getPrecedence() { + return precedence; + } + + /** + * Gets the operator's function. + * @return the function. + */ + public Function getFunction() { + return function; + } + +} diff --git a/src/org/nwapw/abacus/tree/OperatorAssociativity.java b/src/org/nwapw/abacus/function/OperatorAssociativity.java similarity index 77% rename from src/org/nwapw/abacus/tree/OperatorAssociativity.java rename to src/org/nwapw/abacus/function/OperatorAssociativity.java index 1d7f6df..237b918 100644 --- a/src/org/nwapw/abacus/tree/OperatorAssociativity.java +++ b/src/org/nwapw/abacus/function/OperatorAssociativity.java @@ -1,4 +1,4 @@ -package org.nwapw.abacus.tree; +package org.nwapw.abacus.function; /** * Enum to represent the associativity of an operator. diff --git a/src/org/nwapw/abacus/lexing/Lexer.java b/src/org/nwapw/abacus/lexing/Lexer.java index 1a3cab8..213330a 100644 --- a/src/org/nwapw/abacus/lexing/Lexer.java +++ b/src/org/nwapw/abacus/lexing/Lexer.java @@ -5,9 +5,7 @@ import org.nwapw.abacus.lexing.pattern.Match; import org.nwapw.abacus.lexing.pattern.Pattern; import org.nwapw.abacus.lexing.pattern.PatternNode; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashSet; +import java.util.*; /** * A lexer that can generate tokens of a given type given a list of regular expressions @@ -16,16 +14,53 @@ import java.util.HashSet; */ public class Lexer { + /** + * An entry that represents a pattern that has been registered with the lexer. + * @param the type used to identify the pattern. + */ + private static class PatternEntry{ + /** + * The name of the entry. + */ + public String name; + /** + * The id of the entry. + */ + public T id; + + /** + * Creates a new pattern entry with the given name and id. + * @param name the name of the pattern entry. + * @param id the id of the pattern entry. + */ + public PatternEntry(String name, T id){ + this.name = name; + this.id = id; + } + + @Override + public int hashCode() { + return Objects.hash(name, id); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof PatternEntry && + ((PatternEntry) obj).name.equals(name) && + ((PatternEntry) obj).id.equals(id); + } + } + /** * The registered patterns. */ - private ArrayList> patterns; + private HashMap, Pattern> patterns; /** * Creates a new lexer with no registered patterns. */ public Lexer(){ - patterns = new ArrayList<>(); + patterns = new HashMap<>(); } /** @@ -35,7 +70,16 @@ public class Lexer { */ public void register(String pattern, T id){ Pattern compiledPattern = new Pattern<>(pattern, id); - if(compiledPattern.getHead() != null) patterns.add(compiledPattern); + if(compiledPattern.getHead() != null) patterns.put(new PatternEntry<>(pattern, id), compiledPattern); + } + + /** + * Unregisters a pattern. + * @param pattern the pattern to unregister + * @param id the ID by which to identify the pattern. + */ + public void unregister(String pattern, T id){ + patterns.remove(new PatternEntry<>(pattern, id)); } /** @@ -50,7 +94,7 @@ public class Lexer { HashSet> currentSet = new HashSet<>(); HashSet> futureSet = new HashSet<>(); int index = startAt; - for(Pattern pattern : patterns){ + for(Pattern pattern : patterns.values()){ pattern.getHead().addInto(currentSet); } while(!currentSet.isEmpty()){ diff --git a/src/org/nwapw/abacus/plugin/Plugin.java b/src/org/nwapw/abacus/plugin/Plugin.java index 4f662de..9e87be2 100644 --- a/src/org/nwapw/abacus/plugin/Plugin.java +++ b/src/org/nwapw/abacus/plugin/Plugin.java @@ -1,8 +1,10 @@ package org.nwapw.abacus.plugin; import org.nwapw.abacus.function.Function; +import org.nwapw.abacus.function.Operator; import java.util.HashMap; +import java.util.Set; /** * A plugin class that can be externally implemented and loaded via the @@ -17,11 +19,19 @@ public abstract class Plugin { * A hash map of functions mapped to their string names. */ private HashMap functions; + /** + * A hash map of operators mapped to their string names. + */ + private HashMap operators; /** * The plugin manager in which to search for functions * not inside this package, */ private PluginManager manager; + /** + * Whether this plugin has been loaded. + */ + private boolean enabled; private Plugin(){ } @@ -32,15 +42,24 @@ public abstract class Plugin { public Plugin(PluginManager manager) { this.manager = manager; functions = new HashMap<>(); + operators = new HashMap<>(); + enabled = false; } /** - * Determines whether the current plugin provides the given function name. - * @param functionName the name of the function provided. - * @return true of the function exists, false if it doesn't. + * Gets the list of functions provided by this plugin. + * @return the list of registered functions. */ - public final boolean hasFunction(String functionName) { - return functions.containsKey(functionName); + public final Set providedFunctions(){ + return functions.keySet(); + } + + /** + * Gets the list of functions provided by this plugin. + * @return the list of registered functions. + */ + public final Set providedOperators(){ + return operators.keySet(); } /** @@ -52,37 +71,91 @@ public abstract class Plugin { return functions.get(functionName); } + /** + * Gets an operator under the given operator name. + * @param operatorName the name of the operator to get. + * @return the operator, or null if this plugin doesn't provide it. + */ + public final Operator getOperator(String operatorName) { + return operators.get(operatorName); + } + + /** + * Enables the function, loading the necessary instances + * of functions. + */ + public final void enable(){ + if(enabled) return; + onEnable(); + enabled = true; + } + + /** + * Disables the plugin, clearing loaded data store by default + * and calling its disable() method. + */ + public final void disable(){ + if(!enabled) return; + onDisable(); + functions.clear(); + operators.clear(); + enabled = false; + } + /** * To be used in load(). Registers a function abstract class with the * plugin internally, which makes it accessible to the plugin manager. * @param name the name to register by. * @param toRegister the function implementation. - * @return true if the function was registered successfully, false if not. */ - protected final boolean registerFunction(String name, Function toRegister) { - if(functionFor(name) == null){ - functions.put(name, toRegister); - return true; - } - return false; + protected final void registerFunction(String name, Function toRegister) { + functions.put(name, toRegister); + } + + /** + * To be used in load(). Registers an operator abstract class + * with the plugin internally, which makes it accessible to + * the plugin manager. + * @param name the name of the operator. + * @param operator the operator to register. + */ + protected final void registerOperator(String name, Operator operator) { + operators.put(name, operator); } /** * Searches the PluginManager for the given function name. * This can be used by the plugins internally in order to call functions * they do not provide. - * @param name then name for which to search + * @param name the name for which to search * @return the resulting function, or null if none was found for that name. */ protected final Function functionFor(String name) { return manager.functionFor(name); } + /** + * Searches the PluginManager for the given operator name. + * This can be used by the plugins internally in order to call + * operations they do not provide. + * @param name the name for which to search + * @return the resulting operator, or null if none was found for that name. + */ + protected final Operator operatorFor(String name) { + return manager.operatorFor(name); + } + /** * Abstract method to be overridden by plugin implementation, in which the plugins * are supposed to register the functions they provide and do any other * necessary setup. */ - public abstract void load(); + public abstract void onEnable(); + + /** + * Abstract method overridden by the plugin implementation, in which the plugins + * are supposed to dispose of loaded functions, operators, and macros. + */ + public abstract void onDisable(); } diff --git a/src/org/nwapw/abacus/plugin/PluginListener.java b/src/org/nwapw/abacus/plugin/PluginListener.java new file mode 100644 index 0000000..bfe00ee --- /dev/null +++ b/src/org/nwapw/abacus/plugin/PluginListener.java @@ -0,0 +1,20 @@ +package org.nwapw.abacus.plugin; + +/** + * A listener that responds to changes in the PluginManager. + */ +public interface PluginListener { + + /** + * Called when the PluginManager loads plugins. + * @param manager the manager that fired the event. + */ + public void onLoad(PluginManager manager); + + /** + * Called when the PluginManager unloads all its plugins. + * @param manager the manager that fired the event. + */ + public void onUnload(PluginManager manager); + +} diff --git a/src/org/nwapw/abacus/plugin/PluginManager.java b/src/org/nwapw/abacus/plugin/PluginManager.java index 9715597..f654332 100644 --- a/src/org/nwapw/abacus/plugin/PluginManager.java +++ b/src/org/nwapw/abacus/plugin/PluginManager.java @@ -1,10 +1,10 @@ package org.nwapw.abacus.plugin; import org.nwapw.abacus.function.Function; +import org.nwapw.abacus.function.Operator; import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.HashMap; +import java.util.*; /** * A class that controls instances of plugins, allowing for them @@ -21,6 +21,23 @@ public class PluginManager { * that is, found in a plugin and returned. */ private HashMap cachedFunctions; + /** + * List of operators tha have been cached, + * that is, found in a plugin and returned. + */ + private HashMap cachedOperators; + /** + * List of all functions loaded by the plugins. + */ + private HashSet allFunctions; + /** + * List of all operators loaded by the plugins. + */ + private HashSet allOperators; + /** + * The list of plugin listeners attached to this instance. + */ + private HashSet listeners; /** * Creates a new plugin manager. @@ -28,27 +45,58 @@ public class PluginManager { public PluginManager(){ plugins = new ArrayList<>(); cachedFunctions = new HashMap<>(); + cachedOperators = new HashMap<>(); + allFunctions = new HashSet<>(); + allOperators = new HashSet<>(); + listeners = new HashSet<>(); } + /** + * Searches the plugin list for a certain value, retrieving the Plugin's + * list of items of the type using the setFunction and getting the value + * of it is available via getFunction. If the value is contained + * in the cache, it returns the cached value instead. + * @param plugins the plugin list to search. + * @param cache the cache to use + * @param setFunction the function to retrieve a set of available T's from the plugin + * @param getFunction the function to get the T value under the given name + * @param name the name to search for + * @param the type of element being search + * @return the retrieved element, or null if it was not found. + */ + private static T searchCached(Collection plugins, Map cache, + java.util.function.Function> setFunction, + java.util.function.BiFunction getFunction, + String name){ + if(cache.containsKey(name)) return cache.get(name); + + T loadedValue = null; + for(Plugin plugin : plugins){ + if(setFunction.apply(plugin).contains(name)){ + loadedValue = getFunction.apply(plugin, name); + break; + } + } + + cache.put(name, loadedValue); + return loadedValue; + } /** * Gets a function under the given name. * @param name the name of the function * @return the function under the given name. */ public Function functionFor(String name){ - if(cachedFunctions.containsKey(name)) { - return cachedFunctions.get(name); - } + return searchCached(plugins, cachedFunctions, Plugin::providedFunctions, Plugin::getFunction, name); + } - Function loadedFunction = null; - for(Plugin plugin : plugins){ - if(plugin.hasFunction(name)){ - loadedFunction = plugin.getFunction(name); - break; - } - } - cachedFunctions.put(name, loadedFunction); - return loadedFunction; + /** + * Gets an operator under the given name. + * @param name the name of the operator. + * @return the operator under the given name. + */ + public Operator operatorFor(String name){ + return searchCached(plugins, cachedOperators, Plugin::providedOperators, Plugin::getOperator, name); } /** @@ -56,8 +104,6 @@ public class PluginManager { * @param plugin the plugin to add. */ public void addInstantiated(Plugin plugin){ - plugin.load(); - cachedFunctions.clear(); plugins.add(plugin); } @@ -75,4 +121,66 @@ public class PluginManager { } } + /** + * Loads all the plugins in the PluginManager. + */ + public void load(){ + for(Plugin plugin : plugins) plugin.enable(); + for(Plugin plugin : plugins){ + allFunctions.addAll(plugin.providedFunctions()); + allOperators.addAll(plugin.providedOperators()); + } + listeners.forEach(e -> e.onLoad(this)); + } + + /** + * Unloads all the plugins in the PluginManager. + */ + public void unload(){ + for(Plugin plugin : plugins) plugin.disable(); + allFunctions.clear(); + allOperators.clear(); + listeners.forEach(e -> e.onUnload(this)); + } + + /** + * Reloads all the plugins in the PluginManager. + */ + public void reload(){ + unload(); + reload(); + } + + /** + * Gets all the functions loaded by the Plugin Manager. + * @return the set of all functions that were loaded. + */ + public HashSet getAllFunctions() { + return allFunctions; + } + + /** + * Gets all the operators loaded by the Plugin Manager. + * @return the set of all operators that were loaded. + */ + public HashSet getAllOperators() { + return allOperators; + } + + /** + * Adds a plugin change listener to this plugin manager. + * @param listener the listener to add. + */ + public void addListener(PluginListener listener){ + listeners.add(listener); + } + + /** + * Remove the plugin change listener from this plugin manager. + * @param listener the listener to remove. + */ + public void removeListener(PluginListener listener){ + listeners.remove(listener); + } + } diff --git a/src/org/nwapw/abacus/plugin/StandardPlugin.java b/src/org/nwapw/abacus/plugin/StandardPlugin.java index 8058e49..a864ff6 100755 --- a/src/org/nwapw/abacus/plugin/StandardPlugin.java +++ b/src/org/nwapw/abacus/plugin/StandardPlugin.java @@ -20,7 +20,7 @@ public class StandardPlugin extends Plugin { } @Override - public void load() { + public void onEnable() { registerFunction("+", new Function() { @Override protected boolean matchesParams(NumberInterface[] params) { @@ -234,11 +234,16 @@ public class StandardPlugin extends Plugin { }); } + @Override + public void onDisable() { + + } + /** * Returns the nth term of the Taylor series (centered at 0) of e^x * @param n the term required (n >= 0). * @param x the real number at which the series is evaluated. - * @return + * @return the nth term of the series. */ private NumberInterface getExpSeriesTerm(int n, NumberInterface x){ return x.intPow(n).divide(this.getFunction("!").apply((new NaiveNumber(n)).promoteTo(x.getClass()))); @@ -249,7 +254,7 @@ public class StandardPlugin extends Plugin { * such that the error is at most maxError. * @param maxError Maximum error permissible (This should probably be positive.) * @param x where the function is evaluated. - * @return + * @return the number of terms needed to evaluated the exponential function. */ private int getNTermsExp(NumberInterface maxError, NumberInterface x) { //We need n such that |x^(n+1)| <= (n+1)! * maxError @@ -284,7 +289,7 @@ public class StandardPlugin extends Plugin { /** * Returns the maximum error based on the precision of the class of number. * @param number Any instance of the NumberInterface in question (should return an appropriate precision). - * @return + * @return the maximum error. */ private NumberInterface getMaxError(NumberInterface number){ return (new NaiveNumber(10)).promoteTo(number.getClass()).intPow(-number.precision()); diff --git a/src/org/nwapw/abacus/tree/OpNode.java b/src/org/nwapw/abacus/tree/OpNode.java index e6565f7..475affc 100644 --- a/src/org/nwapw/abacus/tree/OpNode.java +++ b/src/org/nwapw/abacus/tree/OpNode.java @@ -95,13 +95,6 @@ public class OpNode extends TreeNode { String leftString = left != null ? left.toString() : "null"; String rightString = right != null ? right.toString() : "null"; - if(right != null && right instanceof OpNode){ - if(TreeNode.precedenceMap.get(((OpNode) right).getOperation()) > - TreeNode.precedenceMap.get(operation)) { - rightString = "(" + rightString + ")"; - } - } - - return leftString + operation + rightString; + return "(" + leftString + operation + rightString + ")"; } } diff --git a/src/org/nwapw/abacus/tree/TokenType.java b/src/org/nwapw/abacus/tree/TokenType.java index 06301ae..1d70998 100644 --- a/src/org/nwapw/abacus/tree/TokenType.java +++ b/src/org/nwapw/abacus/tree/TokenType.java @@ -6,7 +6,8 @@ package org.nwapw.abacus.tree; */ public enum TokenType { - INTERNAL_FUNCTION_END(-1), INTERNAL_FUNCTION_START(-1), ANY(0), COMMA(1), OP(2), NUM(3), WORD(4), OPEN_PARENTH(5), CLOSE_PARENTH(6); + INTERNAL_FUNCTION_END(-1), + ANY(0), COMMA(1), OP(2), NUM(3), FUNCTION(4), OPEN_PARENTH(5), CLOSE_PARENTH(6); /** * The priority by which this token gets sorted. diff --git a/src/org/nwapw/abacus/tree/TreeBuilder.java b/src/org/nwapw/abacus/tree/TreeBuilder.java new file mode 100644 index 0000000..de1a8ee --- /dev/null +++ b/src/org/nwapw/abacus/tree/TreeBuilder.java @@ -0,0 +1,178 @@ +package org.nwapw.abacus.tree; + +import org.nwapw.abacus.function.OperatorAssociativity; +import org.nwapw.abacus.lexing.Lexer; +import org.nwapw.abacus.lexing.pattern.Match; + +import java.util.*; + +/** + * The builder responsible for turning strings into trees. + */ +public class TreeBuilder { + + /** + * The lexer used to get the input tokens. + */ + private Lexer lexer; + /** + * The map of operator precedences. + */ + private HashMap precedenceMap; + /** + * The map of operator associativity. + */ + private HashMap associativityMap; + + /** + * Comparator used to sort token types. + */ + protected static Comparator tokenSorter = Comparator.comparingInt(e -> e.priority); + + /** + * Creates a new TreeBuilder. + */ + public TreeBuilder(){ + lexer = new Lexer(){{ + register(",", TokenType.COMMA); + register("[0-9]+(\\.[0-9]+)?", TokenType.NUM); + register("\\(", TokenType.OPEN_PARENTH); + register("\\)", TokenType.CLOSE_PARENTH); + }}; + precedenceMap = new HashMap<>(); + associativityMap = new HashMap<>(); + } + + /** + * Registers a function with the TreeBuilder. + * @param function the function to register. + */ + public void registerFunction(String function){ + lexer.register(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, int precedence, OperatorAssociativity associativity){ + lexer.register(operator, TokenType.OP); + precedenceMap.put(operator, precedence); + associativityMap.put(operator, associativity); + } + + /** + * Tokenizes a string, converting it into matches + * @param string the string to tokenize. + * @return the list of tokens produced. + */ + public ArrayList> tokenize(String string){ + return lexer.lexAll(string, 0, tokenSorter); + } + + /** + * Rearranges tokens into a postfix list, using Shunting Yard. + * @param source the source string. + * @param from the tokens to be rearranged. + * @return the resulting list of rearranged tokens. + */ + public ArrayList> intoPostfix(String source, ArrayList> from){ + ArrayList> output = new ArrayList<>(); + Stack> tokenStack = new Stack<>(); + while(!from.isEmpty()){ + Match match = from.remove(0); + TokenType matchType = match.getType(); + if(matchType == TokenType.NUM) { + output.add(match); + } else if(matchType == TokenType.FUNCTION) { + output.add(new Match<>(0, 0, TokenType.INTERNAL_FUNCTION_END)); + tokenStack.push(match); + } else if(matchType == TokenType.OP){ + String tokenString = source.substring(match.getFrom(), match.getTo()); + int precedence = precedenceMap.get(tokenString); + OperatorAssociativity associativity = associativityMap.get(tokenString); + + while(!tokenStack.empty()) { + Match otherMatch = tokenStack.peek(); + TokenType otherMatchType = otherMatch.getType(); + if(otherMatchType != TokenType.OP) break; + + int otherPrecdence = precedenceMap.get(source.substring(otherMatch.getFrom(), otherMatch.getTo())); + if(otherPrecdence < precedence || + (associativity == OperatorAssociativity.RIGHT && otherPrecdence == precedence)) { + break; + } + output.add(tokenStack.pop()); + } + tokenStack.push(match); + } else if(matchType == TokenType.OPEN_PARENTH){ + tokenStack.push(match); + } else if(matchType == TokenType.CLOSE_PARENTH || matchType == TokenType.COMMA){ + while(!tokenStack.empty() && tokenStack.peek().getType() != TokenType.OPEN_PARENTH){ + output.add(tokenStack.pop()); + } + if(tokenStack.empty()) return null; + if(matchType == TokenType.CLOSE_PARENTH){ + tokenStack.pop(); + } + } + } + while(!tokenStack.empty()){ + Match match = tokenStack.peek(); + TokenType matchType = match.getType(); + if(!(matchType == TokenType.OP || matchType == TokenType.FUNCTION)) return null; + output.add(tokenStack.pop()); + } + return output; + } + + /** + * Constructs a tree recursively from a list of tokens. + * @param source the source string. + * @param matches the list of tokens from the source string. + * @return the construct tree expression. + */ + public TreeNode fromStringRecursive(String source, ArrayList> matches){ + if(matches.size() == 0) return null; + Match match = matches.remove(0); + TokenType matchType = match.getType(); + if(matchType == TokenType.OP){ + TreeNode right = fromStringRecursive(source, matches); + TreeNode left = fromStringRecursive(source, matches); + if(left == null || right == null) return null; + else return new OpNode(source.substring(match.getFrom(), match.getTo()), left, right); + } else if(matchType == TokenType.NUM){ + return new NumberNode(Double.parseDouble(source.substring(match.getFrom(), match.getTo()))); + } else if(matchType == TokenType.FUNCTION){ + String functionName = source.substring(match.getFrom(), match.getTo()); + FunctionNode node = new FunctionNode(functionName); + while(!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END){ + TreeNode argument = fromStringRecursive(source, matches); + if(argument == null) return null; + node.addChild(argument); + } + if(matches.isEmpty()) return null; + matches.remove(0); + return node; + } + return null; + } + + /** + * Creates a tree node from a string. + * @param string the string to create a node from. + * @return the resulting tree. + */ + public TreeNode fromString(String string){ + ArrayList> matches = tokenize(string); + if(matches == null) return null; + matches = intoPostfix(string, matches); + if(matches == null) return null; + + Collections.reverse(matches); + return fromStringRecursive(string, matches); + } + +} diff --git a/src/org/nwapw/abacus/tree/TreeNode.java b/src/org/nwapw/abacus/tree/TreeNode.java index d2d560b..50d8991 100644 --- a/src/org/nwapw/abacus/tree/TreeNode.java +++ b/src/org/nwapw/abacus/tree/TreeNode.java @@ -1,5 +1,6 @@ package org.nwapw.abacus.tree; +import org.nwapw.abacus.function.OperatorAssociativity; import org.nwapw.abacus.lexing.Lexer; import org.nwapw.abacus.lexing.pattern.Match; @@ -11,157 +12,11 @@ import java.util.*; public abstract class TreeNode { /** - * The lexer used to lex tokens. + * The function that reduces a tree to a single vale. + * @param reducer the reducer used to reduce the tree. + * @param the type the reducer produces. + * @return the result of the reduction, or null on error. */ - protected static Lexer lexer = new Lexer(){{ - register(",", TokenType.COMMA); - register("\\+|-|\\*|/", TokenType.OP); - register("[0-9]+(\\.[0-9]+)?", TokenType.NUM); - register("[a-zA-Z]+", TokenType.WORD); - register("\\(", TokenType.OPEN_PARENTH); - register("\\)", TokenType.CLOSE_PARENTH); - }}; - /** - * A map that maps operations to their precedence. - */ - protected static HashMap precedenceMap = new HashMap(){{ - put("+", 0); - put("-", 0); - put("*", 1); - put("/", 1); - }}; - /** - * A map that maps operations to their associativity. - */ - protected static HashMap associativityMap = - new HashMap() {{ - put("+", OperatorAssociativity.LEFT); - put("-", OperatorAssociativity.LEFT); - put("*", OperatorAssociativity.LEFT); - put("/", OperatorAssociativity.LEFT); - }}; - - /** - * Comparator used to sort token types. - */ - protected static Comparator tokenSorter = Comparator.comparingInt(e -> e.priority); - - /** - * Tokenizes a string, converting it into matches - * @param string the string to tokenize. - * @return the list of tokens produced. - */ - public static ArrayList> tokenize(String string){ - return lexer.lexAll(string, 0, tokenSorter); - } - - /** - * Rearranges tokens into a postfix list, using Shunting Yard. - * @param source the source string. - * @param from the tokens to be rearranged. - * @return the resulting list of rearranged tokens. - */ - public static ArrayList> intoPostfix(String source, ArrayList> from){ - ArrayList> output = new ArrayList<>(); - Stack> tokenStack = new Stack<>(); - while(!from.isEmpty()){ - Match match = from.remove(0); - TokenType matchType = match.getType(); - if(matchType == TokenType.NUM || matchType == TokenType.WORD) { - output.add(match); - } else if(matchType == TokenType.OP){ - String tokenString = source.substring(match.getFrom(), match.getTo()); - int precedence = precedenceMap.get(tokenString); - OperatorAssociativity associativity = associativityMap.get(tokenString); - - while(!tokenStack.empty()) { - Match otherMatch = tokenStack.peek(); - if(otherMatch.getType() != TokenType.OP) break; - - int otherPrecdence = precedenceMap.get(source.substring(otherMatch.getFrom(), otherMatch.getTo())); - if(otherPrecdence < precedence || - (associativity == OperatorAssociativity.RIGHT && otherPrecdence == precedence)) { - break; - } - output.add(tokenStack.pop()); - } - tokenStack.push(match); - } else if(matchType == TokenType.OPEN_PARENTH){ - if(!output.isEmpty() && output.get(output.size() - 1).getType() == TokenType.WORD){ - tokenStack.push(output.remove(output.size() - 1)); - output.add(new Match<>(0, 0, TokenType.INTERNAL_FUNCTION_END)); - } - tokenStack.push(match); - } else if(matchType == TokenType.CLOSE_PARENTH || matchType == TokenType.COMMA){ - while(!tokenStack.empty() && tokenStack.peek().getType() != TokenType.OPEN_PARENTH){ - output.add(tokenStack.pop()); - } - if(tokenStack.empty()) return null; - if(matchType == TokenType.CLOSE_PARENTH){ - tokenStack.pop(); - if(!tokenStack.empty() && tokenStack.peek().getType() == TokenType.WORD) { - output.add(tokenStack.pop()); - output.add(new Match<>(0, 0, TokenType.INTERNAL_FUNCTION_START)); - } - } - } - } - while(!tokenStack.empty()){ - if(!(tokenStack.peek().getType() == TokenType.OP)) return null; - output.add(tokenStack.pop()); - } - return output; - } - - /** - * Constructs a tree recursively from a list of tokens. - * @param source the source string. - * @param matches the list of tokens from the source string. - * @return the construct tree expression. - */ - public static TreeNode fromStringRecursive(String source, ArrayList> matches){ - if(matches.size() == 0) return null; - Match match = matches.remove(0); - TokenType matchType = match.getType(); - if(matchType == TokenType.OP){ - TreeNode right = fromStringRecursive(source, matches); - TreeNode left = fromStringRecursive(source, matches); - if(left == null || right == null) return null; - else return new OpNode(source.substring(match.getFrom(), match.getTo()), left, right); - } else if(matchType == TokenType.NUM){ - return new NumberNode(Double.parseDouble(source.substring(match.getFrom(), match.getTo()))); - } else if(matchType == TokenType.INTERNAL_FUNCTION_START){ - if(matches.isEmpty() || matches.get(0).getType() != TokenType.WORD) return null; - Match stringName = matches.remove(0); - String functionName = source.substring(stringName.getFrom(), stringName.getTo()); - FunctionNode node = new FunctionNode(functionName); - while(!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END){ - TreeNode argument = fromStringRecursive(source, matches); - if(argument == null) return null; - node.addChild(argument); - } - if(matches.isEmpty()) return null; - matches.remove(0); - return node; - } - return null; - } - - /** - * Creates a tree node from a string. - * @param string the string to create a node from. - * @return the resulting tree. - */ - public static TreeNode fromString(String string){ - ArrayList> matches = tokenize(string); - if(matches == null) return null; - matches = intoPostfix(string, matches); - if(matches == null) return null; - - Collections.reverse(matches); - return fromStringRecursive(string, matches); - } - public abstract T reduce(Reducer reducer); } diff --git a/src/org/nwapw/abacus/window/Window.java b/src/org/nwapw/abacus/window/Window.java index d2d4891..a682e22 100644 --- a/src/org/nwapw/abacus/window/Window.java +++ b/src/org/nwapw/abacus/window/Window.java @@ -1,8 +1,11 @@ package org.nwapw.abacus.window; +import org.nwapw.abacus.function.Operator; 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 javax.swing.*; @@ -15,7 +18,7 @@ import java.awt.event.MouseEvent; /** * The main UI window for the calculator. */ -public class Window extends JFrame { +public class Window extends JFrame implements PluginListener { private static final String CALC_STRING = "Calculate"; private static final String SYNTAX_ERR_STRING = "Syntax Error"; @@ -47,6 +50,10 @@ public class Window extends JFrame { * The plugin manager used to retrieve functions. */ private PluginManager manager; + /** + * The builder used to construct the parse trees. + */ + private TreeBuilder builder; /** * The reducer used to evaluate the tree. */ @@ -123,7 +130,8 @@ public class Window extends JFrame { * Action listener that causes the input to be evaluated. */ private ActionListener evaluateListener = (event) -> { - TreeNode parsedExpression = TreeNode.fromString(inputField.getText()); + if(builder == null) return; + TreeNode parsedExpression = builder.fromString(inputField.getText()); if(parsedExpression == null){ lastOutputArea.setText(SYNTAX_ERR_STRING); return; @@ -156,6 +164,7 @@ public class Window extends JFrame { public Window(PluginManager manager){ this(); this.manager = manager; + manager.addListener(this); reducer = new NumberReducer(manager); } @@ -251,4 +260,21 @@ public class Window extends JFrame { } }); } + + @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.getPrecedence(), operatorObject.getAssociativity()); + } + } + + @Override + public void onUnload(PluginManager manager) { + builder = null; + } }