From 7b1b9aa01ce2ff709a584b72f9c0009295bb0f57 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Sat, 29 Jul 2017 21:02:41 -0700 Subject: [PATCH 1/5] Implement the components of a new tree builder. --- src/org/nwapw/abacus/parsing/Parser.java | 10 ++++++++ src/org/nwapw/abacus/parsing/Tokenizer.java | 9 ++++++++ src/org/nwapw/abacus/parsing/TreeBuilder.java | 23 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 src/org/nwapw/abacus/parsing/Parser.java create mode 100644 src/org/nwapw/abacus/parsing/Tokenizer.java create mode 100644 src/org/nwapw/abacus/parsing/TreeBuilder.java diff --git a/src/org/nwapw/abacus/parsing/Parser.java b/src/org/nwapw/abacus/parsing/Parser.java new file mode 100644 index 0000000..0103a7d --- /dev/null +++ b/src/org/nwapw/abacus/parsing/Parser.java @@ -0,0 +1,10 @@ +package org.nwapw.abacus.parsing; + +import org.nwapw.abacus.tree.TreeNode; + +import java.util.List; + +public interface Parser { + + public TreeNode constructTree(List tokens); +} diff --git a/src/org/nwapw/abacus/parsing/Tokenizer.java b/src/org/nwapw/abacus/parsing/Tokenizer.java new file mode 100644 index 0000000..4baef10 --- /dev/null +++ b/src/org/nwapw/abacus/parsing/Tokenizer.java @@ -0,0 +1,9 @@ +package org.nwapw.abacus.parsing; + +import java.util.List; + +public interface Tokenizer { + + public List tokenizeString(String string); + +} diff --git a/src/org/nwapw/abacus/parsing/TreeBuilder.java b/src/org/nwapw/abacus/parsing/TreeBuilder.java new file mode 100644 index 0000000..57de6ed --- /dev/null +++ b/src/org/nwapw/abacus/parsing/TreeBuilder.java @@ -0,0 +1,23 @@ +package org.nwapw.abacus.parsing; + +import org.nwapw.abacus.tree.TreeNode; + +import java.util.List; + +public class TreeBuilder { + + private Tokenizer tokenizer; + private Parser parser; + + public TreeBuilder(Tokenizer tokenizer, Parser parser){ + this.tokenizer = tokenizer; + this.parser = parser; + } + + public TreeNode fromString(String input){ + List tokens = tokenizer.tokenizeString(input); + if(tokens == null) return null; + return parser.constructTree(tokens); + } + +} From 65e8b7d15e0ebf857aabb5cf276c8157567c239f Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Sat, 29 Jul 2017 21:13:32 -0700 Subject: [PATCH 2/5] Change matches to store the string they matched. --- src/org/nwapw/abacus/lexing/Lexer.java | 9 +++--- .../nwapw/abacus/lexing/pattern/Match.java | 32 ++++++------------- src/org/nwapw/abacus/tree/TreeBuilder.java | 30 ++++++++--------- 3 files changed, 28 insertions(+), 43 deletions(-) diff --git a/src/org/nwapw/abacus/lexing/Lexer.java b/src/org/nwapw/abacus/lexing/Lexer.java index 5839801..82f5898 100644 --- a/src/org/nwapw/abacus/lexing/Lexer.java +++ b/src/org/nwapw/abacus/lexing/Lexer.java @@ -102,7 +102,7 @@ public class Lexer { if(index < from.length() && node.matches(from.charAt(index))) { node.addOutputsInto(futureSet); } else if(node instanceof EndNode){ - matches.add(new Match<>(startAt, index, ((EndNode) node).getPatternId())); + matches.add(new Match<>(from.substring(startAt, index), ((EndNode) node).getPatternId())); } } @@ -115,7 +115,7 @@ public class Lexer { } matches.sort((a, b) -> compare.compare(a.getType(), b.getType())); 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); } @@ -132,9 +132,10 @@ public class Lexer { ArrayList> matches = new ArrayList<>(); Match lastMatch = 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); - index += lastMatch.getTo() - lastMatch.getFrom(); + index += length; } if(lastMatch == null) return null; return matches; diff --git a/src/org/nwapw/abacus/lexing/pattern/Match.java b/src/org/nwapw/abacus/lexing/pattern/Match.java index 15ee33d..49b7ad8 100644 --- a/src/org/nwapw/abacus/lexing/pattern/Match.java +++ b/src/org/nwapw/abacus/lexing/pattern/Match.java @@ -7,13 +7,9 @@ package org.nwapw.abacus.lexing.pattern; public class Match { /** - * The bottom range of the string, inclusive. + * The content of this match. */ - private int from; - /** - * The top range of the string, exclusive. - */ - private int to; + private String content; /** * The pattern type this match matched. */ @@ -21,30 +17,20 @@ public class Match { /** * Creates a new match with the given parameters. - * @param from the bottom range of the string. - * @param to the top range of the string. + * @param content the content of this match. * @param type the type of the match. */ - public Match(int from, int to, T type){ - this.from = from; - this.to = to; + public Match(String content, T type){ + this.content = content; this.type = type; } /** - * Gets the bottom range bound of the string. - * @return the bottom range bound of the string. + * Gets the content of this match. + * @return the content. */ - public int getFrom() { - return from; - } - - /** - * Gets the top range bound of the string. - * @return the top range bound of the string. - */ - public int getTo() { - return to; + public String getContent() { + return content; } /** diff --git a/src/org/nwapw/abacus/tree/TreeBuilder.java b/src/org/nwapw/abacus/tree/TreeBuilder.java index 97800e4..13ac266 100644 --- a/src/org/nwapw/abacus/tree/TreeBuilder.java +++ b/src/org/nwapw/abacus/tree/TreeBuilder.java @@ -91,11 +91,10 @@ public class TreeBuilder { /** * 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 List> intoPostfix(String source, List> from){ + public List> intoPostfix(List> from){ ArrayList> output = new ArrayList<>(); Stack> tokenStack = new Stack<>(); while(!from.isEmpty()){ @@ -104,10 +103,10 @@ public class TreeBuilder { if(matchType == TokenType.NUM) { output.add(match); } 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); } else if(matchType == TokenType.OP){ - String tokenString = source.substring(match.getFrom(), match.getTo()); + String tokenString = match.getContent(); OperatorType type = typeMap.get(tokenString); int precedence = precedenceMap.get(tokenString); OperatorAssociativity associativity = associativityMap.get(tokenString); @@ -123,7 +122,7 @@ public class TreeBuilder { if(!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break; if(otherMatchType == TokenType.OP){ - int otherPrecedence = precedenceMap.get(source.substring(otherMatch.getFrom(), otherMatch.getTo())); + int otherPrecedence = precedenceMap.get(match.getContent()); if(otherPrecedence < precedence || (associativity == OperatorAssociativity.RIGHT && otherPrecedence == precedence)) { break; @@ -155,34 +154,33 @@ public class TreeBuilder { /** * 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, List> matches){ + public TreeNode fromStringRecursive(List> matches){ if(matches.size() == 0) return null; Match match = matches.remove(0); TokenType matchType = match.getType(); if(matchType == TokenType.OP){ - String operator = source.substring(match.getFrom(), match.getTo()); + String operator = match.getContent(); OperatorType type = typeMap.get(operator); if(type == OperatorType.BINARY_INFIX){ - TreeNode right = fromStringRecursive(source, matches); - TreeNode left = fromStringRecursive(source, matches); + TreeNode right = fromStringRecursive(matches); + TreeNode left = fromStringRecursive(matches); if(left == null || right == null) return null; else return new BinaryInfixNode(operator, left, right); } else { - TreeNode applyTo = fromStringRecursive(source, matches); + TreeNode applyTo = fromStringRecursive(matches); if(applyTo == null) return null; else return new UnaryPrefixNode(operator, applyTo); } } else if(matchType == TokenType.NUM){ - return new NumberNode(abacus.numberFromString(source.substring(match.getFrom(), match.getTo()))); + return new NumberNode(abacus.numberFromString(match.getContent())); } else if(matchType == TokenType.FUNCTION){ - String functionName = source.substring(match.getFrom(), match.getTo()); + String functionName = match.getContent(); FunctionNode node = new FunctionNode(functionName); while(!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END){ - TreeNode argument = fromStringRecursive(source, matches); + TreeNode argument = fromStringRecursive(matches); if(argument == null) return null; node.prependChild(argument); } @@ -202,11 +200,11 @@ public class TreeBuilder { List> matches = tokenize(string); if(matches == null) return null; matches.removeIf(m -> m.getType() == TokenType.WHITESPACE); - matches = intoPostfix(string, matches); + matches = intoPostfix(matches); if(matches == null) return null; Collections.reverse(matches); - return fromStringRecursive(string, matches); + return fromStringRecursive(matches); } } From dcbda5b255289a43d485595ece765431a2c3cdfa Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Sat, 29 Jul 2017 21:36:39 -0700 Subject: [PATCH 3/5] Add comments to the two parsing interfaces. --- src/org/nwapw/abacus/parsing/Parser.java | 10 ++++++++++ src/org/nwapw/abacus/parsing/Tokenizer.java | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/src/org/nwapw/abacus/parsing/Parser.java b/src/org/nwapw/abacus/parsing/Parser.java index 0103a7d..d4a72d0 100644 --- a/src/org/nwapw/abacus/parsing/Parser.java +++ b/src/org/nwapw/abacus/parsing/Parser.java @@ -4,7 +4,17 @@ 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 the type of tokens accepted by this parser. + */ public interface Parser { + /** + * 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 tokens); } diff --git a/src/org/nwapw/abacus/parsing/Tokenizer.java b/src/org/nwapw/abacus/parsing/Tokenizer.java index 4baef10..0f1b270 100644 --- a/src/org/nwapw/abacus/parsing/Tokenizer.java +++ b/src/org/nwapw/abacus/parsing/Tokenizer.java @@ -2,8 +2,17 @@ package org.nwapw.abacus.parsing; import java.util.List; +/** + * Interface that provides the ability to convert a string into a list of tokens. + * @param the type of the tokens produced. + */ public interface Tokenizer { + /** + * Converts a string into tokens. + * @param string the string to convert. + * @return the list of tokens, or null on error. + */ public List tokenizeString(String string); } From e61e2b07b2f8ac4a8ee3993a419a886d87eafbd4 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Sat, 29 Jul 2017 21:37:32 -0700 Subject: [PATCH 4/5] Implement a LexerTokenizer and a ShuntingYard parser. These are basically two pieces of the old TreeBuilder, but decoupled and reimplemented conventionally. --- .../nwapw/abacus/parsing/LexerTokenizer.java | 67 +++++++ .../abacus/parsing/ShuntingYardParser.java | 173 ++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 src/org/nwapw/abacus/parsing/LexerTokenizer.java create mode 100644 src/org/nwapw/abacus/parsing/ShuntingYardParser.java diff --git a/src/org/nwapw/abacus/parsing/LexerTokenizer.java b/src/org/nwapw/abacus/parsing/LexerTokenizer.java new file mode 100644 index 0000000..104541e --- /dev/null +++ b/src/org/nwapw/abacus/parsing/LexerTokenizer.java @@ -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>, PluginListener { + + /** + * Comparator used to sort the tokens produced by the lexer. + */ + protected static final Comparator TOKEN_SORTER = Comparator.comparingInt(e -> e.priority); + + /** + * The lexer instance used to turn strings into matches. + */ + private Lexer lexer; + + /** + * Creates a new lexer tokenizer. + */ + public LexerTokenizer(){ + lexer = new Lexer() {{ + register(" ", TokenType.WHITESPACE); + register(",", TokenType.COMMA); + register("[0-9]*(\\.[0-9]+)?", TokenType.NUM); + register("\\(", TokenType.OPEN_PARENTH); + register("\\)", TokenType.CLOSE_PARENTH); + }}; + } + + @Override + public List> 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); + } + } + +} diff --git a/src/org/nwapw/abacus/parsing/ShuntingYardParser.java b/src/org/nwapw/abacus/parsing/ShuntingYardParser.java new file mode 100644 index 0000000..9934006 --- /dev/null +++ b/src/org/nwapw/abacus/parsing/ShuntingYardParser.java @@ -0,0 +1,173 @@ +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.OperatorType; +import org.nwapw.abacus.lexing.pattern.Match; +import org.nwapw.abacus.plugin.PluginListener; +import org.nwapw.abacus.plugin.PluginManager; +import org.nwapw.abacus.tree.*; + +import java.util.*; + +/** + * A parser that uses shunting yard to rearranged matches into postfix + * and then convert them into a parse tree. + */ +public class ShuntingYardParser implements Parser>, PluginListener { + + /** + * The Abacus instance used to create number instances. + */ + private Abacus abacus; + /** + * Map of operator precedences, loaded from the plugin operators. + */ + private Map precedenceMap; + /** + * Map of operator associativity, loaded from the plugin operators. + */ + private Map associativityMap; + /** + * Map of operator types, loaded from plugin operators. + */ + private Map typeMap; + + /** + * Creates a new Shunting Yard parser with the given Abacus instance. + * @param abacus the abacus instance. + */ + public ShuntingYardParser(Abacus abacus){ + this.abacus = abacus; + precedenceMap = new HashMap<>(); + associativityMap = new HashMap<>(); + typeMap = new HashMap<>(); + } + + /** + * Rearranges tokens into a postfix list, using Shunting Yard. + * @param from the tokens to be rearranged. + * @return the resulting list of rearranged tokens. + */ + public List> intoPostfix(List> 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<>("" , TokenType.INTERNAL_FUNCTION_END)); + tokenStack.push(match); + } else if(matchType == TokenType.OP){ + String tokenString = match.getContent(); + OperatorType type = typeMap.get(tokenString); + int precedence = precedenceMap.get(tokenString); + OperatorAssociativity associativity = associativityMap.get(tokenString); + + if(type == OperatorType.UNARY_POSTFIX){ + output.add(match); + continue; + } + + while(!tokenStack.empty()) { + Match otherMatch = tokenStack.peek(); + TokenType otherMatchType = otherMatch.getType(); + if(!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break; + + if(otherMatchType == TokenType.OP){ + int otherPrecedence = precedenceMap.get(match.getContent()); + if(otherPrecedence < precedence || + (associativity == OperatorAssociativity.RIGHT && otherPrecedence == 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 matches the list of tokens from the source string. + * @return the construct tree expression. + */ + public TreeNode constructRecursive(List> matches){ + if(matches.size() == 0) return null; + Match match = matches.remove(0); + TokenType matchType = match.getType(); + if(matchType == TokenType.OP){ + String operator = match.getContent(); + OperatorType type = typeMap.get(operator); + if(type == OperatorType.BINARY_INFIX){ + TreeNode right = constructRecursive(matches); + TreeNode left = constructRecursive(matches); + if(left == null || right == null) return null; + else return new BinaryInfixNode(operator, left, right); + } else { + TreeNode applyTo = constructRecursive(matches); + if(applyTo == null) return null; + else return new UnaryPrefixNode(operator, applyTo); + } + } else if(matchType == TokenType.NUM){ + return new NumberNode(abacus.numberFromString(match.getContent())); + } else if(matchType == TokenType.FUNCTION){ + String functionName = match.getContent(); + FunctionNode node = new FunctionNode(functionName); + while(!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END){ + TreeNode argument = constructRecursive(matches); + if(argument == null) return null; + node.prependChild(argument); + } + if(matches.isEmpty()) return null; + matches.remove(0); + return node; + } + return null; + } + + @Override + public TreeNode constructTree(List> tokens) { + tokens = intoPostfix(new ArrayList<>(tokens)); + Collections.reverse(tokens); + return constructRecursive(tokens); + } + + @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(); + } +} From 6337c74e632d50e8dc816bfd9c74d16a9d1d2b17 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Sat, 29 Jul 2017 21:37:55 -0700 Subject: [PATCH 5/5] Replace the old TreeBuilder with the new TreeBuilder. --- src/org/nwapw/abacus/Abacus.java | 45 ++--- src/org/nwapw/abacus/tree/TreeBuilder.java | 210 --------------------- 2 files changed, 14 insertions(+), 241 deletions(-) delete mode 100644 src/org/nwapw/abacus/tree/TreeBuilder.java diff --git a/src/org/nwapw/abacus/Abacus.java b/src/org/nwapw/abacus/Abacus.java index eacc0d8..a5c61bb 100644 --- a/src/org/nwapw/abacus/Abacus.java +++ b/src/org/nwapw/abacus/Abacus.java @@ -1,15 +1,15 @@ package org.nwapw.abacus; import org.nwapw.abacus.config.ConfigurationObject; -import org.nwapw.abacus.function.Operator; import org.nwapw.abacus.number.NaiveNumber; import org.nwapw.abacus.number.NumberInterface; +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.PluginListener; import org.nwapw.abacus.plugin.PluginManager; import org.nwapw.abacus.plugin.StandardPlugin; import org.nwapw.abacus.tree.NumberReducer; -import org.nwapw.abacus.tree.TreeBuilder; import org.nwapw.abacus.tree.TreeNode; import org.nwapw.abacus.window.Window; @@ -23,7 +23,7 @@ import java.lang.reflect.InvocationTargetException; * for piecing together all of the components, allowing * their interaction with each other. */ -public class Abacus implements PluginListener { +public class Abacus { /** * The default implementation to use for the number representation. @@ -44,11 +44,6 @@ public class Abacus implements PluginListener { * and getting functions from them. */ private PluginManager pluginManager; - /** - * Tree builder built from plugin manager, - * used to construct parse trees. - */ - private TreeBuilder treeBuilder; /** * The reducer used to evaluate the tree. */ @@ -57,6 +52,11 @@ public class Abacus implements PluginListener { * 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. @@ -67,8 +67,12 @@ public class Abacus implements PluginListener { 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(this); + pluginManager.addListener(lexerTokenizer); + pluginManager.addListener(shuntingYardParser); pluginManager.addInstantiated(new StandardPlugin(pluginManager)); try { ClassFinder.loadJars("plugins") @@ -129,7 +133,6 @@ public class Abacus implements PluginListener { * @return the resulting tree, null if the tree builder or the produced tree are null. */ public TreeNode parseString(String input){ - if(treeBuilder == null) return null; return treeBuilder.fromString(input); } @@ -156,26 +159,6 @@ public class Abacus implements PluginListener { return null; } - @Override - public void onLoad(PluginManager manager) { - treeBuilder = new TreeBuilder(this); - for(String function : manager.getAllFunctions()){ - treeBuilder.registerFunction(function); - } - for(String operator : manager.getAllOperators()){ - Operator operatorObject = manager.operatorFor(operator); - treeBuilder.registerOperator(operator, - operatorObject.getAssociativity(), - operatorObject.getType(), - operatorObject.getPrecedence()); - } - } - - @Override - public void onUnload(PluginManager manager) { - treeBuilder = null; - } - public static void main(String[] args){ try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); diff --git a/src/org/nwapw/abacus/tree/TreeBuilder.java b/src/org/nwapw/abacus/tree/TreeBuilder.java deleted file mode 100644 index 13ac266..0000000 --- a/src/org/nwapw/abacus/tree/TreeBuilder.java +++ /dev/null @@ -1,210 +0,0 @@ -package org.nwapw.abacus.tree; - -import org.nwapw.abacus.Abacus; -import org.nwapw.abacus.function.OperatorAssociativity; -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.Pattern; - -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 Map precedenceMap; - /** - * The map of operator associativity. - */ - private Map associativityMap; - /** - * The map of operator types. - */ - private Map typeMap; - /** - * The abacus instance required to interact with - * other components of the calculator. - */ - private Abacus abacus; - - /** - * Comparator used to sort token types. - */ - protected static Comparator tokenSorter = Comparator.comparingInt(e -> e.priority); - - /** - * Creates a new TreeBuilder. - */ - public TreeBuilder(Abacus abacus){ - this.abacus = abacus; - lexer = new Lexer(){{ - register(" ", TokenType.WHITESPACE); - register(",", TokenType.COMMA); - register("[0-9]*(\\.[0-9]+)?", TokenType.NUM); - register("\\(", TokenType.OPEN_PARENTH); - register("\\)", TokenType.CLOSE_PARENTH); - }}; - precedenceMap = new HashMap<>(); - associativityMap = 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> tokenize(String string){ - return lexer.lexAll(string, 0, tokenSorter); - } - - /** - * Rearranges tokens into a postfix list, using Shunting Yard. - * @param from the tokens to be rearranged. - * @return the resulting list of rearranged tokens. - */ - public List> intoPostfix(List> 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<>("" , TokenType.INTERNAL_FUNCTION_END)); - tokenStack.push(match); - } else if(matchType == TokenType.OP){ - String tokenString = match.getContent(); - OperatorType type = typeMap.get(tokenString); - int precedence = precedenceMap.get(tokenString); - OperatorAssociativity associativity = associativityMap.get(tokenString); - - if(type == OperatorType.UNARY_POSTFIX){ - output.add(match); - continue; - } - - while(!tokenStack.empty()) { - Match otherMatch = tokenStack.peek(); - TokenType otherMatchType = otherMatch.getType(); - if(!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break; - - if(otherMatchType == TokenType.OP){ - int otherPrecedence = precedenceMap.get(match.getContent()); - if(otherPrecedence < precedence || - (associativity == OperatorAssociativity.RIGHT && otherPrecedence == 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 matches the list of tokens from the source string. - * @return the construct tree expression. - */ - public TreeNode fromStringRecursive(List> matches){ - if(matches.size() == 0) return null; - Match match = matches.remove(0); - TokenType matchType = match.getType(); - if(matchType == TokenType.OP){ - String operator = match.getContent(); - OperatorType type = typeMap.get(operator); - if(type == OperatorType.BINARY_INFIX){ - TreeNode right = fromStringRecursive(matches); - TreeNode left = fromStringRecursive(matches); - if(left == null || right == null) return null; - else return new BinaryInfixNode(operator, left, right); - } else { - TreeNode applyTo = fromStringRecursive(matches); - if(applyTo == null) return null; - else return new UnaryPrefixNode(operator, applyTo); - } - } else if(matchType == TokenType.NUM){ - return new NumberNode(abacus.numberFromString(match.getContent())); - } else if(matchType == TokenType.FUNCTION){ - String functionName = match.getContent(); - FunctionNode node = new FunctionNode(functionName); - while(!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END){ - TreeNode argument = fromStringRecursive(matches); - if(argument == null) return null; - node.prependChild(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){ - List> matches = tokenize(string); - if(matches == null) return null; - matches.removeIf(m -> m.getType() == TokenType.WHITESPACE); - matches = intoPostfix(matches); - if(matches == null) return null; - - Collections.reverse(matches); - return fromStringRecursive(matches); - } - -}