Merge branch 'plugins'

This commit is contained in:
Danila Fedorin 2017-07-27 14:08:40 -07:00
commit 6813643b15
13 changed files with 564 additions and 203 deletions

View File

@ -25,6 +25,7 @@ public class Abacus {
manager.addInstantiated(new StandardPlugin(manager)); manager.addInstantiated(new StandardPlugin(manager));
mainUi = new Window(manager); mainUi = new Window(manager);
mainUi.setVisible(true); mainUi.setVisible(true);
manager.load();
} }
public static void main(String[] args){ public static void main(String[] args){

View File

@ -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;
}
}

View File

@ -1,4 +1,4 @@
package org.nwapw.abacus.tree; package org.nwapw.abacus.function;
/** /**
* Enum to represent the associativity of an operator. * Enum to represent the associativity of an operator.

View File

@ -5,9 +5,7 @@ import org.nwapw.abacus.lexing.pattern.Match;
import org.nwapw.abacus.lexing.pattern.Pattern; import org.nwapw.abacus.lexing.pattern.Pattern;
import org.nwapw.abacus.lexing.pattern.PatternNode; import org.nwapw.abacus.lexing.pattern.PatternNode;
import java.util.ArrayList; import java.util.*;
import java.util.Comparator;
import java.util.HashSet;
/** /**
* A lexer that can generate tokens of a given type given a list of regular expressions * 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<T> { public class Lexer<T> {
/**
* An entry that represents a pattern that has been registered with the lexer.
* @param <T> the type used to identify the pattern.
*/
private static class PatternEntry<T>{
/**
* 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. * The registered patterns.
*/ */
private ArrayList<Pattern<T>> patterns; private HashMap<PatternEntry<T>, Pattern<T>> patterns;
/** /**
* Creates a new lexer with no registered patterns. * Creates a new lexer with no registered patterns.
*/ */
public Lexer(){ public Lexer(){
patterns = new ArrayList<>(); patterns = new HashMap<>();
} }
/** /**
@ -35,7 +70,16 @@ public class Lexer<T> {
*/ */
public void register(String pattern, T id){ public void register(String pattern, T id){
Pattern<T> compiledPattern = new Pattern<>(pattern, id); Pattern<T> 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<T> {
HashSet<PatternNode<T>> currentSet = new HashSet<>(); HashSet<PatternNode<T>> currentSet = new HashSet<>();
HashSet<PatternNode<T>> futureSet = new HashSet<>(); HashSet<PatternNode<T>> futureSet = new HashSet<>();
int index = startAt; int index = startAt;
for(Pattern<T> pattern : patterns){ for(Pattern<T> pattern : patterns.values()){
pattern.getHead().addInto(currentSet); pattern.getHead().addInto(currentSet);
} }
while(!currentSet.isEmpty()){ while(!currentSet.isEmpty()){

View File

@ -1,8 +1,10 @@
package org.nwapw.abacus.plugin; package org.nwapw.abacus.plugin;
import org.nwapw.abacus.function.Function; import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.function.Operator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Set;
/** /**
* A plugin class that can be externally implemented and loaded via the * 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. * A hash map of functions mapped to their string names.
*/ */
private HashMap<String, Function> functions; private HashMap<String, Function> functions;
/**
* A hash map of operators mapped to their string names.
*/
private HashMap<String, Operator> operators;
/** /**
* The plugin manager in which to search for functions * The plugin manager in which to search for functions
* not inside this package, * not inside this package,
*/ */
private PluginManager manager; private PluginManager manager;
/**
* Whether this plugin has been loaded.
*/
private boolean enabled;
private Plugin(){ } private Plugin(){ }
@ -32,15 +42,24 @@ public abstract class Plugin {
public Plugin(PluginManager manager) { public Plugin(PluginManager manager) {
this.manager = manager; this.manager = manager;
functions = new HashMap<>(); functions = new HashMap<>();
operators = new HashMap<>();
enabled = false;
} }
/** /**
* Determines whether the current plugin provides the given function name. * Gets the list of functions provided by this plugin.
* @param functionName the name of the function provided. * @return the list of registered functions.
* @return true of the function exists, false if it doesn't.
*/ */
public final boolean hasFunction(String functionName) { public final Set<String> providedFunctions(){
return functions.containsKey(functionName); return functions.keySet();
}
/**
* Gets the list of functions provided by this plugin.
* @return the list of registered functions.
*/
public final Set<String> providedOperators(){
return operators.keySet();
} }
/** /**
@ -52,37 +71,91 @@ public abstract class Plugin {
return functions.get(functionName); 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 * To be used in load(). Registers a function abstract class with the
* plugin internally, which makes it accessible to the plugin manager. * plugin internally, which makes it accessible to the plugin manager.
* @param name the name to register by. * @param name the name to register by.
* @param toRegister the function implementation. * @param toRegister the function implementation.
* @return true if the function was registered successfully, false if not.
*/ */
protected final boolean registerFunction(String name, Function toRegister) { protected final void registerFunction(String name, Function toRegister) {
if(functionFor(name) == null){ functions.put(name, toRegister);
functions.put(name, toRegister); }
return true;
} /**
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.
*/
protected final void registerOperator(String name, Operator operator) {
operators.put(name, operator);
} }
/** /**
* Searches the PluginManager for the given function name. * Searches the PluginManager for the given function name.
* This can be used by the plugins internally in order to call functions * This can be used by the plugins internally in order to call functions
* they do not provide. * 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. * @return the resulting function, or null if none was found for that name.
*/ */
protected final Function functionFor(String name) { protected final Function functionFor(String name) {
return manager.functionFor(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 * Abstract method to be overridden by plugin implementation, in which the plugins
* are supposed to register the functions they provide and do any other * are supposed to register the functions they provide and do any other
* necessary setup. * 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();
} }

View File

@ -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);
}

View File

@ -1,10 +1,10 @@
package org.nwapw.abacus.plugin; package org.nwapw.abacus.plugin;
import org.nwapw.abacus.function.Function; import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.function.Operator;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
/** /**
* A class that controls instances of plugins, allowing for them * 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. * that is, found in a plugin and returned.
*/ */
private HashMap<String, Function> cachedFunctions; private HashMap<String, Function> cachedFunctions;
/**
* List of operators tha have been cached,
* that is, found in a plugin and returned.
*/
private HashMap<String, Operator> cachedOperators;
/**
* List of all functions loaded by the plugins.
*/
private HashSet<String> allFunctions;
/**
* List of all operators loaded by the plugins.
*/
private HashSet<String> allOperators;
/**
* The list of plugin listeners attached to this instance.
*/
private HashSet<PluginListener> listeners;
/** /**
* Creates a new plugin manager. * Creates a new plugin manager.
@ -28,27 +45,58 @@ public class PluginManager {
public PluginManager(){ public PluginManager(){
plugins = new ArrayList<>(); plugins = new ArrayList<>();
cachedFunctions = new HashMap<>(); 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 <T> the type of element being search
* @return the retrieved element, or null if it was not found.
*/
private static <T> T searchCached(Collection<Plugin> plugins, Map<String, T> cache,
java.util.function.Function<Plugin, Set<String>> setFunction,
java.util.function.BiFunction<Plugin, String, T> 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. * Gets a function under the given name.
* @param name the name of the function * @param name the name of the function
* @return the function under the given name. * @return the function under the given name.
*/ */
public Function functionFor(String name){ public Function functionFor(String name){
if(cachedFunctions.containsKey(name)) { return searchCached(plugins, cachedFunctions, Plugin::providedFunctions, Plugin::getFunction, name);
return cachedFunctions.get(name); }
}
Function loadedFunction = null; /**
for(Plugin plugin : plugins){ * Gets an operator under the given name.
if(plugin.hasFunction(name)){ * @param name the name of the operator.
loadedFunction = plugin.getFunction(name); * @return the operator under the given name.
break; */
} public Operator operatorFor(String name){
} return searchCached(plugins, cachedOperators, Plugin::providedOperators, Plugin::getOperator, name);
cachedFunctions.put(name, loadedFunction);
return loadedFunction;
} }
/** /**
@ -56,8 +104,6 @@ public class PluginManager {
* @param plugin the plugin to add. * @param plugin the plugin to add.
*/ */
public void addInstantiated(Plugin plugin){ public void addInstantiated(Plugin plugin){
plugin.load();
cachedFunctions.clear();
plugins.add(plugin); 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<String> getAllFunctions() {
return allFunctions;
}
/**
* Gets all the operators loaded by the Plugin Manager.
* @return the set of all operators that were loaded.
*/
public HashSet<String> 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);
}
} }

View File

@ -20,7 +20,7 @@ public class StandardPlugin extends Plugin {
} }
@Override @Override
public void load() { public void onEnable() {
registerFunction("+", new Function() { registerFunction("+", new Function() {
@Override @Override
protected boolean matchesParams(NumberInterface[] params) { 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 * Returns the nth term of the Taylor series (centered at 0) of e^x
* @param n the term required (n >= 0). * @param n the term required (n >= 0).
* @param x the real number at which the series is evaluated. * @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){ private NumberInterface getExpSeriesTerm(int n, NumberInterface x){
return x.intPow(n).divide(this.getFunction("!").apply((new NaiveNumber(n)).promoteTo(x.getClass()))); 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. * such that the error is at most maxError.
* @param maxError Maximum error permissible (This should probably be positive.) * @param maxError Maximum error permissible (This should probably be positive.)
* @param x where the function is evaluated. * @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) { private int getNTermsExp(NumberInterface maxError, NumberInterface x) {
//We need n such that |x^(n+1)| <= (n+1)! * maxError //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. * 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). * @param number Any instance of the NumberInterface in question (should return an appropriate precision).
* @return * @return the maximum error.
*/ */
private NumberInterface getMaxError(NumberInterface number){ private NumberInterface getMaxError(NumberInterface number){
return (new NaiveNumber(10)).promoteTo(number.getClass()).intPow(-number.precision()); return (new NaiveNumber(10)).promoteTo(number.getClass()).intPow(-number.precision());

View File

@ -95,13 +95,6 @@ public class OpNode extends TreeNode {
String leftString = left != null ? left.toString() : "null"; String leftString = left != null ? left.toString() : "null";
String rightString = right != null ? right.toString() : "null"; String rightString = right != null ? right.toString() : "null";
if(right != null && right instanceof OpNode){ return "(" + leftString + operation + rightString + ")";
if(TreeNode.precedenceMap.get(((OpNode) right).getOperation()) >
TreeNode.precedenceMap.get(operation)) {
rightString = "(" + rightString + ")";
}
}
return leftString + operation + rightString;
} }
} }

View File

@ -6,7 +6,8 @@ package org.nwapw.abacus.tree;
*/ */
public enum TokenType { 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. * The priority by which this token gets sorted.

View File

@ -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<TokenType> lexer;
/**
* The map of operator precedences.
*/
private HashMap<String, Integer> precedenceMap;
/**
* The map of operator associativity.
*/
private HashMap<String, OperatorAssociativity> associativityMap;
/**
* Comparator used to sort token types.
*/
protected static Comparator<TokenType> tokenSorter = Comparator.comparingInt(e -> e.priority);
/**
* Creates a new TreeBuilder.
*/
public TreeBuilder(){
lexer = new Lexer<TokenType>(){{
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<Match<TokenType>> 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<Match<TokenType>> intoPostfix(String source, ArrayList<Match<TokenType>> from){
ArrayList<Match<TokenType>> output = new ArrayList<>();
Stack<Match<TokenType>> tokenStack = new Stack<>();
while(!from.isEmpty()){
Match<TokenType> 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<TokenType> 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<TokenType> 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<Match<TokenType>> matches){
if(matches.size() == 0) return null;
Match<TokenType> 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<Match<TokenType>> matches = tokenize(string);
if(matches == null) return null;
matches = intoPostfix(string, matches);
if(matches == null) return null;
Collections.reverse(matches);
return fromStringRecursive(string, matches);
}
}

View File

@ -1,5 +1,6 @@
package org.nwapw.abacus.tree; package org.nwapw.abacus.tree;
import org.nwapw.abacus.function.OperatorAssociativity;
import org.nwapw.abacus.lexing.Lexer; import org.nwapw.abacus.lexing.Lexer;
import org.nwapw.abacus.lexing.pattern.Match; import org.nwapw.abacus.lexing.pattern.Match;
@ -11,157 +12,11 @@ import java.util.*;
public abstract class TreeNode { 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 <T> the type the reducer produces.
* @return the result of the reduction, or null on error.
*/ */
protected static Lexer<TokenType> lexer = new Lexer<TokenType>(){{
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<String, Integer> precedenceMap = new HashMap<String, Integer>(){{
put("+", 0);
put("-", 0);
put("*", 1);
put("/", 1);
}};
/**
* A map that maps operations to their associativity.
*/
protected static HashMap<String, OperatorAssociativity> associativityMap =
new HashMap<String, OperatorAssociativity>() {{
put("+", OperatorAssociativity.LEFT);
put("-", OperatorAssociativity.LEFT);
put("*", OperatorAssociativity.LEFT);
put("/", OperatorAssociativity.LEFT);
}};
/**
* Comparator used to sort token types.
*/
protected static Comparator<TokenType> 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<Match<TokenType>> 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<Match<TokenType>> intoPostfix(String source, ArrayList<Match<TokenType>> from){
ArrayList<Match<TokenType>> output = new ArrayList<>();
Stack<Match<TokenType>> tokenStack = new Stack<>();
while(!from.isEmpty()){
Match<TokenType> 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<TokenType> 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<Match<TokenType>> matches){
if(matches.size() == 0) return null;
Match<TokenType> 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<TokenType> 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<Match<TokenType>> 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> T reduce(Reducer<T> reducer); public abstract <T> T reduce(Reducer<T> reducer);
} }

View File

@ -1,8 +1,11 @@
package org.nwapw.abacus.window; package org.nwapw.abacus.window;
import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.plugin.PluginListener;
import org.nwapw.abacus.plugin.PluginManager; import org.nwapw.abacus.plugin.PluginManager;
import org.nwapw.abacus.tree.NumberReducer; import org.nwapw.abacus.tree.NumberReducer;
import org.nwapw.abacus.tree.TreeBuilder;
import org.nwapw.abacus.tree.TreeNode; import org.nwapw.abacus.tree.TreeNode;
import javax.swing.*; import javax.swing.*;
@ -15,7 +18,7 @@ import java.awt.event.MouseEvent;
/** /**
* The main UI window for the calculator. * The main UI window for the calculator.
*/ */
public class Window extends JFrame { public class Window extends JFrame implements PluginListener {
private static final String CALC_STRING = "Calculate"; private static final String CALC_STRING = "Calculate";
private static final String SYNTAX_ERR_STRING = "Syntax Error"; private static final String SYNTAX_ERR_STRING = "Syntax Error";
@ -47,6 +50,10 @@ public class Window extends JFrame {
* The plugin manager used to retrieve functions. * The plugin manager used to retrieve functions.
*/ */
private PluginManager manager; private PluginManager manager;
/**
* The builder used to construct the parse trees.
*/
private TreeBuilder builder;
/** /**
* The reducer used to evaluate the tree. * 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. * Action listener that causes the input to be evaluated.
*/ */
private ActionListener evaluateListener = (event) -> { private ActionListener evaluateListener = (event) -> {
TreeNode parsedExpression = TreeNode.fromString(inputField.getText()); if(builder == null) return;
TreeNode parsedExpression = builder.fromString(inputField.getText());
if(parsedExpression == null){ if(parsedExpression == null){
lastOutputArea.setText(SYNTAX_ERR_STRING); lastOutputArea.setText(SYNTAX_ERR_STRING);
return; return;
@ -156,6 +164,7 @@ public class Window extends JFrame {
public Window(PluginManager manager){ public Window(PluginManager manager){
this(); this();
this.manager = manager; this.manager = manager;
manager.addListener(this);
reducer = new NumberReducer(manager); 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;
}
} }