From 189f8c6e15ab5360958bb94cf17553f3dbf768da Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 27 Jul 2017 09:33:01 -0700 Subject: [PATCH 01/12] Move OperatorAssociativity into the function class. --- .../nwapw/abacus/{tree => function}/OperatorAssociativity.java | 2 +- src/org/nwapw/abacus/tree/TreeNode.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) rename src/org/nwapw/abacus/{tree => function}/OperatorAssociativity.java (77%) 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/tree/TreeNode.java b/src/org/nwapw/abacus/tree/TreeNode.java index d2d560b..93efc02 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; From 79e85832cee048d6e7f5c6e797e26b88a8568d38 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 27 Jul 2017 10:38:18 -0700 Subject: [PATCH 02/12] Add operator map to Plugin class, and use it in PluginManager. --- src/org/nwapw/abacus/plugin/Plugin.java | 61 +++++++++++++++++-- .../nwapw/abacus/plugin/PluginManager.java | 47 +++++++++----- 2 files changed, 88 insertions(+), 20 deletions(-) diff --git a/src/org/nwapw/abacus/plugin/Plugin.java b/src/org/nwapw/abacus/plugin/Plugin.java index 4f662de..db2f2d9 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,6 +19,10 @@ 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, @@ -35,12 +41,19 @@ public abstract class Plugin { } /** - * 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,6 +65,15 @@ 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); + } + /** * To be used in load(). Registers a function abstract class with the * plugin internally, which makes it accessible to the plugin manager. @@ -67,17 +89,44 @@ public abstract class Plugin { return false; } + /** + * 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. + * @return true if the operator was registered successfully, false if not. + */ + protected final boolean registerOperator(String name, Operator operator) { + if(operatorFor(name) == null){ + operators.put(name, operator); + return true; + } + return false; + } + /** * 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 diff --git a/src/org/nwapw/abacus/plugin/PluginManager.java b/src/org/nwapw/abacus/plugin/PluginManager.java index 9715597..ddfe4a4 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,11 @@ 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; /** * Creates a new plugin manager. @@ -30,25 +35,39 @@ public class PluginManager { cachedFunctions = new HashMap<>(); } + 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); } /** From 077a34c618d30077f04941fa1c7abd14020351b7 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 27 Jul 2017 10:47:11 -0700 Subject: [PATCH 03/12] Switch Lexer to use a map for patterns, to allow for removal. --- src/org/nwapw/abacus/lexing/Lexer.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/org/nwapw/abacus/lexing/Lexer.java b/src/org/nwapw/abacus/lexing/Lexer.java index 1a3cab8..7c84f16 100644 --- a/src/org/nwapw/abacus/lexing/Lexer.java +++ b/src/org/nwapw/abacus/lexing/Lexer.java @@ -7,6 +7,7 @@ import org.nwapw.abacus.lexing.pattern.PatternNode; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; /** @@ -19,13 +20,13 @@ public class Lexer { /** * The registered patterns. */ - private ArrayList> patterns; + private HashMap> patterns; /** * Creates a new lexer with no registered patterns. */ public Lexer(){ - patterns = new ArrayList<>(); + patterns = new HashMap<>(); } /** @@ -35,7 +36,7 @@ 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(pattern, compiledPattern); } /** @@ -50,7 +51,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()){ From ee1de6dc17c090be530e7f8a26ff461bbc45f416 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 27 Jul 2017 10:53:56 -0700 Subject: [PATCH 04/12] Add the operator that had been in use by Plugin and PluginManager. --- src/org/nwapw/abacus/function/Operator.java | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/org/nwapw/abacus/function/Operator.java diff --git a/src/org/nwapw/abacus/function/Operator.java b/src/org/nwapw/abacus/function/Operator.java new file mode 100644 index 0000000..18d1555 --- /dev/null +++ b/src/org/nwapw/abacus/function/Operator.java @@ -0,0 +1,27 @@ +package org.nwapw.abacus.function; + +public abstract class Operator { + + private OperatorAssociativity associativity; + private int precedence; + private Function function; + + public Operator(OperatorAssociativity associativity, int precedence, Function function){ + this.associativity = associativity; + this.precedence = precedence; + this.function = function; + } + + public OperatorAssociativity getAssociativity() { + return associativity; + } + + public int getPrecedence() { + return precedence; + } + + public Function getFunction() { + return function; + } + +} From 07dd9d0a1ad438174819f3455bb10d1d6995e1b7 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 27 Jul 2017 10:54:11 -0700 Subject: [PATCH 05/12] Support removing expressions. --- src/org/nwapw/abacus/lexing/Lexer.java | 40 ++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/org/nwapw/abacus/lexing/Lexer.java b/src/org/nwapw/abacus/lexing/Lexer.java index 7c84f16..0da217e 100644 --- a/src/org/nwapw/abacus/lexing/Lexer.java +++ b/src/org/nwapw/abacus/lexing/Lexer.java @@ -5,10 +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.HashMap; -import java.util.HashSet; +import java.util.*; /** * A lexer that can generate tokens of a given type given a list of regular expressions @@ -17,10 +14,32 @@ import java.util.HashSet; */ public class Lexer { + private static class PatternEntry{ + public String name; + public T id; + + 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 HashMap> patterns; + private HashMap, Pattern> patterns; /** * Creates a new lexer with no registered patterns. @@ -36,7 +55,16 @@ public class Lexer { */ public void register(String pattern, T id){ Pattern compiledPattern = new Pattern<>(pattern, id); - if(compiledPattern.getHead() != null) patterns.put(pattern, 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)); } /** From 78e2d50f89e759bff2a5cc96dfac10d3f9050c08 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 27 Jul 2017 10:58:11 -0700 Subject: [PATCH 06/12] Add comments and clear appropriate cache. --- src/org/nwapw/abacus/plugin/PluginManager.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/org/nwapw/abacus/plugin/PluginManager.java b/src/org/nwapw/abacus/plugin/PluginManager.java index ddfe4a4..7442b17 100644 --- a/src/org/nwapw/abacus/plugin/PluginManager.java +++ b/src/org/nwapw/abacus/plugin/PluginManager.java @@ -35,6 +35,19 @@ public class PluginManager { cachedFunctions = new HashMap<>(); } + /** + * 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, @@ -77,6 +90,7 @@ public class PluginManager { public void addInstantiated(Plugin plugin){ plugin.load(); cachedFunctions.clear(); + cachedOperators.clear(); plugins.add(plugin); } From f931b9f3225dde98803177941b206d535249115d Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 27 Jul 2017 12:53:58 -0700 Subject: [PATCH 07/12] Move parsing code into TreeBuilder, change lexing and parsing algorithms --- .../nwapw/abacus/plugin/PluginManager.java | 1 + src/org/nwapw/abacus/tree/OpNode.java | 9 +- src/org/nwapw/abacus/tree/TokenType.java | 3 +- src/org/nwapw/abacus/tree/TreeBuilder.java | 142 ++++++++++++++++ src/org/nwapw/abacus/tree/TreeNode.java | 152 ------------------ src/org/nwapw/abacus/window/Window.java | 8 +- 6 files changed, 153 insertions(+), 162 deletions(-) create mode 100644 src/org/nwapw/abacus/tree/TreeBuilder.java diff --git a/src/org/nwapw/abacus/plugin/PluginManager.java b/src/org/nwapw/abacus/plugin/PluginManager.java index 7442b17..97b421c 100644 --- a/src/org/nwapw/abacus/plugin/PluginManager.java +++ b/src/org/nwapw/abacus/plugin/PluginManager.java @@ -33,6 +33,7 @@ public class PluginManager { public PluginManager(){ plugins = new ArrayList<>(); cachedFunctions = new HashMap<>(); + cachedOperators = new HashMap<>(); } /** 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..d0560e1 --- /dev/null +++ b/src/org/nwapw/abacus/tree/TreeBuilder.java @@ -0,0 +1,142 @@ +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 org.nwapw.abacus.plugin.PluginManager; + +import java.util.*; + +public class TreeBuilder { + + private Lexer lexer; + private HashMap precedenceMap; + private HashMap associativityMap; + + /** + * Comparator used to sort token types. + */ + protected static Comparator tokenSorter = Comparator.comparingInt(e -> e.priority); + + 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<>(); + } + + /** + * 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()){ + 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 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 93efc02..a3cae09 100644 --- a/src/org/nwapw/abacus/tree/TreeNode.java +++ b/src/org/nwapw/abacus/tree/TreeNode.java @@ -11,158 +11,6 @@ import java.util.*; */ public abstract class TreeNode { - /** - * The lexer used to lex tokens. - */ - 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..e419820 100644 --- a/src/org/nwapw/abacus/window/Window.java +++ b/src/org/nwapw/abacus/window/Window.java @@ -3,6 +3,7 @@ package org.nwapw.abacus.window; import org.nwapw.abacus.number.NumberInterface; 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.*; @@ -47,6 +48,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 +128,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()); + TreeBuilder builder = new TreeBuilder(); + TreeNode parsedExpression = builder.fromString(inputField.getText()); if(parsedExpression == null){ lastOutputArea.setText(SYNTAX_ERR_STRING); return; From e6559015b37504085fe982ab58e903227dc9f46b Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 27 Jul 2017 13:26:17 -0700 Subject: [PATCH 08/12] Rename load to onLoad and add onDisable to plugin. --- src/org/nwapw/abacus/plugin/Plugin.java | 36 ++++++++++- .../nwapw/abacus/plugin/PluginManager.java | 60 ++++++++++++++++++- .../nwapw/abacus/plugin/StandardPlugin.java | 7 ++- 3 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/org/nwapw/abacus/plugin/Plugin.java b/src/org/nwapw/abacus/plugin/Plugin.java index db2f2d9..d702950 100644 --- a/src/org/nwapw/abacus/plugin/Plugin.java +++ b/src/org/nwapw/abacus/plugin/Plugin.java @@ -28,6 +28,10 @@ public abstract class Plugin { * not inside this package, */ private PluginManager manager; + /** + * Whether this plugin has been loaded. + */ + private boolean enabled; private Plugin(){ } @@ -38,6 +42,8 @@ public abstract class Plugin { public Plugin(PluginManager manager) { this.manager = manager; functions = new HashMap<>(); + operators = new HashMap<>(); + enabled = false; } /** @@ -74,6 +80,28 @@ public abstract class Plugin { 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. @@ -132,6 +160,12 @@ public abstract class Plugin { * 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/PluginManager.java b/src/org/nwapw/abacus/plugin/PluginManager.java index 97b421c..212bc7e 100644 --- a/src/org/nwapw/abacus/plugin/PluginManager.java +++ b/src/org/nwapw/abacus/plugin/PluginManager.java @@ -26,6 +26,18 @@ public class PluginManager { * 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. @@ -34,6 +46,8 @@ public class PluginManager { plugins = new ArrayList<>(); cachedFunctions = new HashMap<>(); cachedOperators = new HashMap<>(); + allFunctions = new HashSet<>(); + allOperators = new HashSet<>(); } /** @@ -89,9 +103,6 @@ public class PluginManager { * @param plugin the plugin to add. */ public void addInstantiated(Plugin plugin){ - plugin.load(); - cachedFunctions.clear(); - cachedOperators.clear(); plugins.add(plugin); } @@ -109,4 +120,47 @@ 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()); + } + } + + /** + * Unloads all the plugins in the PluginManager. + */ + public void unload(){ + for(Plugin plugin : plugins) plugin.disable(); + allFunctions.clear(); + allOperators.clear(); + } + + /** + * 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; + } } diff --git a/src/org/nwapw/abacus/plugin/StandardPlugin.java b/src/org/nwapw/abacus/plugin/StandardPlugin.java index 9881ff2..dfc920e 100755 --- a/src/org/nwapw/abacus/plugin/StandardPlugin.java +++ b/src/org/nwapw/abacus/plugin/StandardPlugin.java @@ -17,7 +17,7 @@ public class StandardPlugin extends Plugin { } @Override - public void load() { + public void onEnable() { registerFunction("+", new Function() { @Override protected boolean matchesParams(NumberInterface[] params) { @@ -108,6 +108,11 @@ 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). From f2c280766d3874a50fef982af0ba2846849a3bb6 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 27 Jul 2017 14:06:04 -0700 Subject: [PATCH 09/12] Add a PluginListener type for use in the PluginManager. --- .../nwapw/abacus/plugin/PluginListener.java | 20 +++++++++++++++++++ .../nwapw/abacus/plugin/PluginManager.java | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/org/nwapw/abacus/plugin/PluginListener.java 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 212bc7e..f654332 100644 --- a/src/org/nwapw/abacus/plugin/PluginManager.java +++ b/src/org/nwapw/abacus/plugin/PluginManager.java @@ -48,6 +48,7 @@ public class PluginManager { cachedOperators = new HashMap<>(); allFunctions = new HashSet<>(); allOperators = new HashSet<>(); + listeners = new HashSet<>(); } /** @@ -129,6 +130,7 @@ public class PluginManager { allFunctions.addAll(plugin.providedFunctions()); allOperators.addAll(plugin.providedOperators()); } + listeners.forEach(e -> e.onLoad(this)); } /** @@ -138,6 +140,7 @@ public class PluginManager { for(Plugin plugin : plugins) plugin.disable(); allFunctions.clear(); allOperators.clear(); + listeners.forEach(e -> e.onUnload(this)); } /** @@ -163,4 +166,21 @@ public class PluginManager { 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); + } + } From a211884499900c6227ff2be486f4c8ce86c435db Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 27 Jul 2017 14:06:15 -0700 Subject: [PATCH 10/12] Prevent operation lookups, as they pollute the cache. --- src/org/nwapw/abacus/plugin/Plugin.java | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/org/nwapw/abacus/plugin/Plugin.java b/src/org/nwapw/abacus/plugin/Plugin.java index d702950..9e87be2 100644 --- a/src/org/nwapw/abacus/plugin/Plugin.java +++ b/src/org/nwapw/abacus/plugin/Plugin.java @@ -107,14 +107,9 @@ public abstract class Plugin { * 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); } /** @@ -123,14 +118,9 @@ public abstract class Plugin { * the plugin manager. * @param name the name of the operator. * @param operator the operator to register. - * @return true if the operator was registered successfully, false if not. */ - protected final boolean registerOperator(String name, Operator operator) { - if(operatorFor(name) == null){ - operators.put(name, operator); - return true; - } - return false; + protected final void registerOperator(String name, Operator operator) { + operators.put(name, operator); } /** From efbd6a4c2086e00d9d61833c8f894ea718a19930 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 27 Jul 2017 14:06:25 -0700 Subject: [PATCH 11/12] Add missing return documentation. --- src/org/nwapw/abacus/plugin/StandardPlugin.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/nwapw/abacus/plugin/StandardPlugin.java b/src/org/nwapw/abacus/plugin/StandardPlugin.java index dfc920e..5faafd6 100755 --- a/src/org/nwapw/abacus/plugin/StandardPlugin.java +++ b/src/org/nwapw/abacus/plugin/StandardPlugin.java @@ -117,7 +117,7 @@ public class StandardPlugin extends Plugin { * 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()))); @@ -128,7 +128,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+2) <= (n+1)! * maxError @@ -162,7 +162,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()); From 2ca23fd4276639138e0304b165c4536c8a687c21 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 27 Jul 2017 14:06:57 -0700 Subject: [PATCH 12/12] Implement correct plugin loading and registration. --- src/org/nwapw/abacus/Abacus.java | 1 + src/org/nwapw/abacus/function/Operator.java | 30 ++++++++++++++++ src/org/nwapw/abacus/lexing/Lexer.java | 15 ++++++++ src/org/nwapw/abacus/tree/TreeBuilder.java | 40 +++++++++++++++++++-- src/org/nwapw/abacus/tree/TreeNode.java | 6 ++++ src/org/nwapw/abacus/window/Window.java | 24 +++++++++++-- 6 files changed, 112 insertions(+), 4 deletions(-) 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 index 18d1555..bd12495 100644 --- a/src/org/nwapw/abacus/function/Operator.java +++ b/src/org/nwapw/abacus/function/Operator.java @@ -1,25 +1,55 @@ 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/lexing/Lexer.java b/src/org/nwapw/abacus/lexing/Lexer.java index 0da217e..213330a 100644 --- a/src/org/nwapw/abacus/lexing/Lexer.java +++ b/src/org/nwapw/abacus/lexing/Lexer.java @@ -14,10 +14,25 @@ import java.util.*; */ 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; diff --git a/src/org/nwapw/abacus/tree/TreeBuilder.java b/src/org/nwapw/abacus/tree/TreeBuilder.java index d0560e1..de1a8ee 100644 --- a/src/org/nwapw/abacus/tree/TreeBuilder.java +++ b/src/org/nwapw/abacus/tree/TreeBuilder.java @@ -3,14 +3,25 @@ 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 org.nwapw.abacus.plugin.PluginManager; 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; /** @@ -18,6 +29,9 @@ public class TreeBuilder { */ protected static Comparator tokenSorter = Comparator.comparingInt(e -> e.priority); + /** + * Creates a new TreeBuilder. + */ public TreeBuilder(){ lexer = new Lexer(){{ register(",", TokenType.COMMA); @@ -29,6 +43,26 @@ public class TreeBuilder { 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. @@ -86,7 +120,9 @@ public class TreeBuilder { } } while(!tokenStack.empty()){ - if(!(tokenStack.peek().getType() == TokenType.OP)) return null; + Match match = tokenStack.peek(); + TokenType matchType = match.getType(); + if(!(matchType == TokenType.OP || matchType == TokenType.FUNCTION)) return null; output.add(tokenStack.pop()); } return output; diff --git a/src/org/nwapw/abacus/tree/TreeNode.java b/src/org/nwapw/abacus/tree/TreeNode.java index a3cae09..50d8991 100644 --- a/src/org/nwapw/abacus/tree/TreeNode.java +++ b/src/org/nwapw/abacus/tree/TreeNode.java @@ -11,6 +11,12 @@ import java.util.*; */ public abstract class TreeNode { + /** + * 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. + */ 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 e419820..a682e22 100644 --- a/src/org/nwapw/abacus/window/Window.java +++ b/src/org/nwapw/abacus/window/Window.java @@ -1,6 +1,8 @@ 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; @@ -16,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"; @@ -128,7 +130,7 @@ public class Window extends JFrame { * Action listener that causes the input to be evaluated. */ private ActionListener evaluateListener = (event) -> { - TreeBuilder builder = new TreeBuilder(); + if(builder == null) return; TreeNode parsedExpression = builder.fromString(inputField.getText()); if(parsedExpression == null){ lastOutputArea.setText(SYNTAX_ERR_STRING); @@ -162,6 +164,7 @@ public class Window extends JFrame { public Window(PluginManager manager){ this(); this.manager = manager; + manager.addListener(this); reducer = new NumberReducer(manager); } @@ -257,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; + } }