diff --git a/core/src/main/java/org/nwapw/abacus/function/DocumentationType.java b/core/src/main/java/org/nwapw/abacus/function/DocumentationType.java index e3ab7fd..5edc9a0 100644 --- a/core/src/main/java/org/nwapw/abacus/function/DocumentationType.java +++ b/core/src/main/java/org/nwapw/abacus/function/DocumentationType.java @@ -6,6 +6,6 @@ package org.nwapw.abacus.function; */ public enum DocumentationType { - FUNCTION + FUNCTION, TREE_VALUE_FUNCTION } diff --git a/core/src/main/java/org/nwapw/abacus/function/Function.java b/core/src/main/java/org/nwapw/abacus/function/Function.java deleted file mode 100755 index bd5af72..0000000 --- a/core/src/main/java/org/nwapw/abacus/function/Function.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.nwapw.abacus.function; - -import org.nwapw.abacus.number.NumberInterface; - -/** - * A function that operates on one or more - * inputs and returns a single number. - */ -public abstract class Function { - - /** - * Checks whether the given params will work for the given function. - * - * @param params the given params - * @return true if the params can be used with this function. - */ - protected abstract boolean matchesParams(NumberInterface[] params); - - /** - * Internal apply implementation, which already receives appropriately promoted - * parameters that have bee run through matchesParams - * - * @param params the promoted parameters. - * @return the return value of the function. - */ - protected abstract NumberInterface applyInternal(NumberInterface[] params); - - /** - * Function to check, promote arguments and run the function. - * - * @param params the raw input parameters. - * @return the return value of the function, or null if an error occurred. - */ - public NumberInterface apply(NumberInterface... params) { - if (!matchesParams(params)) return null; - return applyInternal(params); - } - -} diff --git a/core/src/main/java/org/nwapw/abacus/parsing/LexerTokenizer.java b/core/src/main/java/org/nwapw/abacus/parsing/LexerTokenizer.java index b7bdd1f..2d7b16b 100644 --- a/core/src/main/java/org/nwapw/abacus/parsing/LexerTokenizer.java +++ b/core/src/main/java/org/nwapw/abacus/parsing/LexerTokenizer.java @@ -50,9 +50,15 @@ public class LexerTokenizer implements Tokenizer>, PluginListen for (String operator : manager.getAllOperators()) { lexer.register(Pattern.sanitize(operator), TokenType.OP); } + for (String operator : manager.getAllTreeValueOperators()){ + lexer.register(Pattern.sanitize(operator), TokenType.TREE_VALUE_OP); + } for (String function : manager.getAllFunctions()) { lexer.register(Pattern.sanitize(function), TokenType.FUNCTION); } + for (String function : manager.getAllTreeValueFunctions()){ + lexer.register(Pattern.sanitize(function), TokenType.TREE_VALUE_FUNCTION); + } } @Override @@ -60,9 +66,15 @@ public class LexerTokenizer implements Tokenizer>, PluginListen for (String operator : manager.getAllOperators()) { lexer.unregister(Pattern.sanitize(operator), TokenType.OP); } + for (String operator : manager.getAllTreeValueOperators()){ + lexer.unregister(Pattern.sanitize(operator), TokenType.TREE_VALUE_OP); + } for (String function : manager.getAllFunctions()) { lexer.unregister(Pattern.sanitize(function), TokenType.FUNCTION); } + for (String function : manager.getAllTreeValueFunctions()){ + lexer.unregister(Pattern.sanitize(function), TokenType.TREE_VALUE_FUNCTION); + } } } diff --git a/core/src/main/java/org/nwapw/abacus/parsing/ShuntingYardParser.java b/core/src/main/java/org/nwapw/abacus/parsing/ShuntingYardParser.java index c792463..7490d96 100644 --- a/core/src/main/java/org/nwapw/abacus/parsing/ShuntingYardParser.java +++ b/core/src/main/java/org/nwapw/abacus/parsing/ShuntingYardParser.java @@ -55,10 +55,10 @@ public class ShuntingYardParser implements Parser>, PluginListe matchType = match.getType(); if (matchType == TokenType.NUM || matchType == TokenType.VARIABLE) { output.add(match); - } else if (matchType == TokenType.FUNCTION) { + } else if (matchType == TokenType.FUNCTION || matchType == TokenType.TREE_VALUE_FUNCTION) { output.add(new Match<>("", TokenType.INTERNAL_FUNCTION_END)); tokenStack.push(match); - } else if (matchType == TokenType.OP) { + } else if (matchType == TokenType.OP || matchType == TokenType.TREE_VALUE_OP) { String tokenString = match.getContent(); OperatorType type = typeMap.get(tokenString); int precedence = precedenceMap.get(tokenString); @@ -70,7 +70,7 @@ public class ShuntingYardParser implements Parser>, PluginListe } if (tokenString.equals("-") && (previousType == null || previousType == TokenType.OP || - previousType == TokenType.OPEN_PARENTH)) { + previousType == TokenType.TREE_VALUE_OP || previousType == TokenType.OPEN_PARENTH)) { from.add(0, new Match<>("`", TokenType.OP)); continue; } @@ -78,9 +78,12 @@ public class ShuntingYardParser implements Parser>, PluginListe while (!tokenStack.empty() && type == OperatorType.BINARY_INFIX) { Match otherMatch = tokenStack.peek(); TokenType otherMatchType = otherMatch.getType(); - if (!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break; + if (!(otherMatchType == TokenType.OP || + otherMatchType == TokenType.TREE_VALUE_OP || + otherMatchType == TokenType.FUNCTION || + otherMatchType == TokenType.TREE_VALUE_FUNCTION)) break; - if (otherMatchType == TokenType.OP) { + if (otherMatchType == TokenType.OP || otherMatchType == TokenType.TREE_VALUE_OP) { int otherPrecedence = precedenceMap.get(otherMatch.getContent()); if (otherPrecedence < precedence || (associativity == OperatorAssociativity.RIGHT && otherPrecedence == precedence)) { @@ -105,7 +108,10 @@ public class ShuntingYardParser implements Parser>, PluginListe while (!tokenStack.empty()) { Match match = tokenStack.peek(); TokenType newMatchType = match.getType(); - if (!(newMatchType == TokenType.OP || newMatchType == TokenType.FUNCTION)) return null; + if (!(newMatchType == TokenType.OP || + newMatchType == TokenType.TREE_VALUE_OP || + newMatchType == TokenType.FUNCTION || + newMatchType == TokenType.TREE_VALUE_FUNCTION)) return null; output.add(tokenStack.pop()); } return output; @@ -121,30 +127,43 @@ public class ShuntingYardParser implements Parser>, PluginListe if (matches.size() == 0) return null; Match match = matches.remove(0); TokenType matchType = match.getType(); - if (matchType == TokenType.OP) { + if (matchType == TokenType.OP || matchType == TokenType.TREE_VALUE_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 BinaryNode(operator, left, right); + if(matchType == TokenType.OP) { + return new NumberBinaryNode(operator, left, right); + } else { + return new TreeValueBinaryNode(operator, left, right); + } } else { TreeNode applyTo = constructRecursive(matches); if (applyTo == null) return null; - else return new UnaryNode(operator, applyTo); + if(matchType == TokenType.OP){ + return new NumberUnaryNode(operator, applyTo); + } else { + return new TreeValueUnaryNode(operator, applyTo); + } } } else if (matchType == TokenType.NUM) { return new NumberNode(match.getContent()); } else if (matchType == TokenType.VARIABLE) { return new VariableNode(match.getContent()); - } else if (matchType == TokenType.FUNCTION) { + } else if (matchType == TokenType.FUNCTION || matchType == TokenType.TREE_VALUE_FUNCTION) { String functionName = match.getContent(); - FunctionNode node = new FunctionNode(functionName); + CallNode node; + if(matchType == TokenType.FUNCTION){ + node = new FunctionNode(functionName); + } else { + node = new TreeValueFunctionNode(functionName); + } while (!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END) { TreeNode argument = constructRecursive(matches); if (argument == null) return null; - node.prependChild(argument); + node.getChildren().add(0, argument); } if (matches.isEmpty()) return null; matches.remove(0); @@ -170,6 +189,12 @@ public class ShuntingYardParser implements Parser>, PluginListe associativityMap.put(operator, operatorInstance.getAssociativity()); typeMap.put(operator, operatorInstance.getType()); } + for (String operator : manager.getAllTreeValueOperators()) { + Operator operatorInstance = manager.treeValueOperatorFor(operator); + precedenceMap.put(operator, operatorInstance.getPrecedence()); + associativityMap.put(operator, operatorInstance.getAssociativity()); + typeMap.put(operator, operatorInstance.getType()); + } } @Override diff --git a/core/src/main/java/org/nwapw/abacus/plugin/Plugin.java b/core/src/main/java/org/nwapw/abacus/plugin/Plugin.java index 4907131..223d7aa 100644 --- a/core/src/main/java/org/nwapw/abacus/plugin/Plugin.java +++ b/core/src/main/java/org/nwapw/abacus/plugin/Plugin.java @@ -1,9 +1,6 @@ package org.nwapw.abacus.plugin; -import org.nwapw.abacus.function.Documentation; -import org.nwapw.abacus.function.DocumentationType; -import org.nwapw.abacus.function.Function; -import org.nwapw.abacus.function.Operator; +import org.nwapw.abacus.function.*; import org.nwapw.abacus.number.NumberInterface; /** @@ -65,10 +62,21 @@ public abstract class Plugin { * @param name the name to register by. * @param toRegister the function implementation. */ - protected final void registerFunction(String name, Function toRegister) { + protected final void registerFunction(String name, NumberFunction toRegister) { manager.registerFunction(name, toRegister); } + /** + * To be used in load(). Registers a tree value function abstract class + * with the plugin internally, which makes it accessible to the plugin manager. + * + * @param name the name to register by. + * @param toRegister the tree value function implementation. + */ + protected final void registerTreeValueFunction(String name, TreeValueFunction toRegister) { + manager.registerTreeValueFunction(name, toRegister); + } + /** * To be used in load(). Registers an operator abstract class * with the plugin internally, which makes it accessible to @@ -77,10 +85,22 @@ public abstract class Plugin { * @param name the name of the operator. * @param operator the operator to register. */ - protected final void registerOperator(String name, Operator operator) { + protected final void registerOperator(String name, NumberOperator operator) { manager.registerOperator(name, operator); } + /** + * To be used in load(). Registers an operator + * with the plugin internally, which makes it accessible + * to the plugin manager. + * + * @param name the name of the tree value operator. + * @param operator the tree value operator to register. + */ + protected final void registerTreeValueOperator(String name, TreeValueOperator operator) { + manager.registerTreeValueOperator(name, operator); + } + /** * To be used in load(). Registers a new number implementation with the plugin. * This makes it accessible to the plugin manager. @@ -110,10 +130,22 @@ public abstract class Plugin { * @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) { + protected final NumberFunction functionFor(String name) { return manager.functionFor(name); } + /** + * 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 the name for which to search. + * @return the resulting tree value function, or null if none was found for that name. + */ + protected final TreeValueFunction treeValueFunctionFor(String name) { + return manager.treeValueFunctionFor(name); + } + /** * Searches the PluginManager for the given operator name. * This can be used by the plugins internally in order to call @@ -122,10 +154,22 @@ public abstract class Plugin { * @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) { + protected final NumberOperator operatorFor(String name) { return manager.operatorFor(name); } + /** + * Searches the PluginManager for the given tree value 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 tree value operator, or null if none was found for that name. + */ + protected final TreeValueOperator treeValueOperatorFor(String name) { + return manager.treeValueOperatorFor(name); + } + /** * Searches the PluginManager for the given number implementation * name. This can be used by the plugins internally in order to find diff --git a/core/src/main/java/org/nwapw/abacus/plugin/PluginManager.java b/core/src/main/java/org/nwapw/abacus/plugin/PluginManager.java index b808411..64b3eb3 100644 --- a/core/src/main/java/org/nwapw/abacus/plugin/PluginManager.java +++ b/core/src/main/java/org/nwapw/abacus/plugin/PluginManager.java @@ -1,10 +1,7 @@ package org.nwapw.abacus.plugin; import org.nwapw.abacus.Abacus; -import org.nwapw.abacus.function.Documentation; -import org.nwapw.abacus.function.DocumentationType; -import org.nwapw.abacus.function.Function; -import org.nwapw.abacus.function.Operator; +import org.nwapw.abacus.function.*; import org.nwapw.abacus.number.NumberInterface; import java.lang.reflect.InvocationTargetException; @@ -30,11 +27,19 @@ public class PluginManager { /** * The map of functions registered by the plugins. */ - private Map registeredFunctions; + private Map registeredFunctions; + /** + * The map of tree value functions regstered by the plugins. + */ + private Map registeredTreeValueFunctions; /** * The map of operators registered by the plugins */ - private Map registeredOperators; + private Map registeredOperators; + /** + * The map of tree value operators registered by the plugins. + */ + private Map registeredTreeValueOperators; /** * The map of number implementations registered by the plugins. */ @@ -72,7 +77,9 @@ public class PluginManager { loadedPluginClasses = new HashSet<>(); plugins = new HashSet<>(); registeredFunctions = new HashMap<>(); + registeredTreeValueFunctions = new HashMap<>(); registeredOperators = new HashMap<>(); + registeredTreeValueOperators = new HashMap<>(); registeredNumberImplementations = new HashMap<>(); registeredDocumentation = new HashSet<>(); cachedInterfaceImplementations = new HashMap<>(); @@ -86,20 +93,40 @@ public class PluginManager { * @param name the name of the function. * @param function the function to register. */ - public void registerFunction(String name, Function function) { + public void registerFunction(String name, NumberFunction function) { registeredFunctions.put(name, function); } + /** + * Registers a tree value function under the given name. + * + * @param name the name of the function. + * @param function the function to register. + */ + public void registerTreeValueFunction(String name, TreeValueFunction function) { + registeredTreeValueFunctions.put(name, function); + } + /** * Registers an operator under the given name. * * @param name the name of the operator. * @param operator the operator to register. */ - public void registerOperator(String name, Operator operator) { + public void registerOperator(String name, NumberOperator operator) { registeredOperators.put(name, operator); } + /** + * Registers a tree value operator under the given name. + * + * @param name the name of the tree value operator. + * @param operator the tree value operator to register. + */ + public void registerTreeValueOperator(String name, TreeValueOperator operator) { + registeredTreeValueOperators.put(name, operator); + } + /** * Registers a number implementation under the given name. * @@ -126,20 +153,40 @@ public class PluginManager { * @param name the name of the function. * @return the function, or null if it was not found. */ - public Function functionFor(String name) { + public NumberFunction functionFor(String name) { return registeredFunctions.get(name); } + /** + * Gets the tree value function registered under the given name. + * + * @param name the name of the function. + * @return the function, or null if it was not found. + */ + public TreeValueFunction treeValueFunctionFor(String name) { + return registeredTreeValueFunctions.get(name); + } + /** * Gets the operator registered under the given name. * * @param name the name of the operator. * @return the operator, or null if it was not found. */ - public Operator operatorFor(String name) { + public NumberOperator operatorFor(String name) { return registeredOperators.get(name); } + /** + * Gets the tree value operator registered under the given name. + * + * @param name the name of the tree value operator. + * @return the operator, or null if it was not found. + */ + public TreeValueOperator treeValueOperatorFor(String name) { + return registeredTreeValueOperators.get(name); + } + /** * Gets the number implementation registered under the given name. * @@ -277,7 +324,9 @@ public class PluginManager { plugin.disable(); } registeredFunctions.clear(); + registeredTreeValueFunctions.clear(); registeredOperators.clear(); + registeredTreeValueOperators.clear(); registeredNumberImplementations.clear(); registeredDocumentation.clear(); cachedInterfaceImplementations.clear(); @@ -302,6 +351,15 @@ public class PluginManager { return registeredFunctions.keySet(); } + /** + * Gets all the tree vlaue functions loaded by the PluginManager. + * + * @return the set of all the tree value functions that were loaded. + */ + public Set getAllTreeValueFunctions() { + return registeredTreeValueFunctions.keySet(); + } + /** * Gets all the operators loaded by the Plugin Manager. * @@ -311,6 +369,15 @@ public class PluginManager { return registeredOperators.keySet(); } + /** + * Gets all the tree value operators loaded by the PluginManager. + * + * @return the set of all tree value operators that were loaded. + */ + public Set getAllTreeValueOperators() { + return registeredTreeValueOperators.keySet(); + } + /** * Gets all the number implementations loaded by the Plugin Manager. * diff --git a/core/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java b/core/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java index cc2db4b..16bbba1 100755 --- a/core/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java +++ b/core/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java @@ -18,83 +18,60 @@ public class StandardPlugin extends Plugin { /** * The addition operator, + */ - public static final Operator OP_ADD = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0, new Function() { + public static final NumberOperator OP_ADD = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) { @Override - protected boolean matchesParams(NumberInterface[] params) { - return params.length >= 1; - } - - @Override - protected NumberInterface applyInternal(NumberInterface[] params) { - NumberInterface sum = params[0]; - for (int i = 1; i < params.length; i++) { - sum = sum.add(params[i]); - } - return sum; - } - }); - /** - * The subtraction operator, - - */ - public static final Operator OP_SUBTRACT = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0, new Function() { - @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 2; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { + return params[0].add(params[1]); + } + }; + /** + * The subtraction operator, - + */ + public static final NumberOperator OP_SUBTRACT = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) { + @Override + public boolean matchesParams(NumberInterface[] params) { + return params.length == 2; + } + + @Override + public NumberInterface applyInternal(NumberInterface[] params) { return params[0].subtract(params[1]); } - }); + }; /** * The negation operator, - */ - public static final Operator OP_NEGATE = new Operator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0, new Function() { + public static final NumberOperator OP_NEGATE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0) { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { return params[0].negate(); } - }); + }; /** * The multiplication operator, * */ - public static final Operator OP_MULTIPLY = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() { + public static final NumberOperator OP_MULTIPLY = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) { @Override - protected boolean matchesParams(NumberInterface[] params) { - return params.length >= 1; + public boolean matchesParams(NumberInterface[] params) { + return params.length == 2; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { - NumberInterface product = params[0]; - for (int i = 1; i < params.length; i++) { - product = product.multiply(params[i]); - } - return product; + public NumberInterface applyInternal(NumberInterface[] params) { + return params[0].multiply(params[1]); } - }); - /** - * The combination operator. - */ - public static final Operator OP_NCR = new Operator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0, new Function() { - @Override - protected boolean matchesParams(NumberInterface[] params) { - return params.length == 2 && params[0].fractionalPart().signum() == 0 - && params[1].fractionalPart().signum() == 0; - } - - @Override - protected NumberInterface applyInternal(NumberInterface[] params) { - return OP_NPR.getFunction().apply(params).divide(OP_FACTORIAL.getFunction().apply(params[1])); - } - }); + }; /** * The implementation for double-based naive numbers. */ @@ -109,6 +86,20 @@ public class StandardPlugin extends Plugin { return new NaiveNumber(Math.PI); } }; + /** + * The square root function. + */ + public static final NumberFunction FUNCTION_SQRT = new NumberFunction() { + @Override + public boolean matchesParams(NumberInterface[] params) { + return params.length == 1; + } + + @Override + public NumberInterface applyInternal(NumberInterface[] params) { + return OP_CARET.apply(params[0], ((new NaiveNumber(0.5)).promoteTo(params[0].getClass()))); + } + }; /** * The implementation for the infinite-precision BigDecimal. */ @@ -152,31 +143,31 @@ public class StandardPlugin extends Plugin { /** * The division operator, / */ - public static final Operator OP_DIVIDE = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() { + public static final NumberOperator OP_DIVIDE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 2 && params[1].compareTo(fromInt(params[0].getClass(), 0)) != 0; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { return params[0].divide(params[1]); } - }); + }; /** * The factorial operator, ! */ - public static final Operator OP_FACTORIAL = new Operator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0, new Function() { + public static final NumberOperator OP_FACTORIAL = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0) { //private HashMap, ArrayList> storedList = new HashMap, ArrayList>(); @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1 && params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0 && params[0].signum() >= 0; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { if (params[0].signum() == 0) { return fromInt(params[0].getClass(), 1); } @@ -194,19 +185,19 @@ public class StandardPlugin extends Plugin { storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass())); }*/ } - }); + }; /** * The permutation operator. */ - public static final Operator OP_NPR = new Operator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0, new Function() { + public static final NumberOperator OP_NPR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 2 && params[0].fractionalPart().signum() == 0 && params[1].fractionalPart().signum() == 0; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { if (params[0].compareTo(params[1]) < 0 || params[0].signum() < 0 || (params[0].signum() == 0 && params[1].signum() != 0)) return fromInt(params[0].getClass(), 0); @@ -224,32 +215,47 @@ public class StandardPlugin extends Plugin { } return total; } - }); + }; + /** + * The combination operator. + */ + public static final NumberOperator OP_NCR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) { + @Override + public boolean matchesParams(NumberInterface[] params) { + return params.length == 2 && params[0].fractionalPart().signum() == 0 + && params[1].fractionalPart().signum() == 0; + } + + @Override + public NumberInterface applyInternal(NumberInterface[] params) { + return OP_NPR.apply(params).divide(OP_FACTORIAL.apply(params[1])); + } + }; /** * The absolute value function, abs(-3) = 3 */ - public static final Function FUNCTION_ABS = new Function() { + public static final NumberFunction FUNCTION_ABS = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { return params[0].multiply(fromInt(params[0].getClass(), params[0].signum())); } }; /** * The natural log function. */ - public static final Function FUNCTION_LN = new Function() { + public static final NumberFunction FUNCTION_LN = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1 && params[0].compareTo(fromInt(params[0].getClass(), 0)) > 0; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { NumberInterface param = params[0]; NumberInterface one = fromInt(param.getClass(), 1); int powersOf2 = 0; @@ -322,73 +328,29 @@ public class StandardPlugin extends Plugin { /** * Gets a random number smaller or equal to the given number's integer value. */ - public static final Function FUNCTION_RAND_INT = new Function() { + public static final NumberFunction FUNCTION_RAND_INT = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { return fromInt(params[0].getClass(), (int) Math.round(Math.random() * params[0].floor().intValue())); } }; - /** - * The caret / pow operator, ^ - */ - public static final Operator OP_CARET = new Operator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2, new Function() { - @Override - protected boolean matchesParams(NumberInterface[] params) { - NumberInterface zero = fromInt(params[0].getClass(), 0); - return params.length == 2 - && !(params[0].compareTo(zero) == 0 - && params[1].compareTo(zero) == 0) - && !(params[0].signum() == -1 && params[1].fractionalPart().compareTo(zero) != 0); - } - - @Override - protected NumberInterface applyInternal(NumberInterface[] params) { - NumberInterface zero = fromInt(params[0].getClass(), 0); - if (params[0].compareTo(zero) == 0) - return zero; - else if (params[1].compareTo(zero) == 0) - return fromInt(params[0].getClass(), 1); - //Detect integer bases: - if (params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0 - && FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[0].getClass(), Integer.MAX_VALUE)) < 0 - && FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[1].getClass(), 1)) >= 0) { - NumberInterface[] newParams = {params[0], params[1].fractionalPart()}; - return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(newParams)); - } - return FUNCTION_EXP.apply(FUNCTION_LN.apply(FUNCTION_ABS.apply(params[0])).multiply(params[1])); - } - }); - /** - * The square root function. - */ - public static final Function FUNCTION_SQRT = new Function() { - @Override - protected boolean matchesParams(NumberInterface[] params) { - return params.length == 1; - } - - @Override - protected NumberInterface applyInternal(NumberInterface[] params) { - return OP_CARET.getFunction().apply(params[0], ((new NaiveNumber(0.5)).promoteTo(params[0].getClass()))); - } - }; private static final HashMap, ArrayList> FACTORIAL_LISTS = new HashMap<>(); /** * The exponential function, exp(1) = e^1 = 2.71... */ - public static final Function FUNCTION_EXP = new Function() { + public static final NumberFunction FUNCTION_EXP = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { NumberInterface maxError = params[0].getMaxError(); int n = 0; if (params[0].signum() < 0) { @@ -415,17 +377,47 @@ public class StandardPlugin extends Plugin { } } }; + /** + * The caret / pow operator, ^ + */ + public static final NumberOperator OP_CARET = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2) { + @Override + public boolean matchesParams(NumberInterface[] params) { + NumberInterface zero = fromInt(params[0].getClass(), 0); + return params.length == 2 + && !(params[0].compareTo(zero) == 0 + && params[1].compareTo(zero) == 0) + && !(params[0].signum() == -1 && params[1].fractionalPart().compareTo(zero) != 0); + } + + @Override + public NumberInterface applyInternal(NumberInterface[] params) { + NumberInterface zero = fromInt(params[0].getClass(), 0); + if (params[0].compareTo(zero) == 0) + return zero; + else if (params[1].compareTo(zero) == 0) + return fromInt(params[0].getClass(), 1); + //Detect integer bases: + if (params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0 + && FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[0].getClass(), Integer.MAX_VALUE)) < 0 + && FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[1].getClass(), 1)) >= 0) { + NumberInterface[] newParams = {params[0], params[1].fractionalPart()}; + return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(newParams)); + } + return FUNCTION_EXP.apply(FUNCTION_LN.apply(FUNCTION_ABS.apply(params[0])).multiply(params[1])); + } + }; /** * The sine function (the argument is interpreted in radians). */ - public final Function functionSin = new Function() { + public final NumberFunction functionSin = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { NumberInterface pi = piFor(params[0].getClass()); NumberInterface twoPi = pi.multiply(fromInt(pi.getClass(), 2)); NumberInterface theta = getSmallAngle(params[0], pi); @@ -442,14 +434,14 @@ public class StandardPlugin extends Plugin { /** * The cosine function (the argument is in radians). */ - public final Function functionCos = new Function() { + public final NumberFunction functionCos = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { return functionSin.apply(piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2)) .subtract(params[0])); } @@ -457,56 +449,56 @@ public class StandardPlugin extends Plugin { /** * The tangent function (the argument is in radians). */ - public final Function functionTan = new Function() { + public final NumberFunction functionTan = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { return functionSin.apply(params[0]).divide(functionCos.apply(params[0])); } }; /** * The secant function (the argument is in radians). */ - public final Function functionSec = new Function() { + public final NumberFunction functionSec = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { return fromInt(params[0].getClass(), 1).divide(functionCos.apply(params[0])); } }; /** * The cosecant function (the argument is in radians). */ - public final Function functionCsc = new Function() { + public final NumberFunction functionCsc = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { return fromInt(params[0].getClass(), 1).divide(functionSin.apply(params[0])); } }; /** * The cotangent function (the argument is in radians). */ - public final Function functionCot = new Function() { + public final NumberFunction functionCot = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { return functionCos.apply(params[0]).divide(functionSin.apply(params[0])); } }; @@ -514,15 +506,15 @@ public class StandardPlugin extends Plugin { /** * The arcsine function (return type in radians). */ - public final Function functionArcsin = new Function() { + public final NumberFunction functionArcsin = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1 && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) <= 0; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { if (FUNCTION_ABS.apply(params[0]).compareTo(new NaiveNumber(0.8).promoteTo(params[0].getClass())) >= 0) { NumberInterface[] newParams = {FUNCTION_SQRT.apply(fromInt(params[0].getClass(), 1).subtract(params[0].multiply(params[0])))}; return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2)) @@ -547,14 +539,14 @@ public class StandardPlugin extends Plugin { /** * The arccosine function. */ - public final Function functionArccos = new Function() { + public final NumberFunction functionArccos = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1 && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) <= 0; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2)) .subtract(functionArcsin.apply(params)); } @@ -563,14 +555,14 @@ public class StandardPlugin extends Plugin { /** * The arccosecant function. */ - public final Function functionArccsc = new Function() { + public final NumberFunction functionArccsc = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1 && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) >= 0; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { NumberInterface[] reciprocalParamArr = {fromInt(params[0].getClass(), 1).divide(params[0])}; return functionArcsin.apply(reciprocalParamArr); } @@ -579,14 +571,14 @@ public class StandardPlugin extends Plugin { /** * The arcsecant function. */ - public final Function functionArcsec = new Function() { + public final NumberFunction functionArcsec = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1 && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) >= 0; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { NumberInterface[] reciprocalParamArr = {fromInt(params[0].getClass(), 1).divide(params[0])}; return functionArccos.apply(reciprocalParamArr); } @@ -595,14 +587,14 @@ public class StandardPlugin extends Plugin { /** * The arctangent function. */ - public final Function functionArctan = new Function() { + public final NumberFunction functionArctan = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { if (params[0].signum() == -1) { NumberInterface[] negatedParams = {params[0].negate()}; return applyInternal(negatedParams).negate(); @@ -636,14 +628,14 @@ public class StandardPlugin extends Plugin { /** * The arccotangent function. Range: (0, pi). */ - public final Function functionArccot = new Function() { + public final NumberFunction functionArccot = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 1; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2)) .subtract(functionArctan.apply(params)); } diff --git a/core/src/main/java/org/nwapw/abacus/tree/NumberReducer.java b/core/src/main/java/org/nwapw/abacus/tree/NumberReducer.java index 4dae775..fa73958 100644 --- a/core/src/main/java/org/nwapw/abacus/tree/NumberReducer.java +++ b/core/src/main/java/org/nwapw/abacus/tree/NumberReducer.java @@ -1,7 +1,7 @@ package org.nwapw.abacus.tree; import org.nwapw.abacus.Abacus; -import org.nwapw.abacus.function.Function; +import org.nwapw.abacus.function.*; import org.nwapw.abacus.number.NumberInterface; /** @@ -30,25 +30,45 @@ public class NumberReducer implements Reducer { return abacus.numberFromString(((NumberNode) node).getNumber()); } else if(node instanceof VariableNode) { return abacus.numberFromString("0"); - } else if (node instanceof BinaryNode) { + } else if (node instanceof NumberBinaryNode) { NumberInterface left = (NumberInterface) children[0]; NumberInterface right = (NumberInterface) children[1]; - Function function = abacus.getPluginManager().operatorFor(((BinaryNode) node).getOperation()).getFunction(); - if (function == null) return null; - return function.apply(left, right); - } else if (node instanceof UnaryNode) { + NumberOperator operator = abacus.getPluginManager().operatorFor(((BinaryNode) node).getOperation()); + return operator.apply(left, right); + } else if (node instanceof NumberUnaryNode) { NumberInterface child = (NumberInterface) children[0]; - Function functionn = abacus.getPluginManager().operatorFor(((UnaryNode) node).getOperation()).getFunction(); - if (functionn == null) return null; - return functionn.apply(child); + NumberOperator operator = abacus.getPluginManager().operatorFor(((UnaryNode) node).getOperation()); + return operator.apply(child); } else if (node instanceof FunctionNode) { NumberInterface[] convertedChildren = new NumberInterface[children.length]; for (int i = 0; i < convertedChildren.length; i++) { convertedChildren[i] = (NumberInterface) children[i]; } - Function function = abacus.getPluginManager().functionFor(((FunctionNode) node).getFunction()); + NumberFunction function = abacus.getPluginManager().functionFor(((FunctionNode) node).getCallTo()); if (function == null) return null; return function.apply(convertedChildren); + } else if (node instanceof TreeValueFunctionNode){ + CallNode callNode = (CallNode) node; + TreeNode[] realChildren = new TreeNode[callNode.getChildren().size()]; + for(int i = 0; i < realChildren.length; i++){ + realChildren[i] = callNode.getChildren().get(i); + } + TreeValueFunction function = + abacus.getPluginManager().treeValueFunctionFor(callNode.getCallTo()); + if(function == null) return null; + return function.applyWithReducer(this, realChildren); + } else if (node instanceof TreeValueBinaryNode) { + BinaryNode binaryNode = (BinaryNode) node; + TreeValueOperator operator = abacus.getPluginManager() + .treeValueOperatorFor(binaryNode.getOperation()); + if(operator == null) return null; + return operator.applyWithReducer(this, binaryNode.getLeft(), binaryNode.getRight()); + } else if(node instanceof TreeValueUnaryNode) { + UnaryNode unaryNode = (UnaryNode) node; + TreeValueOperator operator = abacus.getPluginManager() + .treeValueOperatorFor(unaryNode.getOperation()); + if(operator == null) return null; + return operator.applyWithReducer(this, unaryNode.getApplyTo()); } return null; } diff --git a/core/src/main/java/org/nwapw/abacus/tree/TokenType.java b/core/src/main/java/org/nwapw/abacus/tree/TokenType.java index aa1957a..7df9a94 100644 --- a/core/src/main/java/org/nwapw/abacus/tree/TokenType.java +++ b/core/src/main/java/org/nwapw/abacus/tree/TokenType.java @@ -7,7 +7,8 @@ package org.nwapw.abacus.tree; public enum TokenType { INTERNAL_FUNCTION_END(-1), - ANY(0), WHITESPACE(1), COMMA(2), OP(3), NUM(4), VARIABLE(5), FUNCTION(6), OPEN_PARENTH(7), CLOSE_PARENTH(8); + ANY(0), WHITESPACE(1), COMMA(2), VARIABLE(3), OP(4), TREE_VALUE_OP(4), + NUM(5), FUNCTION(6), TREE_VALUE_FUNCTION(6), OPEN_PARENTH(7), CLOSE_PARENTH(7); /** * The priority by which this token gets sorted. diff --git a/core/src/main/kotlin/org/nwapw/abacus/function/NumberFunction.kt b/core/src/main/kotlin/org/nwapw/abacus/function/NumberFunction.kt new file mode 100644 index 0000000..f76f147 --- /dev/null +++ b/core/src/main/kotlin/org/nwapw/abacus/function/NumberFunction.kt @@ -0,0 +1,12 @@ +package org.nwapw.abacus.function + +import org.nwapw.abacus.function.applicable.Applicable +import org.nwapw.abacus.number.NumberInterface + +/** + * A function that operates on numbers. + * + * This function takes some number of input NumberInterfaces and returns + * another NumberInterface as a result. + */ +abstract class NumberFunction : Applicable \ No newline at end of file diff --git a/core/src/main/kotlin/org/nwapw/abacus/function/NumberOperator.kt b/core/src/main/kotlin/org/nwapw/abacus/function/NumberOperator.kt new file mode 100644 index 0000000..81349c3 --- /dev/null +++ b/core/src/main/kotlin/org/nwapw/abacus/function/NumberOperator.kt @@ -0,0 +1,17 @@ +package org.nwapw.abacus.function + +import org.nwapw.abacus.function.applicable.Applicable +import org.nwapw.abacus.number.NumberInterface + +/** + * An operator that operates on NumberImplementations. + * + * This is simply an alias for Operator. + * @param associativity the associativity of the operator. + * @param type the type of the operator (binary, unary, etc) + * @param precedence the precedence of the operator. + */ +abstract class NumberOperator(associativity: OperatorAssociativity, type: OperatorType, + precedence: Int) : + Operator(associativity, type, precedence), + Applicable \ No newline at end of file diff --git a/core/src/main/kotlin/org/nwapw/abacus/function/Operator.kt b/core/src/main/kotlin/org/nwapw/abacus/function/Operator.kt index e0cb60f..754589b 100644 --- a/core/src/main/kotlin/org/nwapw/abacus/function/Operator.kt +++ b/core/src/main/kotlin/org/nwapw/abacus/function/Operator.kt @@ -3,12 +3,11 @@ package org.nwapw.abacus.function /** * A single operator that can be used by Abacus. * - * This is a data class that holds the information about a single operator, such as a plus or minus. + * This is a class that holds the information about a single operator, such as a plus or minus. * * @param associativity the associativity of this operator, used for order of operations;. * @param type the type of this operator, used for parsing (infix / prefix / postfix and binary / unary) * @param precedence the precedence of this operator, used for order of operations. - * @param function the function this operator applies to its arguments. */ -data class Operator(val associativity: OperatorAssociativity, val type: OperatorType, - val precedence: Int, val function: Function) \ No newline at end of file +open class Operator(val associativity: OperatorAssociativity, val type: OperatorType, + val precedence: Int) \ No newline at end of file diff --git a/core/src/main/kotlin/org/nwapw/abacus/function/TreeValueFunction.kt b/core/src/main/kotlin/org/nwapw/abacus/function/TreeValueFunction.kt new file mode 100644 index 0000000..7b31d92 --- /dev/null +++ b/core/src/main/kotlin/org/nwapw/abacus/function/TreeValueFunction.kt @@ -0,0 +1,13 @@ +package org.nwapw.abacus.function + +import org.nwapw.abacus.function.applicable.ReducerApplicable +import org.nwapw.abacus.number.NumberInterface +import org.nwapw.abacus.tree.TreeNode + +/** + * A function that operates on trees. + * + * A function that operates on parse tree nodes instead of on already simplified numbers. + * Despite this, it returns a number, not a tree. + */ +abstract class TreeValueFunction : ReducerApplicable diff --git a/core/src/main/kotlin/org/nwapw/abacus/function/TreeValueOperator.kt b/core/src/main/kotlin/org/nwapw/abacus/function/TreeValueOperator.kt new file mode 100644 index 0000000..7c4eea7 --- /dev/null +++ b/core/src/main/kotlin/org/nwapw/abacus/function/TreeValueOperator.kt @@ -0,0 +1,18 @@ +package org.nwapw.abacus.function + +import org.nwapw.abacus.function.applicable.ReducerApplicable +import org.nwapw.abacus.number.NumberInterface +import org.nwapw.abacus.tree.TreeNode + +/** + * An operator that operates on trees. + * + * This operator operates on parse trees, returning, however a number. + * @param associativity the associativity of the operator. + * @param type the type of the operator (infix, postfix, etc) + * @param precedence the precedence of the operator. + */ +abstract class TreeValueOperator(associativity: OperatorAssociativity, type: OperatorType, + precedence: Int) : + Operator(associativity, type, precedence), + ReducerApplicable \ No newline at end of file diff --git a/core/src/main/kotlin/org/nwapw/abacus/function/applicable/Applicable.kt b/core/src/main/kotlin/org/nwapw/abacus/function/applicable/Applicable.kt new file mode 100644 index 0000000..5e219c1 --- /dev/null +++ b/core/src/main/kotlin/org/nwapw/abacus/function/applicable/Applicable.kt @@ -0,0 +1,40 @@ +package org.nwapw.abacus.function.applicable + +/** + * A class that can be applied to arguments. + * + * Applicable is a class that represents something that can be applied to one or more + * arguments of the same type, and returns a single value from that application. + * @param the type of the parameters passed to this applicable. + * @param the return type of the applicable. + */ +interface Applicable { + + /** + * Checks if the given applicable can be used with the given parameters. + * @param params the parameter array to verify for compatibility. + * @return whether the array can be used with applyInternal. + */ + fun matchesParams(params: Array): Boolean + + /** + * Applies the applicable object to the given parameters, + * without checking for compatibility. + * @param params the parameters to apply to. + * @return the result of the application. + */ + fun applyInternal(params: Array): O? + + /** + * If the parameters can be used with this applicable, returns + * the result of the application of the applicable to the parameters. + * Otherwise, returns null. + * @param params the parameters to apply to. + * @return the result of the operation, or null if parameters do not match. + */ + fun apply(vararg params: T): O? { + if (!matchesParams(params)) return null + return applyInternal(params) + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/nwapw/abacus/function/applicable/ReducerApplicable.kt b/core/src/main/kotlin/org/nwapw/abacus/function/applicable/ReducerApplicable.kt new file mode 100644 index 0000000..6cef5e1 --- /dev/null +++ b/core/src/main/kotlin/org/nwapw/abacus/function/applicable/ReducerApplicable.kt @@ -0,0 +1,43 @@ +package org.nwapw.abacus.function.applicable + +import org.nwapw.abacus.tree.Reducer + +/** + * Applicable that requires a reducer. + * + * ReducerApplicable slightly more specific Applicable that requires a reducer + * to be passed to it along with the parameters. + * @param the type of the input arguments. + * @param the return type of the application. + * @param the required type of the reducer. + */ +interface ReducerApplicable { + + /** + * Checks if this applicable can be applied to the + * given parameters. + * @param params the parameters to check. + */ + fun matchesParams(params: Array): Boolean + + /** + * Applies this applicable to the given arguments, and reducer. + * @param reducer the reducer to use in the application. + * @param params the arguments to apply to. + * @return the result of the application. + */ + fun applyWithReducerInternal(reducer: Reducer, params: Array): O? + + /** + * Applies this applicable to the given arguments, and reducer, + * if the arguments and reducer are compatible with this applicable. + * @param reducer the reducer to use in the application. + * @param params the arguments to apply to. + * @return the result of the application, or null if the arguments are incompatible. + */ + fun applyWithReducer(reducer: Reducer, vararg params: T): O? { + if (!matchesParams(params)) return null + return applyWithReducerInternal(reducer, params) + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/nwapw/abacus/tree/BinaryNode.kt b/core/src/main/kotlin/org/nwapw/abacus/tree/BinaryNode.kt index 7505f82..4d0d9dd 100644 --- a/core/src/main/kotlin/org/nwapw/abacus/tree/BinaryNode.kt +++ b/core/src/main/kotlin/org/nwapw/abacus/tree/BinaryNode.kt @@ -11,13 +11,7 @@ package org.nwapw.abacus.tree * @param left the left node. * @param right the right node. */ -data class BinaryNode(val operation: String, val left: TreeNode? = null, val right: TreeNode?) : TreeNode() { - - override fun reduce(reducer: Reducer): T? { - val leftReduce = left?.reduce(reducer) ?: return null - val rightReduce = right?.reduce(reducer) ?: return null - return reducer.reduceNode(this, leftReduce, rightReduce) - } +abstract class BinaryNode(val operation: String, val left: TreeNode? = null, val right: TreeNode?) : TreeNode() { override fun toString(): String { return "(" + (left?.toString() ?: "null") + operation + (right?.toString() ?: "null") + ")" diff --git a/core/src/main/kotlin/org/nwapw/abacus/tree/CallNode.kt b/core/src/main/kotlin/org/nwapw/abacus/tree/CallNode.kt new file mode 100644 index 0000000..5f99494 --- /dev/null +++ b/core/src/main/kotlin/org/nwapw/abacus/tree/CallNode.kt @@ -0,0 +1,29 @@ +package org.nwapw.abacus.tree + +/** + * Represents a more generic function call. + * + * This class does not specify how it should be reduced, allowing other classes + * to extend this functionality. + * + * @param callTo the name of the things being called. + */ +abstract class CallNode(val callTo: String) : TreeNode() { + + /** + * The list of children this node has. + */ + val children: MutableList = mutableListOf() + + override fun toString(): String { + val buffer = StringBuffer() + buffer.append(callTo) + buffer.append("(") + for (i in 0 until children.size) { + buffer.append(children[i].toString()) + buffer.append(if (i != children.size - 1) ", " else ")") + } + return buffer.toString() + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/nwapw/abacus/tree/FunctionNode.kt b/core/src/main/kotlin/org/nwapw/abacus/tree/FunctionNode.kt index 64c1298..813ad52 100644 --- a/core/src/main/kotlin/org/nwapw/abacus/tree/FunctionNode.kt +++ b/core/src/main/kotlin/org/nwapw/abacus/tree/FunctionNode.kt @@ -8,45 +8,11 @@ package org.nwapw.abacus.tree * * @param function the function string. */ -data class FunctionNode(val function: String) : TreeNode() { - - /** - * List of function parameters added to this node. - */ - val children: MutableList = mutableListOf() +class FunctionNode(function: String) : CallNode(function) { override fun reduce(reducer: Reducer): T? { val children = Array(children.size, { children[it].reduce(reducer) ?: return null; }) return reducer.reduceNode(this, *children) } - override fun toString(): String { - val buffer = StringBuffer() - buffer.append(function) - buffer.append('(') - for (i in 0 until children.size) { - buffer.append(children[i].toString()) - buffer.append(if (i == children.size - 1) ")" else ",") - } - return buffer.toString() - } - - /** - * Appends a child to this node's list of children. - * - * @node the node to append. - */ - fun appendChild(node: TreeNode) { - children.add(node) - } - - /** - * Prepends a child to this node's list of children. - * - * @node the node to prepend. - */ - fun prependChild(node: TreeNode) { - children.add(0, node) - } - } \ No newline at end of file diff --git a/core/src/main/kotlin/org/nwapw/abacus/tree/NumberBinaryNode.kt b/core/src/main/kotlin/org/nwapw/abacus/tree/NumberBinaryNode.kt new file mode 100644 index 0000000..74c2206 --- /dev/null +++ b/core/src/main/kotlin/org/nwapw/abacus/tree/NumberBinaryNode.kt @@ -0,0 +1,22 @@ +package org.nwapw.abacus.tree + +/** + * A binary operator node that reduces its children. + * + * NumberBinaryNode operates by simply reducing its children and + * then using the result of that reduction to reduce itself. + * + * @param operation the operation this node performs. + * @param left the left child of this node. + * @param right the right child of this node. + */ +class NumberBinaryNode(operation: String, left: TreeNode?, right: TreeNode?) + : BinaryNode(operation, left, right) { + + override fun reduce(reducer: Reducer): T? { + val left = left?.reduce(reducer) ?: return null + val right = right?.reduce(reducer) ?: return null + return reducer.reduceNode(this, left, right) + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/nwapw/abacus/tree/NumberNode.kt b/core/src/main/kotlin/org/nwapw/abacus/tree/NumberNode.kt index de6597a..3507568 100644 --- a/core/src/main/kotlin/org/nwapw/abacus/tree/NumberNode.kt +++ b/core/src/main/kotlin/org/nwapw/abacus/tree/NumberNode.kt @@ -1,7 +1,5 @@ package org.nwapw.abacus.tree -import org.nwapw.abacus.number.NumberInterface - /** * A tree node that holds a single number value. * @@ -10,7 +8,7 @@ import org.nwapw.abacus.number.NumberInterface * * @number the number value of this node. */ -data class NumberNode(val number: String) : TreeNode() { +class NumberNode(val number: String) : TreeNode() { override fun reduce(reducer: Reducer): T? { return reducer.reduceNode(this) diff --git a/core/src/main/kotlin/org/nwapw/abacus/tree/NumberUnaryNode.kt b/core/src/main/kotlin/org/nwapw/abacus/tree/NumberUnaryNode.kt new file mode 100644 index 0000000..e4fee3f --- /dev/null +++ b/core/src/main/kotlin/org/nwapw/abacus/tree/NumberUnaryNode.kt @@ -0,0 +1,19 @@ +package org.nwapw.abacus.tree + +/** + * A unary operator node that reduces its children. + * + * NumberUnaryNode operates by simply reducing its child, + * and using the result of that reduction to reduce itself. + * @param operation the operation this node performs. + * @param child the child this node should be applied to. + */ +class NumberUnaryNode(operation: String, child: TreeNode?) + : UnaryNode(operation, child) { + + override fun reduce(reducer: Reducer): T? { + val child = applyTo?.reduce(reducer) ?: return null + return reducer.reduceNode(this, child) + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/nwapw/abacus/tree/TreeValueBinaryNode.kt b/core/src/main/kotlin/org/nwapw/abacus/tree/TreeValueBinaryNode.kt new file mode 100644 index 0000000..4edfef3 --- /dev/null +++ b/core/src/main/kotlin/org/nwapw/abacus/tree/TreeValueBinaryNode.kt @@ -0,0 +1,21 @@ +package org.nwapw.abacus.tree + +/** + * A tree node that represents a binary tree value operator. + * + * + * The tree value operators operate on trees, and so this + * node does not reduce its children. It is up to the implementation to handle + * reduction. + * @param operation the operation this node performs. + * @param left the left child of this node. + * @param right the right child of this node. + */ +class TreeValueBinaryNode(operation: String, left: TreeNode?, right: TreeNode?) + : BinaryNode(operation, left, right) { + + override fun reduce(reducer: Reducer): T? { + return reducer.reduceNode(this) + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/nwapw/abacus/tree/TreeValueFunctionNode.kt b/core/src/main/kotlin/org/nwapw/abacus/tree/TreeValueFunctionNode.kt new file mode 100644 index 0000000..68c358e --- /dev/null +++ b/core/src/main/kotlin/org/nwapw/abacus/tree/TreeValueFunctionNode.kt @@ -0,0 +1,16 @@ +package org.nwapw.abacus.tree + +/** + * A tree node that represents a tree value function call. + * + * This is in many ways similar to a simple FunctionNode, and the distinction + * is mostly to help the reducer. Besides that, this class also does not + * even attempt to reduce its children. + */ +class TreeValueFunctionNode(name: String) : CallNode(name) { + + override fun reduce(reducer: Reducer): T? { + return reducer.reduceNode(this) + } + +} diff --git a/core/src/main/kotlin/org/nwapw/abacus/tree/TreeValueUnaryNode.kt b/core/src/main/kotlin/org/nwapw/abacus/tree/TreeValueUnaryNode.kt new file mode 100644 index 0000000..fdc0db5 --- /dev/null +++ b/core/src/main/kotlin/org/nwapw/abacus/tree/TreeValueUnaryNode.kt @@ -0,0 +1,19 @@ +package org.nwapw.abacus.tree + +/** + * A tree node that represents a unary tree value operator. + * + * The tree value operators operate on trees, and so this + * node does not reduce its children. It is up to the implementation to handle + * reduction. + * @param operation the operation this node performs. + * @param child the node the operation should be applied to. + */ +class TreeValueUnaryNode(operation: String, child: TreeNode?) + : UnaryNode(operation, child) { + + override fun reduce(reducer: Reducer): T? { + return reducer.reduceNode(this); + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/nwapw/abacus/tree/UnaryNode.kt b/core/src/main/kotlin/org/nwapw/abacus/tree/UnaryNode.kt index 65853ba..285231b 100644 --- a/core/src/main/kotlin/org/nwapw/abacus/tree/UnaryNode.kt +++ b/core/src/main/kotlin/org/nwapw/abacus/tree/UnaryNode.kt @@ -9,12 +9,7 @@ package org.nwapw.abacus.tree * @param operation the operation applied to the given node. * @param applyTo the node to which the operation will be applied. */ -data class UnaryNode(val operation: String, val applyTo: TreeNode? = null) : TreeNode() { - - override fun reduce(reducer: Reducer): T? { - val reducedChild = applyTo?.reduce(reducer) ?: return null - return reducer.reduceNode(this, reducedChild) - } +abstract class UnaryNode(val operation: String, val applyTo: TreeNode? = null) : TreeNode() { override fun toString(): String { return "(" + (applyTo?.toString() ?: "null") + ")" + operation diff --git a/core/src/main/kotlin/org/nwapw/abacus/tree/VariableNode.kt b/core/src/main/kotlin/org/nwapw/abacus/tree/VariableNode.kt index 0bf05c6..93d91dc 100644 --- a/core/src/main/kotlin/org/nwapw/abacus/tree/VariableNode.kt +++ b/core/src/main/kotlin/org/nwapw/abacus/tree/VariableNode.kt @@ -8,7 +8,7 @@ package org.nwapw.abacus.tree * * @param variable the actual variable name that this node represents. */ -data class VariableNode(val variable: String) : TreeNode() { +class VariableNode(val variable: String) : TreeNode() { override fun reduce(reducer: Reducer): T? { return reducer.reduceNode(this) diff --git a/core/src/test/java/org/nwapw/abacus/tests/TokenizerTests.java b/core/src/test/java/org/nwapw/abacus/tests/TokenizerTests.java index 56515e3..79a7cd2 100644 --- a/core/src/test/java/org/nwapw/abacus/tests/TokenizerTests.java +++ b/core/src/test/java/org/nwapw/abacus/tests/TokenizerTests.java @@ -5,10 +5,7 @@ import org.junit.BeforeClass; import org.junit.Test; import org.nwapw.abacus.Abacus; import org.nwapw.abacus.config.Configuration; -import org.nwapw.abacus.function.Function; -import org.nwapw.abacus.function.Operator; -import org.nwapw.abacus.function.OperatorAssociativity; -import org.nwapw.abacus.function.OperatorType; +import org.nwapw.abacus.function.*; import org.nwapw.abacus.lexing.pattern.Match; import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.parsing.LexerTokenizer; @@ -21,24 +18,46 @@ public class TokenizerTests { private static Abacus abacus = new Abacus(new Configuration(0, "precise", new String[]{})); private static LexerTokenizer lexerTokenizer = new LexerTokenizer(); - private static Function subtractFunction = new Function() { + private static NumberFunction subtractFunction = new NumberFunction() { @Override - protected boolean matchesParams(NumberInterface[] params) { + public boolean matchesParams(NumberInterface[] params) { return params.length == 2; } @Override - protected NumberInterface applyInternal(NumberInterface[] params) { + public NumberInterface applyInternal(NumberInterface[] params) { return params[0].subtract(params[1]); } }; private static Plugin testPlugin = new Plugin(abacus.getPluginManager()) { @Override public void onEnable() { - registerOperator("+", new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, - 0, subtractFunction)); - registerOperator("-", new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, - 0, subtractFunction)); + registerOperator("+", new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, + 0) { + + @Override + public boolean matchesParams(NumberInterface[] params) { + return true; + } + + @Override + public NumberInterface applyInternal(NumberInterface[] params) { + return subtractFunction.apply(params); + } + }); + registerOperator("-", new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, + 0) { + + @Override + public boolean matchesParams(NumberInterface[] params) { + return true; + } + + @Override + public NumberInterface applyInternal(NumberInterface[] params) { + return subtractFunction.apply(params); + } + }); registerFunction("subtract", subtractFunction); } diff --git a/fx/src/main/java/org/nwapw/abacus/fx/AbacusController.java b/fx/src/main/java/org/nwapw/abacus/fx/AbacusController.java index 0194953..e8096fd 100644 --- a/fx/src/main/java/org/nwapw/abacus/fx/AbacusController.java +++ b/fx/src/main/java/org/nwapw/abacus/fx/AbacusController.java @@ -360,6 +360,8 @@ public class AbacusController implements PluginListener { PluginManager pluginManager = abacus.getPluginManager(); functionList.addAll(manager.getAllFunctions().stream().map(name -> pluginManager.documentationFor(name, DocumentationType.FUNCTION)) .collect(Collectors.toCollection(ArrayList::new))); + functionList.addAll(manager.getAllTreeValueFunctions().stream().map(name -> pluginManager.documentationFor(name, DocumentationType.TREE_VALUE_FUNCTION)) + .collect(Collectors.toCollection(ArrayList::new))); functionList.sort(Comparator.comparing(Documentation::getCodeName)); }