1
0
mirror of https://github.com/DanilaFe/abacus synced 2026-01-26 00:25:20 +00:00

Compare commits

..

21 Commits

Author SHA1 Message Date
1190ece7dd Implement a PluginLoader, which can load plugin jars. 2017-07-27 18:10:20 -07:00
f119f19c04 Make pow an operator, represented by caret. 2017-07-27 16:55:18 -07:00
65772c8d57 Fix function argument order. 2017-07-27 16:52:16 -07:00
bbbb2e855e Fix typo. 2017-07-27 16:37:54 -07:00
8a29019852 Add ignoring whitespace and fix function precedence. 2017-07-27 16:36:13 -07:00
0d7a416446 Add a missing comment. 2017-07-27 16:27:26 -07:00
167e13cfe1 Merge branch 'master' of github.com:DanilaFe/abacus 2017-07-27 15:28:21 -07:00
b0ae3f90fc Add sanitization to TreeBuilder. 2017-07-27 15:26:02 -07:00
a7c2084254 Remove backslashes that will be obsolete. 2017-07-27 15:02:13 -07:00
rileyJones
bf6f48bf82 Jar Plugin Loader 2017-07-27 14:33:08 -07:00
f7da896fc0 Fix several bugs and register operations as operations. 2017-07-27 14:15:45 -07:00
6813643b15 Merge branch 'plugins' 2017-07-27 14:08:40 -07:00
e6cb755ec9 Merge branch 'master' of github.com:DanilaFe/abacus 2017-07-27 14:08:31 -07:00
Arthur Drobot
088a45cf4c Add sqrt function. 2017-07-27 13:47:51 -07:00
Arthur Drobot
557bc66e53 Begin working on memoization for factorial. (Commented out for now.) 2017-07-27 13:39:19 -07:00
Arthur Drobot
9666ef9019 Add pow function. 2017-07-27 13:17:22 -07:00
Arthur Drobot
ba30227b28 Add natural log function. May not be terribly efficient currently, but it works and is usable. 2017-07-27 13:04:41 -07:00
Arthur Drobot
ea5a7a9558 Increase precision of NaiveNumber to 18. 2017-07-27 10:32:09 -07:00
Arthur Drobot
3e52a9d645 Modify exp to work properly with the new changes and support all reals. 2017-07-27 10:16:38 -07:00
Arthur Drobot
7a0fa31cad Merge branch 'master' of https://github.com/DanilaFe/abacus 2017-07-27 10:07:07 -07:00
Arthur Drobot
aec37b6720 Add absolute value function to standard plugin. Modify getNTermsExp to work on negative exponents instead (and correctly). 2017-07-27 10:03:26 -07:00
10 changed files with 359 additions and 28 deletions

View File

@@ -3,7 +3,7 @@ package org.nwapw.abacus.function;
/**
* A class that represents a single infix operator.
*/
public abstract class Operator {
public class Operator {
/**
* The associativity of the operator.

View File

@@ -235,4 +235,22 @@ public class Pattern<T> {
public PatternNode<T> getHead() {
return head;
}
/**
* Removes all characters that are considered "special" from
* the given string.
* @param from the string to sanitize.
* @return the resulting string.
*/
public static String sanitize(String from){
Pattern<Integer> pattern = new Pattern<>("", 0);
from = from.replace(".", "\\.");
from = from.replace("|", "\\|");
from = from.replace("(", "\\(");
from = from.replace(")", "\\)");
for(Character key : pattern.operations.keySet()){
from = from.replace("" + key, "\\" + key);
}
return from;
}
}

View File

@@ -29,7 +29,7 @@ public class NaiveNumber implements NumberInterface {
@Override
public int precision() {
return 15;
return 18;
}
@Override

View File

@@ -0,0 +1,65 @@
package org.nwapw.abacus.plugin;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipInputStream;
public class ClassFinder extends ClassLoader{
ArrayList<Class> classes;
public ClassFinder(){
super(ClassFinder.class.getClassLoader());
classes=new ArrayList();
}
public Class loadClass(String className) throws ClassNotFoundException{
return findClass(className);
}
public ArrayList<String> loadClass(File jarLocation) throws ClassNotFoundException, IOException{
return addJar(jarLocation);
}
public ArrayList<String> addJar(File jarLocation) throws IOException {
JarFile jarFolder = new JarFile(jarLocation);
Enumeration jarList = jarFolder.entries();
HashMap classSize = new HashMap();
HashMap classContent = new HashMap();
ArrayList<String> classNames = new ArrayList();
JarEntry tempJar;
ZipInputStream zipStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(jarLocation)));
while(jarList.hasMoreElements()){
tempJar = (JarEntry)jarList.nextElement();
zipStream.getNextEntry();
if(!tempJar.isDirectory()) {
if (tempJar.getName().substring(tempJar.getName().indexOf('.')).equals(".class") && (tempJar.getName().length() < 9 || !tempJar.getName().substring(0, 9).equals("META-INF/"))) {
int size = (int)tempJar.getSize();
classSize.put(tempJar.getName(),new Integer((int)tempJar.getSize()));
byte[] bytes = new byte[size];
zipStream.read(bytes,0,size);
classContent.put(tempJar.getName(),bytes);
classNames.add(tempJar.getName());
}
}
}
jarFolder.close();
for(String name:classNames) {
classes.add(super.defineClass(name, (byte[]) classContent.get(name), 0, (int) classSize.get(name)));
}
return classNames;
}
public ArrayList<Class> getClasses(){
return classes;
}
public Class getClass(int number){
return classes.get(number);
}
public void delClasses(){
classes=new ArrayList();
}
}

View File

@@ -0,0 +1,106 @@
package org.nwapw.abacus.plugin;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Properties;
import java.util.jar.JarFile;
/**
* A plugin loader, used to scan a directory for
* plugins and load them into classes that can then be
* used by the plugin manager.
*/
public class PluginLoader {
/**
* An internal class that represents a Jar file that
* has been founded, but not loaded.
*/
private static final class PluginEntry {
String mainClass;
File jarPath;
}
/**
* The path which to search for plugins.
*/
private File path;
/**
* The array of loaded plugin main classes.
*/
private ArrayList<Class<?>> foundMainClasses;
/**
* Creates a new plugin loader at the given path.
* @param path the path which to search for plugins.
*/
public PluginLoader(File path) {
this.path = path;
}
/**
* Loads all the plugin classes that have been found.
* @return the list of loaded classes.
* @throws IOException thrown when loading classes from URL fails.
* @throws ClassNotFoundException thrown when the class specified in plugin.properties is missing.
*/
private ArrayList<Class<?>> loadPluginClasses() throws IOException, ClassNotFoundException {
ArrayList<Class<?>> foundMainClasses = new ArrayList<>();
for(PluginEntry entry : findPlugins()){
if(entry.mainClass == null) continue;
ClassLoader loader = URLClassLoader.newInstance(
new URL[]{ entry.jarPath.toURI().toURL() },
getClass().getClassLoader());
Class<?> loadedClass = loader.loadClass(entry.mainClass);
if(!Plugin.class.isAssignableFrom(loadedClass)) continue;
foundMainClasses.add(loadedClass);
}
return foundMainClasses;
}
/**
* Find all jars that have a plugin.properties file in the plugin folder.
* @return the list of all plugin entries, with their main class names and the jars files.
* @throws IOException thrown if reading the jar file fails
*/
private ArrayList<PluginEntry> findPlugins() throws IOException {
ArrayList<PluginEntry> pluginEntries = new ArrayList<>();
File[] childFiles = path.listFiles();
if(childFiles == null) return pluginEntries;
for(File file : childFiles){
if(!file.isFile() || !file.getName().endsWith(".jar")) continue;
JarFile jarFile = new JarFile(file);
if(jarFile.getEntry("plugin.properties") == null) continue;
Properties properties = new Properties();
properties.load(jarFile.getInputStream(jarFile.getEntry("plugin.properties")));
PluginEntry entry = new PluginEntry();
entry.mainClass = properties.getProperty("mainClass");
entry.jarPath = file;
pluginEntries.add(entry);
}
return pluginEntries;
}
/**
* Loads all valid plugins and keeps track of them.
* @throws IOException thrown if loading from jar files fails.
* @throws ClassNotFoundException thrown if class specified in plugin.properties doesn't exist.
*/
public void loadValidPlugins() throws IOException, ClassNotFoundException {
foundMainClasses = loadPluginClasses();
}
/**
* Gets the list of all the plugins that have last been loaded.
* @return the list of loaded class files.
*/
public ArrayList<Class<?>> getFoundMainClasses() {
return foundMainClasses;
}
}

View File

@@ -1,9 +1,14 @@
package org.nwapw.abacus.plugin;
import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.function.OperatorAssociativity;
import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface;
import javax.print.attribute.standard.MediaSize;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.function.BiFunction;
/**
@@ -18,7 +23,7 @@ public class StandardPlugin extends Plugin {
@Override
public void onEnable() {
registerFunction("+", new Function() {
registerOperator("+", new Operator(OperatorAssociativity.LEFT, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length >= 1;
@@ -32,9 +37,9 @@ public class StandardPlugin extends Plugin {
}
return sum;
}
});
}));
registerFunction("-", new Function() {
registerOperator("-", new Operator(OperatorAssociativity.LEFT, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2;
@@ -44,9 +49,9 @@ public class StandardPlugin extends Plugin {
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].subtract(params[1]);
}
});
}));
registerFunction("*", new Function() {
registerOperator("*", new Operator(OperatorAssociativity.LEFT, 1, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length >= 1;
@@ -60,9 +65,9 @@ public class StandardPlugin extends Plugin {
}
return product;
}
});
}));
registerFunction("/", new Function() {
registerOperator("/", new Operator(OperatorAssociativity.LEFT, 1, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2;
@@ -72,9 +77,22 @@ public class StandardPlugin extends Plugin {
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].divide(params[1]);
}
});
}));
registerOperator("^", new Operator(OperatorAssociativity.RIGHT, 2, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return StandardPlugin.this.getFunction("exp").apply(StandardPlugin.this.getFunction("ln").apply(params[0]).multiply(params[1]));
}
}));
registerFunction("!", new Function() {
//private HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> storedList = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>();
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
@@ -92,6 +110,23 @@ public class StandardPlugin extends Plugin {
factorial = factorial.multiply(multiplier);
}
return factorial;
/*if(!storedList.containsKey(params[0].getClass())){
storedList.put(params[0].getClass(), new ArrayList<NumberInterface>());
storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass()));
storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass()));
}*/
}
});
registerFunction("abs", new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].multiply((new NaiveNumber(params[0].signum())).promoteTo(params[0].getClass()));
}
});
@@ -103,7 +138,100 @@ public class StandardPlugin extends Plugin {
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return sumSeries(params[0], StandardPlugin.this::getExpSeriesTerm, getNTermsExp(getMaxError(params[0]), params[0]));
boolean takeReciprocal = params[0].signum() == -1;
params[0] = StandardPlugin.this.getFunction("abs").apply(params[0]);
NumberInterface sum = sumSeries(params[0], StandardPlugin.this::getExpSeriesTerm, getNTermsExp(getMaxError(params[0]), params[0]));
if(takeReciprocal){
sum = NaiveNumber.ONE.promoteTo(sum.getClass()).divide(sum);
}
return sum;
}
});
registerFunction("ln", new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface param = params[0];
int powersOf2 = 0;
while(StandardPlugin.this.getFunction("abs").apply(param.subtract(NaiveNumber.ONE.promoteTo(param.getClass()))).compareTo((new NaiveNumber(0.1)).promoteTo(param.getClass())) >= 0){
if(param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() == 1) {
param = param.divide(new NaiveNumber(2).promoteTo(param.getClass()));
powersOf2++;
if(param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() != 1) {
break;
//No infinite loop for you.
}
}
else {
param = param.multiply(new NaiveNumber(2).promoteTo(param.getClass()));
powersOf2--;
if(param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() != 1) {
break;
//No infinite loop for you.
}
}
}
return getLog2(param).multiply((new NaiveNumber(powersOf2)).promoteTo(param.getClass())).add(getLogPartialSum(param));
}
/**
* Returns the partial sum of the Taylor series for logx (around x=1).
* Automatically determines the number of terms needed based on the precision of x.
* @param x value at which the series is evaluated. 0 < x < 2. (x=2 is convergent but impractical.)
* @return the partial sum.
*/
private NumberInterface getLogPartialSum(NumberInterface x){
NumberInterface maxError = StandardPlugin.this.getMaxError(x);
x = x.subtract(NaiveNumber.ONE.promoteTo(x.getClass())); //Terms used are for log(x+1).
NumberInterface currentTerm = x, sum = x;
int n = 1;
while(StandardPlugin.this.getFunction("abs").apply(currentTerm).compareTo(maxError) > 0){
n++;
currentTerm = currentTerm.multiply(x).multiply((new NaiveNumber(n-1)).promoteTo(x.getClass())).divide((new NaiveNumber(n)).promoteTo(x.getClass())).negate();
sum = sum.add(currentTerm);
}
return sum;
}
/**
* Returns natural log of 2 to the required precision of the class of number.
* @param number a number of the same type as the return type. (Used for precision.)
* @return the value of log(2) with the appropriate precision.
*/
private NumberInterface getLog2(NumberInterface number){
NumberInterface maxError = StandardPlugin.this.getMaxError(number);
//NumberInterface errorBound = (new NaiveNumber(1)).promoteTo(number.getClass());
//We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n)
//In the following, a=1/3^n, b=1/4^n, c = 1/n.
//a is also an error bound.
NumberInterface a = (new NaiveNumber(1)).promoteTo(number.getClass()), b = a, c = a;
NumberInterface sum = NaiveNumber.ZERO.promoteTo(number.getClass());
int n = 0;
while(a.compareTo(maxError) >= 1){
n++;
a = a.divide((new NaiveNumber(3)).promoteTo(number.getClass()));
b = b.divide((new NaiveNumber(4)).promoteTo(number.getClass()));
c = NaiveNumber.ONE.promoteTo(number.getClass()).divide((new NaiveNumber(n)).promoteTo(number.getClass()));
sum = sum.add(a.add(b).multiply(c));
}
return sum;
}
});
registerFunction("sqrt", new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return StandardPlugin.this.getFunction("pow").apply(params[0], (new NaiveNumber(0.5)));
}
});
}
@@ -130,15 +258,16 @@ public class StandardPlugin extends Plugin {
* @param x where the function is evaluated.
* @return the number of terms needed to evaluated the exponential function.
*/
private int getNTermsExp(NumberInterface maxError, NumberInterface x){
//We need n such that x^(n+2) <= (n+1)! * maxError
private int getNTermsExp(NumberInterface maxError, NumberInterface x) {
//We need n such that |x^(n+1)| <= (n+1)! * maxError
//The variables LHS and RHS refer to the above inequality.
int n = 0;
NumberInterface LHS = x.intPow(2), RHS = maxError;
while(LHS.compareTo(RHS) > 0){
x = this.getFunction("abs").apply(x);
NumberInterface LHS = x, RHS = maxError;
while (LHS.compareTo(RHS) > 0) {
n++;
LHS = LHS.multiply(x);
RHS = RHS.multiply(new NaiveNumber(n).promoteTo(RHS.getClass()));
RHS = RHS.multiply(new NaiveNumber(n + 1).promoteTo(RHS.getClass()));
}
return n;
}

View File

@@ -39,13 +39,21 @@ public class FunctionNode extends TreeNode {
}
/**
* Adds a child to this node.
* Adds a child to the end of this node's child list.
* @param node the child to add.
*/
public void addChild(TreeNode node){
public void appendChild(TreeNode node){
children.add(node);
}
/**
* Adds a new child to the beginning of this node's child list.
* @param node the node to add.
*/
public void prependChild(TreeNode node) {
children.add(0, node);
}
@Override
public <T> T reduce(Reducer<T> reducer) {
Object[] reducedChildren = new Object[children.size()];

View File

@@ -30,7 +30,7 @@ public class NumberReducer implements Reducer<NumberInterface> {
} else if(node instanceof OpNode){
NumberInterface left = (NumberInterface) children[0];
NumberInterface right = (NumberInterface) children[1];
Function function = manager.functionFor(((OpNode) node).getOperation());
Function function = manager.operatorFor(((OpNode) node).getOperation()).getFunction();
if(function == null) return null;
return function.apply(left, right);
} else if(node instanceof FunctionNode){

View File

@@ -7,7 +7,7 @@ package org.nwapw.abacus.tree;
public enum TokenType {
INTERNAL_FUNCTION_END(-1),
ANY(0), COMMA(1), OP(2), NUM(3), FUNCTION(4), OPEN_PARENTH(5), CLOSE_PARENTH(6);
ANY(0), WHITESPACE(1), COMMA(2), OP(3), NUM(4), FUNCTION(5), OPEN_PARENTH(6), CLOSE_PARENTH(7);
/**
* The priority by which this token gets sorted.

View File

@@ -3,6 +3,7 @@ package org.nwapw.abacus.tree;
import org.nwapw.abacus.function.OperatorAssociativity;
import org.nwapw.abacus.lexing.Lexer;
import org.nwapw.abacus.lexing.pattern.Match;
import org.nwapw.abacus.lexing.pattern.Pattern;
import java.util.*;
@@ -34,6 +35,7 @@ public class TreeBuilder {
*/
public TreeBuilder(){
lexer = new Lexer<TokenType>(){{
register(" ", TokenType.WHITESPACE);
register(",", TokenType.COMMA);
register("[0-9]+(\\.[0-9]+)?", TokenType.NUM);
register("\\(", TokenType.OPEN_PARENTH);
@@ -48,7 +50,7 @@ public class TreeBuilder {
* @param function the function to register.
*/
public void registerFunction(String function){
lexer.register(function, TokenType.FUNCTION);
lexer.register(Pattern.sanitize(function), TokenType.FUNCTION);
}
/**
@@ -58,7 +60,7 @@ public class TreeBuilder {
* @param associativity the associativity of the operator.
*/
public void registerOperator(String operator, int precedence, OperatorAssociativity associativity){
lexer.register(operator, TokenType.OP);
lexer.register(Pattern.sanitize(operator), TokenType.OP);
precedenceMap.put(operator, precedence);
associativityMap.put(operator, associativity);
}
@@ -97,12 +99,14 @@ public class TreeBuilder {
while(!tokenStack.empty()) {
Match<TokenType> otherMatch = tokenStack.peek();
TokenType otherMatchType = otherMatch.getType();
if(otherMatchType != TokenType.OP) break;
if(!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break;
int otherPrecdence = precedenceMap.get(source.substring(otherMatch.getFrom(), otherMatch.getTo()));
if(otherPrecdence < precedence ||
(associativity == OperatorAssociativity.RIGHT && otherPrecdence == precedence)) {
break;
if(otherMatchType == TokenType.OP){
int otherPrecedence = precedenceMap.get(source.substring(otherMatch.getFrom(), otherMatch.getTo()));
if(otherPrecedence < precedence ||
(associativity == OperatorAssociativity.RIGHT && otherPrecedence == precedence)) {
break;
}
}
output.add(tokenStack.pop());
}
@@ -151,7 +155,7 @@ public class TreeBuilder {
while(!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END){
TreeNode argument = fromStringRecursive(source, matches);
if(argument == null) return null;
node.addChild(argument);
node.prependChild(argument);
}
if(matches.isEmpty()) return null;
matches.remove(0);
@@ -168,6 +172,7 @@ public class TreeBuilder {
public TreeNode fromString(String string){
ArrayList<Match<TokenType>> matches = tokenize(string);
if(matches == null) return null;
matches.removeIf(m -> m.getType() == TokenType.WHITESPACE);
matches = intoPostfix(string, matches);
if(matches == null) return null;