1
0
mirror of https://github.com/DanilaFe/abacus synced 2026-01-25 16:15:19 +00:00

Compare commits

...

64 Commits

Author SHA1 Message Date
1f0addccea Add documentation loading for functions. 2017-08-25 01:51:14 -07:00
1a47e07e97 Add Tree Value Functions to NumberReducer. 2017-08-25 01:41:51 -07:00
26305c3bae Add the withReducer variants of the Applier functions. 2017-08-25 01:41:32 -07:00
6b9252f902 Add parsing of TreeValueFunctions. 2017-08-25 01:21:28 -07:00
bc26ad0b88 Abstract the call functionality, and add TreeValueFunctionNode. 2017-08-25 01:17:52 -07:00
c5cd0f81ad Remove data modifier from tree classes. 2017-08-25 01:07:59 -07:00
ac19c7b230 Change lexer tokenizer to recognize tree value functions. 2017-08-25 01:03:12 -07:00
40c80db914 Add tree value functions to plugins. 2017-08-25 00:59:39 -07:00
00462281fe Add a function that operates on trees. 2017-08-25 00:49:16 -07:00
01f80bbb53 Abstract some of the Function functionality further. 2017-08-25 00:43:36 -07:00
553c7354c1 Account for the new string-only node structure.
The output has to be the same as the user-provided input, as the
tree isn't converted to numbers until evaluation.
2017-08-18 16:31:54 -07:00
50ede6460c Remove Abacus dependency from ShuntingYardParser. 2017-08-18 15:57:48 -07:00
beb583a231 Move number string parsing from the parser into the reducer. 2017-08-18 14:26:33 -07:00
e0ff229df4 Temporarily substitute 0 for variables. 2017-08-18 14:21:48 -07:00
1c751353f1 Lex and parse variables. 2017-08-18 14:21:14 -07:00
0a15043b63 Implement a variable TreeNode. 2017-08-18 14:20:49 -07:00
21e059c1ca Add a new TokenType for variables. 2017-08-18 14:20:37 -07:00
16faceb3cc Add comment to DocumentationType. 2017-08-16 15:38:02 -07:00
251da90d57 Fix problems with scaling on mobile phones. 2017-08-15 01:24:48 -07:00
a7536b198f Add a proper About page. 2017-08-15 01:02:16 -07:00
66bda5db39 Add the download page. 2017-08-15 00:40:28 -07:00
2425adaea5 Remove the rounded borders. 2017-08-15 00:40:21 -07:00
f5d913d527 Change download to link to the Download page. 2017-08-15 00:40:05 -07:00
beae822d76 Move button styling into scss. 2017-08-15 00:31:47 -07:00
bcd3e3b68a Add a home layout. 2017-08-15 00:14:20 -07:00
8b42acfbb1 Change the code font. 2017-08-14 23:42:17 -07:00
952eeac704 Add some style to code. 2017-08-14 22:58:32 -07:00
b57f24854e Add the page layout for pages like About. 2017-08-14 22:58:27 -07:00
6758771c45 Add support for navbar links. 2017-08-14 22:46:39 -07:00
415a05288a Use the center class for centering the content. 2017-08-14 22:36:46 -07:00
334439e075 Add a header. 2017-08-14 22:36:36 -07:00
852776566d Add some basic CSS styling. 2017-08-14 22:36:26 -07:00
07020de1f9 Add the CSS file and link to it from head. 2017-08-14 21:40:37 -07:00
06c2397a09 Use the minima head.html include. 2017-08-14 21:28:28 -07:00
69fe28d5e4 Modify 404 to use the base layout. 2017-08-14 21:22:55 -07:00
367c282497 Start the base page layout. 2017-08-14 21:22:43 -07:00
2876eda2ca Add a new head include. 2017-08-14 21:22:34 -07:00
4ec8c7a535 Remove default theme altogether. 2017-08-14 21:16:35 -07:00
5167946fe2 Overwrite the default header and footer. Temporarily blank. 2017-08-14 21:13:33 -07:00
418e896108 Create the about page. 2017-08-14 21:12:41 -07:00
d3012b9624 Delete the about me page, and add a folder for pages. 2017-08-14 21:11:16 -07:00
bf7881faab Remove the default post. 2017-08-14 19:42:14 -07:00
b75e92838d Configure the default site. 2017-08-14 19:42:06 -07:00
620f087e38 Create basic Jekyll template. 2017-08-14 19:21:17 -07:00
205d5dbc77 Format code. 2017-08-14 19:03:52 -07:00
d861444d13 Delete old, unused code. 2017-08-13 16:49:25 -07:00
eac2a9ed6b Move the JavaFX files written in Kotlin to the FX module. 2017-08-13 01:51:53 -07:00
31c61fdf95 Reduce Kotlin version to 1.7 2017-08-13 01:08:19 -07:00
8f251d2d13 Split the project into separate modules. 2017-08-12 21:11:01 -07:00
99ffd51a43 Merge remote-tracking branch 'origin/inv-trig-doc' 2017-08-11 22:25:45 -07:00
00c51c62fd Merge branch 'inv-trig'
# Conflicts:
#	src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java
2017-08-11 22:13:20 -07:00
400ed6e70a Merge branch 'master' of github.com:DanilaFe/abacus 2017-08-11 22:10:42 -07:00
Arthur Drobot
5902ba8c5c Inverse trig documentation. 2017-08-11 21:07:34 -07:00
Arthur Drobot
efdc4af31b Add examples to documentation of trig functions. 2017-08-11 20:32:39 -07:00
2e0b1201a3 Merge branch 'inv-trig'
# Conflicts:
#	src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java
2017-08-11 12:29:12 -07:00
Arthur Drobot
d94edaa33f Modify sqrt comment. 2017-08-11 01:23:01 -07:00
Arthur Drobot
1e6cf08ec2 Write arctan and add arccot. 2017-08-11 01:19:13 -07:00
7c378b401a Remove useless comment. 2017-08-10 22:16:12 -07:00
Arthur Drobot
fee9f091fd Revert "Write a RationalNumber class."
This reverts commit f3942b6760.
2017-08-09 18:43:52 -07:00
Arthur Drobot
4226df72f5 Add arcsec. Adjust some comments. 2017-08-09 16:04:26 -07:00
Arthur Drobot
1f0e6a7ce4 Add arccsc. 2017-08-09 15:57:02 -07:00
Arthur Drobot
efe76a6fdc Add arccos. 2017-08-09 15:08:54 -07:00
Arthur Drobot
ca6d8d2ba2 Add arcsin. 2017-08-09 12:47:43 -07:00
Arthur Drobot
1d6957c4d9 Write a RationalNumber class. 2017-08-08 14:53:34 -07:00
79 changed files with 1154 additions and 764 deletions

View File

@@ -1,21 +1,13 @@
plugins {
id 'java'
id 'application'
id 'org.jetbrains.kotlin.jvm' version '1.1.3'
}
apply plugin: 'java'
apply plugin: 'application'
subprojects {
repositories {
mavenCentral()
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.1.3"
}
}
dependencies {
compile 'com.moandjiezana.toml:toml4j:0.7.1'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8"
testCompile 'junit:junit:4.12'
}
// Define the main class for the application
mainClassName = 'org.nwapw.abacus.Abacus'

8
core/build.gradle Normal file
View File

@@ -0,0 +1,8 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.1.3'
}
dependencies {
compile 'com.moandjiezana.toml:toml4j:0.7.1'
testCompile 'junit:junit:4.12'
}

View File

@@ -1,7 +1,6 @@
package org.nwapw.abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.fx.AbacusApplication;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.parsing.LexerTokenizer;
import org.nwapw.abacus.parsing.ShuntingYardParser;
@@ -54,17 +53,13 @@ public class Abacus {
numberReducer = new NumberReducer(this);
this.configuration = new Configuration(configuration);
LexerTokenizer lexerTokenizer = new LexerTokenizer();
ShuntingYardParser shuntingYardParser = new ShuntingYardParser(this);
ShuntingYardParser shuntingYardParser = new ShuntingYardParser();
treeBuilder = new TreeBuilder<>(lexerTokenizer, shuntingYardParser);
pluginManager.addListener(shuntingYardParser);
pluginManager.addListener(lexerTokenizer);
}
public static void main(String[] args) {
AbacusApplication.launch(AbacusApplication.class, args);
}
/**
* Gets the current tree builder.
*

View File

@@ -48,7 +48,7 @@ public class Configuration {
*
* @param copyFrom the configuration to copy.
*/
public Configuration(Configuration copyFrom){
public Configuration(Configuration copyFrom) {
copyFrom(copyFrom);
}
@@ -106,7 +106,7 @@ public class Configuration {
*
* @return the string that represents this configuration.
*/
public String asTomlString(){
public String asTomlString() {
return TOML_WRITER.write(this);
}

View File

@@ -0,0 +1,38 @@
package org.nwapw.abacus.function;
/**
* 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 <T> the type of the parameters passed to this applicable.
* @param <O> the return type of the applicable.
*/
public abstract class Applicable<T, O> {
/**
* 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.
*/
protected abstract boolean matchesParams(T[] params);
/**
* 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.
*/
protected abstract O applyInternal(T[] params);
/**
* 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.
*/
public O apply(T... params){
if(!matchesParams(params)) return null;
return applyInternal(params);
}
}

View File

@@ -0,0 +1,11 @@
package org.nwapw.abacus.function;
/**
* Enum that holds the type of documentation that has been
* registered with Abacus.
*/
public enum DocumentationType {
FUNCTION, TREE_VALUE_FUNCTION
}

View File

@@ -0,0 +1,11 @@
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 extends Applicable<NumberInterface, NumberInterface> {
}

View File

@@ -0,0 +1,43 @@
package org.nwapw.abacus.function;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.tree.Reducer;
import org.nwapw.abacus.tree.TreeNode;
/**
* A function that operates on parse tree nodes instead of on already simplified numbers.
* Despite this, it returns a number, not a tree.
*/
public abstract class TreeValueFunction extends Applicable<TreeNode, NumberInterface> {
@Override
protected NumberInterface applyInternal(TreeNode[] params) {
return null;
}
@Override
public NumberInterface apply(TreeNode... params) {
return null;
}
/**
* Applies the tree value functions to the given tree nodes,
* using the given reducer.
* @param reducer the reducer to use.
* @param params the parameters to apply to.
* @return the result of the application.
*/
public abstract NumberInterface applyWithReducerInternal(Reducer<NumberInterface> reducer, TreeNode[] params);
/**
* Checks if the given parameters and reducer can be used
* with this function, and if so, calls the function on them.
* @param reducer the reducer to use.
* @param params the parameters to apply to.
* @return the result of the application, or null if the parameters are incompatible.
*/
public NumberInterface applyWithReducer(Reducer<NumberInterface> reducer, TreeNode... params) {
if(!matchesParams(params) || reducer == null) return null;
return applyWithReducerInternal(reducer, params);
}
}

View File

@@ -9,7 +9,7 @@ public class ComputationInterruptedException extends RuntimeException {
/**
* Creates a new exception of this type.
*/
public ComputationInterruptedException(){
public ComputationInterruptedException() {
super("Computation interrupted by user.");
}

View File

@@ -129,7 +129,7 @@ public class NaiveNumber extends NumberInterface {
}
@Override
public NumberInterface getMaxError(){
public NumberInterface getMaxError() {
return new NaiveNumber(Math.pow(10, -18));
}
}

View File

@@ -9,10 +9,11 @@ public abstract class NumberInterface {
* Check if the thread was interrupted and
* throw an exception to end the computation.
*/
private static void checkInterrupted(){
if(Thread.currentThread().isInterrupted())
private static void checkInterrupted() {
if (Thread.currentThread().isInterrupted())
throw new ComputationInterruptedException();
}
/**
* The maximum precision to which this number operates.
*
@@ -38,7 +39,7 @@ public abstract class NumberInterface {
* @param multiplier the multiplier
* @return the result of the multiplication.
*/
public final NumberInterface multiply(NumberInterface multiplier){
public final NumberInterface multiply(NumberInterface multiplier) {
checkInterrupted();
return multiplyInternal(multiplier);
}
@@ -61,7 +62,7 @@ public abstract class NumberInterface {
* @param divisor the divisor
* @return the result of the division.
*/
public final NumberInterface divide(NumberInterface divisor){
public final NumberInterface divide(NumberInterface divisor) {
checkInterrupted();
return divideInternal(divisor);
}
@@ -84,7 +85,7 @@ public abstract class NumberInterface {
* @param summand the summand
* @return the result of the summation.
*/
public final NumberInterface add(NumberInterface summand){
public final NumberInterface add(NumberInterface summand) {
checkInterrupted();
return addInternal(summand);
}
@@ -107,7 +108,7 @@ public abstract class NumberInterface {
* @param subtrahend the subtrahend.
* @return the result of the subtraction.
*/
public final NumberInterface subtract(NumberInterface subtrahend){
public final NumberInterface subtract(NumberInterface subtrahend) {
checkInterrupted();
return subtractInternal(subtrahend);
}
@@ -129,7 +130,7 @@ public abstract class NumberInterface {
*
* @return the new instance.
*/
public final NumberInterface negate(){
public final NumberInterface negate() {
checkInterrupted();
return negateInternal();
}
@@ -150,7 +151,7 @@ public abstract class NumberInterface {
* @param exponent the exponent to which to take the number.
* @return the resulting value.
*/
public final NumberInterface intPow(int exponent){
public final NumberInterface intPow(int exponent) {
checkInterrupted();
return intPowInternal(exponent);
}
@@ -184,7 +185,7 @@ public abstract class NumberInterface {
*
* @return the least integer bigger or equal to the number.
*/
public final NumberInterface ceiling(){
public final NumberInterface ceiling() {
checkInterrupted();
return ceilingInternal();
}
@@ -203,7 +204,7 @@ public abstract class NumberInterface {
*
* @return the greatest int smaller than or equal to the number.
*/
public final NumberInterface floor(){
public final NumberInterface floor() {
checkInterrupted();
return floorInternal();
}
@@ -219,9 +220,10 @@ public abstract class NumberInterface {
* Returns the fractional part of the number, specifically x - floor(x).
* Also, checks if the thread has been interrupted,
* and if so, throws an exception.
*
* @return the fractional part of the number.
*/
public final NumberInterface fractionalPart(){
public final NumberInterface fractionalPart() {
checkInterrupted();
return fractionalPartInternal();
}
@@ -260,6 +262,7 @@ public abstract class NumberInterface {
/**
* Returns the smallest error this instance can tolerate depending
* on its precision and value.
*
* @return the smallest error that should be permitted in calculations.
*/
public abstract NumberInterface getMaxError();

View File

@@ -35,7 +35,7 @@ public class PreciseNumber extends NumberInterface {
/**
* MathContext that is actually used in calculations.
*/
private static MathContext internalContext = new MathContext(outputContext.getPrecision()+numExtraInternalSigFigs);
private static MathContext internalContext = new MathContext(outputContext.getPrecision() + numExtraInternalSigFigs);
/**
* The value of the PreciseNumber.
@@ -129,7 +129,7 @@ public class PreciseNumber extends NumberInterface {
int decimalIndex = str.indexOf('.');
if (decimalIndex != -1) {
NumberInterface floor = new PreciseNumber(str.substring(0, decimalIndex));
if(signum() == -1){
if (signum() == -1) {
floor = floor.subtract(ONE);
}
return floor;
@@ -166,7 +166,7 @@ public class PreciseNumber extends NumberInterface {
}
@Override
public NumberInterface getMaxError(){
return new PreciseNumber(value.ulp()).multiplyInternal(TEN.intPowInternal(value.precision()-internalContext.getPrecision()));
public NumberInterface getMaxError() {
return new PreciseNumber(value.ulp()).multiplyInternal(TEN.intPowInternal(value.precision() - internalContext.getPrecision()));
}
}

View File

@@ -34,6 +34,7 @@ public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListen
register(" ", TokenType.WHITESPACE);
register(",", TokenType.COMMA);
register("[0-9]*(\\.[0-9]+)?", TokenType.NUM);
register("[a-zA-Z]+", TokenType.VARIABLE);
register("\\(", TokenType.OPEN_PARENTH);
register("\\)", TokenType.CLOSE_PARENTH);
}};
@@ -52,6 +53,9 @@ public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListen
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
@@ -62,6 +66,9 @@ public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListen
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);
}
}
}

View File

@@ -1,6 +1,5 @@
package org.nwapw.abacus.parsing;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.function.OperatorAssociativity;
import org.nwapw.abacus.function.OperatorType;
@@ -17,10 +16,6 @@ import java.util.*;
*/
public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListener {
/**
* The Abacus instance used to create number instances.
*/
private Abacus abacus;
/**
* Map of operator precedences, loaded from the plugin operators.
*/
@@ -35,12 +30,9 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
private Map<String, OperatorType> typeMap;
/**
* Creates a new Shunting Yard parser with the given Abacus instance.
*
* @param abacus the abacus instance.
* Creates a new Shunting Yard parser.
*/
public ShuntingYardParser(Abacus abacus) {
this.abacus = abacus;
public ShuntingYardParser() {
precedenceMap = new HashMap<>();
associativityMap = new HashMap<>();
typeMap = new HashMap<>();
@@ -61,9 +53,9 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
Match<TokenType> match = from.remove(0);
previousType = matchType;
matchType = match.getType();
if (matchType == TokenType.NUM) {
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) {
@@ -86,7 +78,8 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
while (!tokenStack.empty() && type == OperatorType.BINARY_INFIX) {
Match<TokenType> otherMatch = tokenStack.peek();
TokenType otherMatchType = otherMatch.getType();
if (!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break;
if (!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION ||
otherMatchType == TokenType.TREE_VALUE_FUNCTION)) break;
if (otherMatchType == TokenType.OP) {
int otherPrecedence = precedenceMap.get(otherMatch.getContent());
@@ -113,7 +106,8 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
while (!tokenStack.empty()) {
Match<TokenType> match = tokenStack.peek();
TokenType newMatchType = match.getType();
if (!(newMatchType == TokenType.OP || newMatchType == TokenType.FUNCTION)) return null;
if (!(newMatchType == TokenType.OP || newMatchType == TokenType.FUNCTION ||
newMatchType == TokenType.TREE_VALUE_FUNCTION)) return null;
output.add(tokenStack.pop());
}
return output;
@@ -143,14 +137,21 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
else return new UnaryNode(operator, applyTo);
}
} else if (matchType == TokenType.NUM) {
return new NumberNode(abacus.numberFromString(match.getContent()));
} else if (matchType == TokenType.FUNCTION) {
return new NumberNode(match.getContent());
} else if (matchType == TokenType.VARIABLE) {
return new VariableNode(match.getContent());
} 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);
@@ -162,7 +163,7 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
@Override
public TreeNode constructTree(List<Match<TokenType>> tokens) {
tokens = intoPostfix(new ArrayList<>(tokens));
if(tokens == null) return null;
if (tokens == null) return null;
Collections.reverse(tokens);
TreeNode constructedTree = constructRecursive(tokens);
return tokens.size() == 0 ? constructedTree : null;

View File

@@ -1,15 +1,8 @@
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;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* A plugin class that can be externally implemented and loaded via the
* plugin manager. Plugins provide functionality to the calculator
@@ -73,6 +66,17 @@ public abstract class Plugin {
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
@@ -99,9 +103,10 @@ public abstract class Plugin {
/**
* To be used in load(). Registers a documentation instance
* used to explain some element of the plugin to the user.
*
* @param documentation the documentation instance.
*/
protected final void registerDocumentation(Documentation documentation){
protected final void registerDocumentation(Documentation documentation) {
manager.registerDocumentation(documentation);
}
@@ -117,6 +122,18 @@ public abstract class Plugin {
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
@@ -148,7 +165,7 @@ public abstract class Plugin {
* @param type the type of documentation to search for.
* @return the found documentation, or null if none was found.
*/
protected final Documentation documentationFor(String name, DocumentationType type){
protected final Documentation documentationFor(String name, DocumentationType type) {
return manager.documentationFor(name, type);
}

View File

@@ -1,14 +1,14 @@
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;
import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A class that controls instances of plugins, allowing for them
@@ -28,6 +28,10 @@ public class PluginManager {
* The map of functions registered by the plugins.
*/
private Map<String, Function> registeredFunctions;
/**
* The map of tree value functions regstered by the plugins.
*/
private Map<String, TreeValueFunction> registeredTreeValueFunctions;
/**
* The map of operators registered by the plugins
*/
@@ -69,6 +73,7 @@ public class PluginManager {
loadedPluginClasses = new HashSet<>();
plugins = new HashSet<>();
registeredFunctions = new HashMap<>();
registeredTreeValueFunctions = new HashMap<>();
registeredOperators = new HashMap<>();
registeredNumberImplementations = new HashMap<>();
registeredDocumentation = new HashSet<>();
@@ -79,82 +84,110 @@ public class PluginManager {
/**
* Registers a function under the given name.
* @param name the name of the function.
*
* @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, Function 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 name the name of the operator.
* @param operator the operator to register.
*/
public void registerOperator(String name, Operator operator){
public void registerOperator(String name, Operator operator) {
registeredOperators.put(name, operator);
}
/**
* Registers a number implementation under the given name.
* @param name the name of the number implementation.
*
* @param name the name of the number implementation.
* @param implementation the number implementation to register.
*/
public void registerNumberImplementation(String name, NumberImplementation implementation){
public void registerNumberImplementation(String name, NumberImplementation implementation) {
registeredNumberImplementations.put(name, implementation);
}
/**
* Registers the given documentation with the plugin manager,
* making it accessible to the plugin manager etc.
*
* @param documentation the documentation to register.
*/
public void registerDocumentation(Documentation documentation){
public void registerDocumentation(Documentation documentation) {
registeredDocumentation.add(documentation);
}
/**
* Gets the function registered under the given name.
*
* @param name the name of the function.
* @return the function, or null if it was not found.
*/
public Function functionFor(String name){
public Function 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 Operator operatorFor(String name) {
return registeredOperators.get(name);
}
/**
* Gets the number implementation registered under the given name.
*
* @param name the name of the number implementation.
* @return the number implementation, or null if it was not found.
*/
public NumberImplementation numberImplementationFor(String name){
public NumberImplementation numberImplementationFor(String name) {
return registeredNumberImplementations.get(name);
}
/**
* Gets the documentation for the given entity of the given type.
*
* @param name the name of the entity to search for.
* @param type the type that this entity is, to filter out similarly named documentation.
* @return the documentation object.
*/
public Documentation documentationFor(String name, DocumentationType type){
public Documentation documentationFor(String name, DocumentationType type) {
Documentation toReturn = null;
for(Documentation entry : registeredDocumentation){
if(entry.getCodeName().equals(name) && entry.getType() == type) {
for (Documentation entry : registeredDocumentation) {
if (entry.getCodeName().equals(name) && entry.getType() == type) {
toReturn = entry;
break;
}
}
if(toReturn == null){
if (toReturn == null) {
toReturn = new Documentation(name, "", "", "", type);
registerDocumentation(toReturn);
}
@@ -170,9 +203,9 @@ public class PluginManager {
public NumberImplementation interfaceImplementationFor(Class<? extends NumberInterface> name) {
if (cachedInterfaceImplementations.containsKey(name)) return cachedInterfaceImplementations.get(name);
NumberImplementation toReturn = null;
for(String key : registeredNumberImplementations.keySet()){
for (String key : registeredNumberImplementations.keySet()) {
NumberImplementation implementation = registeredNumberImplementations.get(key);
if(implementation.getImplementation() == name) {
if (implementation.getImplementation() == name) {
toReturn = implementation;
break;
}
@@ -226,10 +259,11 @@ public class PluginManager {
/**
* Removes the plugin with the given class from the manager.
*
* @param toRemove the plugin to remove.
*/
public void removeClass(Class<? extends Plugin> toRemove){
if(!loadedPluginClasses.contains(toRemove)) return;
public void removeClass(Class<? extends Plugin> toRemove) {
if (!loadedPluginClasses.contains(toRemove)) return;
plugins.removeIf(plugin -> plugin.getClass() == toRemove);
loadedPluginClasses.remove(toRemove);
}
@@ -237,7 +271,7 @@ public class PluginManager {
/**
* Removes all plugins from this plugin manager.
*/
public void removeAll(){
public void removeAll() {
loadedPluginClasses.clear();
plugins.clear();
}
@@ -290,6 +324,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<String> getAllTreeValueFunctions() {
return registeredTreeValueFunctions.keySet();
}
/**
* Gets all the operators loaded by the Plugin Manager.
*

View File

@@ -1,19 +1,12 @@
package org.nwapw.abacus.plugin;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.lexing.pattern.Match;
import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber;
import org.nwapw.abacus.parsing.Parser;
import org.nwapw.abacus.parsing.ShuntingYardParser;
import org.nwapw.abacus.tree.TokenType;
import org.nwapw.abacus.tree.TreeNode;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.function.BiFunction;
/**
@@ -22,11 +15,6 @@ import java.util.function.BiFunction;
*/
public class StandardPlugin extends Plugin {
/**
* Stores objects of NumberInterface with integer values for reuse.
*/
private final static HashMap<Class<? extends NumberInterface>, HashMap<Integer, NumberInterface>> integerValues = new HashMap<>();
/**
* The addition operator, +
*/
@@ -92,6 +80,75 @@ public class StandardPlugin extends Plugin {
return product;
}
});
/**
* 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.
*/
public static final NumberImplementation IMPLEMENTATION_NAIVE = new NumberImplementation(NaiveNumber.class, 0) {
@Override
public NumberInterface instanceForString(String string) {
return new NaiveNumber(string);
}
@Override
public NumberInterface instanceForPi() {
return new NaiveNumber(Math.PI);
}
};
/**
* The implementation for the infinite-precision BigDecimal.
*/
public static final NumberImplementation IMPLEMENTATION_PRECISE = new NumberImplementation(PreciseNumber.class, 0) {
@Override
public NumberInterface instanceForString(String string) {
return new PreciseNumber(string);
}
@Override
public NumberInterface instanceForPi() {
NumberInterface C = FUNCTION_SQRT.apply(new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
NumberInterface M = PreciseNumber.ONE;
NumberInterface L = new PreciseNumber("13591409");
NumberInterface X = M;
NumberInterface sum = L;
int termsNeeded = C.getMaxPrecision() / 13 + 1;
NumberInterface lSummand = new PreciseNumber("545140134");
NumberInterface xMultiplier = new PreciseNumber("262537412")
.multiply(new PreciseNumber("1000000000"))
.add(new PreciseNumber("640768000"))
.negate();
for (int i = 0; i < termsNeeded; i++) {
M = M
.multiply(new PreciseNumber((12 * i + 2) + ""))
.multiply(new PreciseNumber((12 * i + 6) + ""))
.multiply(new PreciseNumber((12 * i + 10) + ""))
.divide(new PreciseNumber(Math.pow(i + 1, 3) + ""));
L = L.add(lSummand);
X = X.multiply(xMultiplier);
sum = sum.add(M.multiply(L).divide(X));
}
return C.divide(sum);
}
};
/**
* Stores objects of NumberInterface with integer values for reuse.
*/
private final static HashMap<Class<? extends NumberInterface>, HashMap<Integer, NumberInterface>> integerValues = new HashMap<>();
/**
* The division operator, /
*/
@@ -150,17 +207,17 @@ public class StandardPlugin extends Plugin {
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
if(params[0].compareTo(params[1]) < 0 ||
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);
NumberInterface total = fromInt(params[0].getClass(), 1);
NumberInterface multiplyBy = params[0];
NumberInterface remainingMultiplications = params[1];
NumberInterface halfway = params[0].divide(fromInt(params[0].getClass(), 2));
if(remainingMultiplications.compareTo(halfway) > 0){
if (remainingMultiplications.compareTo(halfway) > 0) {
remainingMultiplications = params[0].subtract(remainingMultiplications);
}
while(remainingMultiplications.signum() > 0){
while (remainingMultiplications.signum() > 0) {
total = total.multiply(multiplyBy);
remainingMultiplications = remainingMultiplications.subtract(fromInt(params[0].getClass(), 1));
multiplyBy = multiplyBy.subtract(fromInt(params[0].getClass(), 1));
@@ -168,21 +225,6 @@ public class StandardPlugin extends Plugin {
return total;
}
});
/**
* 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 absolute value function, abs(-3) = 3
*/
@@ -277,20 +319,6 @@ public class StandardPlugin extends Plugin {
return sum;
}
};
/**
* 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())));
}
};
/**
* Gets a random number smaller or equal to the given number's integer value.
*/
@@ -305,94 +333,6 @@ public class StandardPlugin extends Plugin {
return fromInt(params[0].getClass(), (int) Math.round(Math.random() * params[0].floor().intValue()));
}
};
/**
* The implementation for double-based naive numbers.
*/
public static final NumberImplementation IMPLEMENTATION_NAIVE = new NumberImplementation(NaiveNumber.class, 0) {
@Override
public NumberInterface instanceForString(String string) {
return new NaiveNumber(string);
}
@Override
public NumberInterface instanceForPi() {
return new NaiveNumber(Math.PI);
}
};
/**
* The implementation for the infinite-precision BigDecimal.
*/
public static final NumberImplementation IMPLEMENTATION_PRECISE = new NumberImplementation(PreciseNumber.class, 0) {
@Override
public NumberInterface instanceForString(String string) {
return new PreciseNumber(string);
}
@Override
public NumberInterface instanceForPi() {
NumberInterface C = FUNCTION_SQRT.apply(new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
NumberInterface M = PreciseNumber.ONE;
NumberInterface L = new PreciseNumber("13591409");
NumberInterface X = M;
NumberInterface sum = L;
int termsNeeded = C.getMaxPrecision() / 13 + 1;
NumberInterface lSummand = new PreciseNumber("545140134");
NumberInterface xMultiplier = new PreciseNumber("262537412")
.multiply(new PreciseNumber("1000000000"))
.add(new PreciseNumber("640768000"))
.negate();
for (int i = 0; i < termsNeeded; i++) {
M = M
.multiply(new PreciseNumber((12 * i + 2) + ""))
.multiply(new PreciseNumber((12 * i + 6) + ""))
.multiply(new PreciseNumber((12 * i + 10) + ""))
.divide(new PreciseNumber(Math.pow(i + 1, 3) + ""));
L = L.add(lSummand);
X = X.multiply(xMultiplier);
sum = sum.add(M.multiply(L).divide(X));
}
return C.divide(sum);
}
};
private static final HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
/**
* The exponential function, exp(1) = e^1 = 2.71...
*/
public static final Function FUNCTION_EXP = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface maxError = params[0].getMaxError();
int n = 0;
if (params[0].signum() < 0) {
NumberInterface[] negatedParams = {params[0].negate()};
return fromInt(params[0].getClass(), 1).divide(applyInternal(negatedParams));
} else {
//We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!.
//right and left refer to lhs and rhs in the above inequality.
NumberInterface sum = fromInt(params[0].getClass(), 1);
NumberInterface nextNumerator = params[0];
NumberInterface left = params[0].multiply(fromInt(params[0].getClass(), 3).intPow(params[0].ceiling().intValue())), right = maxError;
do {
sum = sum.add(nextNumerator.divide(factorial(params[0].getClass(), n + 1)));
n++;
nextNumerator = nextNumerator.multiply(params[0]);
left = left.multiply(params[0]);
NumberInterface nextN = fromInt(params[0].getClass(), n + 1);
right = right.multiply(nextN);
//System.out.println(left + ", " + right);
}
while (left.compareTo(right) > 0);
//System.out.println(n+1);
return sum;
}
}
};
/**
* The caret / pow operator, ^
*/
@@ -414,15 +354,67 @@ public class StandardPlugin extends Plugin {
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
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){
&& 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<Class<? extends NumberInterface>, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
/**
* The exponential function, exp(1) = e^1 = 2.71...
*/
public static final Function FUNCTION_EXP = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface maxError = params[0].getMaxError();
int n = 0;
if (params[0].signum() < 0) {
NumberInterface[] negatedParams = {params[0].negate()};
return fromInt(params[0].getClass(), 1).divide(applyInternal(negatedParams));
} else {
//We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!.
//right and left refer to lhs and rhs in the above inequality.
NumberInterface sum = fromInt(params[0].getClass(), 1);
NumberInterface nextNumerator = params[0];
NumberInterface left = params[0].multiply(fromInt(params[0].getClass(), 3).intPow(params[0].ceiling().intValue())), right = maxError;
do {
sum = sum.add(nextNumerator.divide(factorial(params[0].getClass(), n + 1)));
n++;
nextNumerator = nextNumerator.multiply(params[0]);
left = left.multiply(params[0]);
NumberInterface nextN = fromInt(params[0].getClass(), n + 1);
right = right.multiply(nextN);
//System.out.println(left + ", " + right);
}
while (left.compareTo(right) > 0);
//System.out.println(n+1);
return sum;
}
}
};
/**
* The sine function (the argument is interpreted in radians).
*/
@@ -519,6 +511,144 @@ public class StandardPlugin extends Plugin {
}
};
/**
* The arcsine function (return type in radians).
*/
public final Function functionArcsin = new Function() {
@Override
protected 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) {
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))
.subtract(applyInternal(newParams)).multiply(fromInt(params[0].getClass(), params[0].signum()));
}
NumberInterface currentTerm = params[0], sum = currentTerm,
multiplier = currentTerm.multiply(currentTerm), summandBound = sum.getMaxError().multiply(fromInt(sum.getClass(), 1).subtract(multiplier)),
power = currentTerm, coefficient = fromInt(params[0].getClass(), 1);
int exponent = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(summandBound) > 0) {
exponent += 2;
power = power.multiply(multiplier);
coefficient = coefficient.multiply(fromInt(params[0].getClass(), exponent - 2))
.divide(fromInt(params[0].getClass(), exponent - 1));
currentTerm = power.multiply(coefficient).divide(fromInt(power.getClass(), exponent));
sum = sum.add(currentTerm);
}
return sum;
}
};
/**
* The arccosine function.
*/
public final Function functionArccos = new Function() {
@Override
protected 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) {
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(functionArcsin.apply(params));
}
};
/**
* The arccosecant function.
*/
public final Function functionArccsc = new Function() {
@Override
protected 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) {
NumberInterface[] reciprocalParamArr = {fromInt(params[0].getClass(), 1).divide(params[0])};
return functionArcsin.apply(reciprocalParamArr);
}
};
/**
* The arcsecant function.
*/
public final Function functionArcsec = new Function() {
@Override
protected 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) {
NumberInterface[] reciprocalParamArr = {fromInt(params[0].getClass(), 1).divide(params[0])};
return functionArccos.apply(reciprocalParamArr);
}
};
/**
* The arctangent function.
*/
public final Function functionArctan = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
if (params[0].signum() == -1) {
NumberInterface[] negatedParams = {params[0].negate()};
return applyInternal(negatedParams).negate();
}
if (params[0].compareTo(fromInt(params[0].getClass(), 1)) > 0) {
NumberInterface[] reciprocalParams = {fromInt(params[0].getClass(), 1).divide(params[0])};
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(applyInternal(reciprocalParams));
}
if (params[0].compareTo(fromInt(params[0].getClass(), 1)) == 0) {
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 4));
}
if (params[0].compareTo(new NaiveNumber(0.9).promoteTo(params[0].getClass())) >= 0) {
NumberInterface[] newParams = {params[0].multiply(fromInt(params[0].getClass(), 2))
.divide(fromInt(params[0].getClass(), 1).subtract(params[0].multiply(params[0])))};
return applyInternal(newParams).divide(fromInt(params[0].getClass(), 2));
}
NumberInterface currentPower = params[0], currentTerm = currentPower, sum = currentTerm,
maxError = params[0].getMaxError(), multiplier = currentPower.multiply(currentPower).negate();
int n = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) {
n += 2;
currentPower = currentPower.multiply(multiplier);
currentTerm = currentPower.divide(fromInt(currentPower.getClass(), n));
sum = sum.add(currentTerm);
}
return sum;
}
};
/**
* The arccotangent function. Range: (0, pi).
*/
public final Function functionArccot = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(functionArctan.apply(params));
}
};
public StandardPlugin(PluginManager manager) {
super(manager);
}
@@ -599,15 +729,16 @@ public class StandardPlugin extends Plugin {
/**
* Returns a number of class numType with value n.
*
* @param numType class of number to return.
* @param n value of returned number.
* @param n value of returned number.
* @return numClass instance with value n.
*/
private static NumberInterface fromInt(Class<? extends NumberInterface> numType, int n){
if(!integerValues.containsKey(numType)){
private static NumberInterface fromInt(Class<? extends NumberInterface> numType, int n) {
if (!integerValues.containsKey(numType)) {
integerValues.put(numType, new HashMap<>());
}
if(!integerValues.get(numType).containsKey(n)){
if (!integerValues.get(numType).containsKey(n)) {
integerValues.get(numType).put(n, new NaiveNumber(n).promoteTo(numType));
}
return integerValues.get(numType).get(n);
@@ -633,6 +764,7 @@ public class StandardPlugin extends Plugin {
registerFunction("exp", FUNCTION_EXP);
registerFunction("ln", FUNCTION_LN);
registerFunction("sqrt", FUNCTION_SQRT);
registerFunction("sin", functionSin);
registerFunction("cos", functionCos);
registerFunction("tan", functionTan);
@@ -640,6 +772,13 @@ public class StandardPlugin extends Plugin {
registerFunction("csc", functionCsc);
registerFunction("cot", functionCot);
registerFunction("arcsin", functionArcsin);
registerFunction("arccos", functionArccos);
registerFunction("arctan", functionArctan);
registerFunction("arcsec", functionArcsec);
registerFunction("arccsc", functionArccsc);
registerFunction("arccot", functionArccot);
registerFunction("random_int", FUNCTION_RAND_INT);
registerDocumentation(new Documentation("abs", "Absolute Value", "Finds the distance " +
@@ -655,27 +794,40 @@ public class StandardPlugin extends Plugin {
"the power that e has to be brought to to be equal to the number.\n\n" +
"Example: ln(2.718) -> 1", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("sqrt", "Square Root", "Finds the square root " +
"of the number.", "A square root a of a number is defined such that a times a is equal " +
"of the number.", "A square root a of a number is defined as the non-negative a such that a times a is equal " +
"to that number.\n\n" +
"Example: sqrt(4) -> 2", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("sin", "Sine", "Computes the sine of the given angle, " +
"in radians.", "", DocumentationType.FUNCTION));
"in radians.", "Example: sin(pi/6) -> 0.5", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("cos", "Cosine", "Computes the cosine of the given angle, " +
"in radians.", "", DocumentationType.FUNCTION));
"in radians.", "Example: cos(pi/6) -> 0.866... (the exact result is sqrt(3)/2)", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("tan", "Tangent", "Computes the tangent of the given angle, " +
"in radians.", "", DocumentationType.FUNCTION));
"in radians.", "Example: tan(pi/6) -> 0.577... (the exact result is 1/sqrt(3))", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("sec", "Secant", "Computes the secant of the given angle, " +
"in radians.", "", DocumentationType.FUNCTION));
"in radians.", "Example: sec(pi/6) -> 1.154... (the exact result is 2/sqrt(3))", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("csc", "Cosecant", "Computes the cosecant of the given angle, " +
"in radians.", "", DocumentationType.FUNCTION));
"in radians.", "Example: csc(pi/6) -> 2", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("cot", "Cotangent", "Computes the cotangent of the given angle, " +
"in radians.", "", DocumentationType.FUNCTION));
"in radians.", "Example: cot(pi/6) -> 1.732... (the exact result is sqrt(3))", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("random_int", "Random Integer", "Generates a random integer [0, n].",
"Generates a pseudorandom number using the standard JVM random mechanism, keeping it less than or " +
"equal to the given number.\n\n" +
"Example: random_int(5) -> 4\n" +
"random_int(5) -> 3\n" +
"random_int(5) -> 3\n", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("arcsin", "Arcsine", "Computes the arcsine of x. (The result is in radians.)",
"Example: arcsin(0.5) -> 0.523... (the exact result is pi/6)", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("arccos", "Arccosine", "Computes the arccosine of x. (The result is in radians.)",
"Example: arccos(0.5) -> 1.047... (the exact result is pi/3)", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("arctan", "Arctangent", "Computes the arctangent of x. (The result is in radians.)",
"Example: arctan(1) -> 0.785... (the exact result is pi/4)", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("arcsec", "Arcsecant", "Computes the arcsecant of x. (The result is in radians.)",
"Example: arcsec(2) -> 1.047... (the exact result is pi/3)", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("arccsc", "Arccosecant", "Computes the arcscosecant of x. (The result is in radians.)",
"Example: arccsc(2) -> 0.523... (the exact result is pi/6)", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("arccot", "Arccotangent", "Computes the arccotangent of x. (The result is in radians," +
" in the range (0, pi).)",
"Example: arccot(0) -> 1.570... (the exact result is pi/2)", DocumentationType.FUNCTION));
}
@Override

View File

@@ -2,6 +2,7 @@ package org.nwapw.abacus.tree;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.function.TreeValueFunction;
import org.nwapw.abacus.number.NumberInterface;
/**
@@ -27,7 +28,9 @@ public class NumberReducer implements Reducer<NumberInterface> {
@Override
public NumberInterface reduceNode(TreeNode node, Object... children) {
if (node instanceof NumberNode) {
return ((NumberNode) node).getNumber();
return abacus.numberFromString(((NumberNode) node).getNumber());
} else if(node instanceof VariableNode) {
return abacus.numberFromString("0");
} else if (node instanceof BinaryNode) {
NumberInterface left = (NumberInterface) children[0];
NumberInterface right = (NumberInterface) children[1];
@@ -44,9 +47,19 @@ public class NumberReducer implements Reducer<NumberInterface> {
for (int i = 0; i < convertedChildren.length; i++) {
convertedChildren[i] = (NumberInterface) children[i];
}
Function function = abacus.getPluginManager().functionFor(((FunctionNode) node).getFunction());
Function 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);
}
return null;
}

View File

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

View File

@@ -11,7 +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() {
class BinaryNode(val operation: String, val left: TreeNode? = null, val right: TreeNode?) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
val leftReduce = left?.reduce(reducer) ?: return null

View File

@@ -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<TreeNode> = 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()
}
}

View File

@@ -0,0 +1,18 @@
package org.nwapw.abacus.tree
/**
* A tree node that holds a function call.
*
* The function call node can hold any number of children, and passes the to the appropriate reducer,
* but that is its sole purpose.
*
* @param function the function string.
*/
class FunctionNode(function: String) : CallNode(function) {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
val children = Array<Any>(children.size, { children[it].reduce(reducer) ?: return null; })
return reducer.reduceNode(this, *children)
}
}

View File

@@ -10,14 +10,14 @@ import org.nwapw.abacus.number.NumberInterface
*
* @number the number value of this node.
*/
data class NumberNode(val number: NumberInterface) : TreeNode() {
class NumberNode(val number: String) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
return reducer.reduceNode(this)
}
override fun toString(): String {
return number.toString()
return number
}
}

View File

@@ -6,7 +6,7 @@ package org.nwapw.abacus.tree
* The reducer walks the tree, visiting the children first, converting them into
* a value, and then attempts to reduce the parent. Eventually, the single final value is returned.
*/
interface Reducer <out T> {
interface Reducer<out T> {
/**
* Reduces the given tree node, given its already reduced children.
@@ -14,6 +14,6 @@ interface Reducer <out T> {
* @param treeNode the tree node to reduce.
* @param children the list of children, of type T.
*/
fun reduceNode(treeNode: TreeNode, vararg children: Any) : T?
fun reduceNode(treeNode: TreeNode, vararg children: Any): T?
}

View File

@@ -5,6 +5,6 @@ package org.nwapw.abacus.tree
*/
abstract class TreeNode {
abstract fun <T: Any> reduce(reducer: Reducer<T>) : T?
abstract fun <T : Any> reduce(reducer: Reducer<T>): T?
}

View File

@@ -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 <T : Any> reduce(reducer: Reducer<T>): T? {
return reducer.reduceNode(this)
}
}

View File

@@ -9,7 +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() {
class UnaryNode(val operation: String, val applyTo: TreeNode? = null) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
val reducedChild = applyTo?.reduce(reducer) ?: return null

View File

@@ -0,0 +1,21 @@
package org.nwapw.abacus.tree
/**
* A tree node that holds a placeholder variable.
*
* This node holds a variable string, and acts similarly to a number,
* with the key difference of not actually holding a value at runtime.
*
* @param variable the actual variable name that this node represents.
*/
class VariableNode(val variable: String) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
return reducer.reduceNode(this)
}
override fun toString(): String {
return variable
}
}

View File

@@ -14,12 +14,12 @@ public class CalculationTests {
private static Abacus abacus = new Abacus(new Configuration(0, "precise", new String[]{}));
@BeforeClass
public static void prepareTests(){
public static void prepareTests() {
abacus.getPluginManager().addInstantiated(new StandardPlugin(abacus.getPluginManager()));
abacus.getPluginManager().load();
}
private void testOutput(String input, String parseOutput, String output){
private void testOutput(String input, String parseOutput, String output) {
TreeNode parsedTree = abacus.parseString(input);
Assert.assertNotNull(parsedTree);
Assert.assertEquals(parsedTree.toString(), parseOutput);
@@ -28,7 +28,7 @@ public class CalculationTests {
Assert.assertTrue(result.toString().startsWith(output));
}
private void testEvalError(String input, String parseOutput){
private void testEvalError(String input, String parseOutput) {
TreeNode parsedTree = abacus.parseString(input);
Assert.assertNotNull(parsedTree);
Assert.assertEquals(parsedTree.toString(), parseOutput);
@@ -36,56 +36,56 @@ public class CalculationTests {
}
@Test
public void testAddition(){
public void testAddition() {
testOutput("9.5+10", "(9.5+10)", "19.5");
}
@Test
public void testSubtraction(){
public void testSubtraction() {
testOutput("9.5-10", "(9.5-10)", "-0.5");
}
@Test
public void testMultiplication(){
public void testMultiplication() {
testOutput("9.5*10", "(9.5*10)", "95");
}
@Test
public void testDivision(){
public void testDivision() {
testOutput("9.5/2", "(9.5/2)", "4.75");
}
@Test
public void testNegation(){
public void testNegation() {
testOutput("-9.5", "(9.5)`", "-9.5");
}
@Test
public void testFactorial(){
public void testFactorial() {
testOutput("7!", "(7)!", "5040");
}
@Test
public void testAbs(){
public void testAbs() {
testOutput("abs(-1)", "abs((1)`)", "1");
testOutput("abs(1)", "abs(1)", "1");
}
@Test
public void testLn(){
public void testLn() {
testEvalError("ln(-1)", "ln((1)`)");
testOutput("ln2", "ln(2)", "0.6931471805599453094172321214581765680755");
}
@Test
public void testSqrt(){
public void testSqrt() {
testOutput("sqrt0", "sqrt(0)", "0");
testOutput("sqrt4", "sqrt(4)", "2");
testOutput("sqrt2", "sqrt(2)", "1.4142135623730950488016887242096980785696");
}
@Test
public void testExp(){
public void testExp() {
testOutput("exp0", "exp(0)", "1");
testOutput("exp1", "exp(1)", "2.718281828459045235360287471352662497757247");
testOutput("exp300", "exp(300)", "1.9424263952412559365842088360176992193662086");
@@ -93,7 +93,7 @@ public class CalculationTests {
}
@Test
public void testPow(){
public void testPow() {
testOutput("0^2", "(0^2)", "0");
testOutput("2^0", "(2^0)", "1");
testOutput("2^1", "(2^1)", "2");
@@ -101,7 +101,7 @@ public class CalculationTests {
testOutput("2^50", "(2^50)", "112589990684262");
testOutput("7^(-sqrt2*17)", "(7^((sqrt(2)*17))`)", "4.81354609155297814551845300063563");
testEvalError("0^0", "(0^0)");
testEvalError("(-13)^.9999", "((13)`^0.9999)");
testEvalError("(-13)^.9999", "((13)`^.9999)");
}
}

7
docs/404.html Normal file
View File

@@ -0,0 +1,7 @@
---
layout: base
---
<h1>404</h1>
<p><strong>Page not found :(</strong></p>
<p>The requested page could not be found.</p>

27
docs/Gemfile Normal file
View File

@@ -0,0 +1,27 @@
source "https://rubygems.org"
# Hello! This is where you manage which Jekyll version is used to run.
# When you want to use a different version, change it below, save the
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
#
# bundle exec jekyll serve
#
# This will help ensure the proper Jekyll version is running.
# Happy Jekylling!
gem "jekyll", "3.5.2"
# This is the default theme for new Jekyll sites. You may change this to anything you like.
gem "minima", "~> 2.0"
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
# uncomment the line below. To upgrade, run `bundle update github-pages`.
# gem "github-pages", group: :jekyll_plugins
# If you have any plugins, put them here!
group :jekyll_plugins do
gem "jekyll-feed", "~> 0.6"
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

58
docs/Gemfile.lock Normal file
View File

@@ -0,0 +1,58 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2)
colorator (1.1.0)
ffi (1.9.18)
forwardable-extended (2.6.0)
jekyll (3.5.2)
addressable (~> 2.4)
colorator (~> 1.0)
jekyll-sass-converter (~> 1.0)
jekyll-watch (~> 1.1)
kramdown (~> 1.3)
liquid (~> 4.0)
mercenary (~> 0.3.3)
pathutil (~> 0.9)
rouge (~> 1.7)
safe_yaml (~> 1.0)
jekyll-feed (0.9.2)
jekyll (~> 3.3)
jekyll-sass-converter (1.5.0)
sass (~> 3.4)
jekyll-watch (1.5.0)
listen (~> 3.0, < 3.1)
kramdown (1.14.0)
liquid (4.0.0)
listen (3.0.8)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
mercenary (0.3.6)
minima (2.1.1)
jekyll (~> 3.3)
pathutil (0.14.0)
forwardable-extended (~> 2.6)
public_suffix (2.0.5)
rb-fsevent (0.10.2)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
rouge (1.11.1)
safe_yaml (1.0.4)
sass (3.5.1)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
PLATFORMS
ruby
DEPENDENCIES
jekyll (= 3.5.2)
jekyll-feed (~> 0.6)
minima (~> 2.0)
tzinfo-data
BUNDLED WITH
1.15.3

43
docs/_config.yml Normal file
View File

@@ -0,0 +1,43 @@
# Welcome to Jekyll!
#
# This config file is meant for settings that affect your whole blog, values
# which you are expected to set up once and rarely edit after that. If you find
# yourself editing this file very often, consider using Jekyll's data files
# feature for the data you need to update frequently.
#
# For technical reasons, this file is *NOT* reloaded automatically when you use
# 'bundle exec jekyll serve'. If you change this file, please restart the server process.
# Site settings
# These are used to personalize your new site. If you look in the HTML files,
# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
# You can create any custom variable you would like, and they will be accessible
# in the templates via {{ site.myvariable }}.
title: Abacus
email: danila.fedorin@gmail.com
description: > # this means to ignore newlines until "baseurl:"
This is the home page of Abacus,
a calculator developed during
the summer of 2017 as a tool
for the more tech-savvy users.
baseurl: "/abacus" # the subpath of your site, e.g. /blog
url: "htts://danilafe.github.io" # the base hostname & protocol for your site, e.g. http://example.com
github_username: DanilaFe
include: ['_pages']
# Build settings
markdown: kramdown
plugins:
- jekyll-feed
# Exclude from processing.
# The following items will not be processed, by default. Create a custom list
# to override the default setting.
# exclude:
# - Gemfile
# - Gemfile.lock
# - node_modules
# - vendor/bundle/
# - vendor/cache/
# - vendor/gems/
# - vendor/ruby/

View File

17
docs/_includes/head.html Normal file
View File

@@ -0,0 +1,17 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}</title>
<meta name="description" content="{{ page.excerpt | default: site.description | strip_html | normalize_whitespace | truncate: 160 | escape }}">
<link rel="stylesheet" href="{{ "assets/css/main.css" | relative_url }}">
<link rel="canonical" href="{{ page.url | replace:'index.html','' | absolute_url }}">
<link rel="alternate" type="application/rss+xml" title="{{ site.title | escape }}" href="{{ "/feed.xml" | relative_url }}">
{% if jekyll.environment == 'production' and site.google_analytics %}
{% include google-analytics.html %}
{% endif %}
</head>

View File

@@ -0,0 +1,10 @@
<nav>
<div class="center">
<a href="{{ "/" | relative_url }}" class="primary-link">{{ site.title }}</a>
{% for page in site.pages %}
{% if page.in_header %}
<a href="{{ page.url | relative_url }}">{{ page.title }}</a>
{% endif %}
{% endfor %}
</div>
</nav>

17
docs/_layouts/base.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
{% include head.html %}
<body>
{% include header.html %}
<div class="content center">
{{ content }}
</div>
{% include footer.html %}
</body>
</html>

46
docs/_layouts/home.html Normal file
View File

@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
{% include head.html %}
<style>
body {
margin-top: 100px;
color: white;
text-align: center;
}
h1, h2, h3, h4, h5, h6 {
font-family: "Source Code Pro"
}
img {
margin: auto;
width: 100%;
max-width: 432px;
}
div#buttons {
margin-top: 40px;
margin-bottom: 40px;
}
a {
background-color: white;
color: #06e8a4;
}
a:hover {
background-color: #06e8a4;
color: white;
}
</style>
<body>
<h1>Abacus</h1>
<h2>The programmer's calculator</h2>
<div id="buttons">
<a class="button inverted" href="{{ "/download" | relative_url }}">Download</a>
<a class="button inverted" href="{{ "/about" | relative_url }}">About</a>
<a class="button inverted" href="https://github.com/DanilaFe/abacus">Contribute</a>
<a class="button inverted" href="https://github.com/DanilaFe/abacus/wiki">Wiki</a>
</div>
<img src="http://i.imgur.com/Min70QY.png" title="source: imgur.com" />
</body>
</html>

5
docs/_layouts/page.html Normal file
View File

@@ -0,0 +1,5 @@
---
layout: base
---
<h1>{{ page.title }}</h1>
{{ content }}

27
docs/_pages/about.md Normal file
View File

@@ -0,0 +1,27 @@
---
in_header: true
layout: page
title: About
permalink: /about/
---
## So... what IS Abacus?
It's a calculator. Obviously. But what makes it better than
what already exists? There's a few things. Abacus is:
* Programmable, and not in TI Basic.
* Precise. With the "precise" option, Abacus can keep up to 50 significant figures.
* Capable. Ever wonder what 2<sup>700</sup> is? How about 8!!? Abacus can tell you!
* Offline. While Wolfram Alpha can do powerful math, it needs internet connection!
* Built for the desktop. Why use buttons on the screen when there's buttons on the keyboard?
* Open source. Don't like something? Help is always welcome!
## Why was Abacus made?
The initial project was proposed for the [Northwest Advanced Programming Workshop](http://nwapw.org/about/).
You can read the project proposal on the main GitHub page, although the idea has
changed quite a bit, mostly in shifting from "fast" to "precise".
## What is Abacus made with?
Java and Kotlin. Java provides a good layer of abstraction and a great standard
library, while Kotlin allows for the reduction of boilerplate code and null
safety. Using JVM-based languages also allows Abacus to expose its entire
API to plugins, and load them at runtime.

34
docs/_pages/download.md Normal file
View File

@@ -0,0 +1,34 @@
---
in_header: true
layout: page
title: Download
permalink: /download/
---
Currently, we do not provide standalone executables due to our unfamiliarity with
including 3rd-party software. Abacus uses a number of open source libraries,
and we do not want to breach the license terms for any of them. As soon as
as we figure out the correct way to distribute Abacus, we will make a
standalone distribution available. In the meantime, please use the below
steps to run Abacus from source.
## Getting the Code
Abacus is an open source project, and is distributed under the MIT license.
If you would like to download the source code, simply clone it from
[GitHub](https://github.com/DanilaFe/abacus).
Alternatively, if you don't want the bleeding edge version, check out the
[releases](https://github.com/DanilaFe/abacus/releases).
## Running from Source
Once you have unpacked the source code, you can simply run it from
the command line via the shell command:
```
./gradlew run
```
If you're on Windows, the command is similar:
```
gradlew run
```
This should download a distribution of Gradle, a build system that is
used to compile Abacus. After some time, the Abacus window should appear.
From there, you can use it normally.

119
docs/assets/css/main.scss Normal file
View File

@@ -0,0 +1,119 @@
---
---
@import url('https://fonts.googleapis.com/css?family=Source+Code+Pro|Open+Sans|Raleway');
$background-color: #19d69e;
$code-color: #efefef;
$accent-color: #00AFE8;
$clear-color: white;
$title-font: "Open Sans";
$text-font: Helvetica;
$code-font: "Source Code Pro";
$max-width: 850px;
a {
text-decoration: none;
color: $background-color;
&.button {
display: inline-block;
background-color: $background-color;
color: $clear-color;
padding: 10px;
text-decoration: none;
border-radius: 2px;
margin: 10px;
transition: background-color .25s;
&:hover {
background-color: $clear-color;
color: $background-color;
}
&.inverted {
background-color: $clear-color;
color: $background-color;
&:hover {
background-color: $background-color;
color: $clear-color;
}
}
}
}
h1, h2, h3, h4, h5, h6 {
font: {
family: $title-font;
}
}
h1 {
font-size: 50px;
}
nav {
box-sizing: border-box;
background-color: $clear-color;
width: 100%;
padding: 20px;
a {
text-decoration: none;
color: $background-color;
font-size: 20px;
margin-right: 10px;
&.primary-link {
font-size: 30px;
margin-right: 20px;
}
&:hover {
color: $accent-color;
}
transition: color .25s;
}
}
body {
background-color: $background-color;
font: {
family: $text-font;
}
margin: 0px;
}
.center {
box-sizing: border-box;
width: 100%;
max-width: $max-width;
@media (min-width: $max-width) {
margin: {
left: auto;
right: auto;
}
}
}
.content {
margin-top: 20px;
padding: 30px;
background-color: $clear-color;
}
code {
background-color: $code-color;
display: inline-block;
padding: 5px;
font-family: $code-font;
pre & {
padding: 10px;
display: block;
width: 100%;
}
}

6
docs/index.md Normal file
View File

@@ -0,0 +1,6 @@
---
# You don't need to edit this file, it's empty on purpose.
# Edit theme's home layout instead if you wanna make some changes
# See: https://jekyllrb.com/docs/themes/#overriding-theme-defaults
layout: home
---

10
fx/build.gradle Normal file
View File

@@ -0,0 +1,10 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.1.3'
}
apply plugin: 'application'
dependencies {
compile project(':core')
}
mainClassName = 'org.nwapw.abacus.fx.AbacusApplication'

View File

@@ -17,6 +17,10 @@ public class AbacusApplication extends Application {
*/
private AbacusController controller;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/abacus.fxml"));
@@ -33,4 +37,5 @@ public class AbacusApplication extends Application {
super.stop();
controller.performStop();
}
}

View File

@@ -1,13 +1,10 @@
package org.nwapw.abacus.fx;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.text.Text;
@@ -136,36 +133,12 @@ public class AbacusController implements PluginListener {
* The abacus instance used for changing the plugin configuration.
*/
private Abacus abacus;
/**
* Boolean which represents whether changes were made to the configuration.
*/
private boolean changesMade;
/**
* Whether an alert about changes to the configuration was already shown.
*/
private boolean reloadAlertShown;
/**
* The alert shown when a press to "apply" is needed.
*/
private Alert reloadAlert;
/**
* The runnable that takes care of killing computations that take too long.
*/
private final Runnable TIMER_RUNNABLE = () -> {
try {
Configuration abacusConfig = abacus.getConfiguration();
if(abacusConfig.getComputationDelay() == 0) return;
Thread.sleep((long) (abacusConfig.getComputationDelay() * 1000));
performStop();
} catch (InterruptedException e) { }
};
/**
* The runnable used to perform the calculation.
*/
private final Runnable CALCULATION_RUNNABLE = new Runnable() {
private String attemptCalculation(){
private String attemptCalculation() {
try {
TreeNode constructedTree = abacus.parseString(inputField.getText());
if (constructedTree == null) {
@@ -180,7 +153,7 @@ public class AbacusController implements PluginListener {
return resultingString;
} catch (ComputationInterruptedException exception) {
return ERR_STOP;
} catch (RuntimeException exception){
} catch (RuntimeException exception) {
exception.printStackTrace();
return ERR_EXCEPTION;
}
@@ -197,6 +170,18 @@ public class AbacusController implements PluginListener {
});
}
};
/**
* Boolean which represents whether changes were made to the configuration.
*/
private boolean changesMade;
/**
* Whether an alert about changes to the configuration was already shown.
*/
private boolean reloadAlertShown;
/**
* The alert shown when a press to "apply" is needed.
*/
private Alert reloadAlert;
/**
* The thread that is waiting to pause the calculation.
*/
@@ -205,6 +190,18 @@ public class AbacusController implements PluginListener {
* The thread in which the computation runs.
*/
private Thread calculationThread;
/**
* The runnable that takes care of killing computations that take too long.
*/
private final Runnable TIMER_RUNNABLE = () -> {
try {
Configuration abacusConfig = abacus.getConfiguration();
if (abacusConfig.getComputationDelay() == 0) return;
Thread.sleep((long) (abacusConfig.getComputationDelay() * 1000));
performStop();
} catch (InterruptedException e) {
}
};
/**
* Alerts the user if the changes they made
@@ -265,7 +262,7 @@ public class AbacusController implements PluginListener {
computationLimitField.setText(Double.toString(abacus.getConfiguration().getComputationDelay()));
computationLimitField.textProperty().addListener((observable, oldValue, newValue) -> {
if(!newValue.matches("(\\d+(\\.\\d*)?)?")) {
if (!newValue.matches("(\\d+(\\.\\d*)?)?")) {
computationLimitField.setText(oldValue);
} else {
changesMade = true;
@@ -292,12 +289,12 @@ public class AbacusController implements PluginListener {
}
@FXML
public void performStop(){
if(calculationThread != null) {
public void performStop() {
if (calculationThread != null) {
calculationThread.interrupt();
calculationThread = null;
}
if(computationLimitThread != null){
if (computationLimitThread != null) {
computationLimitThread.interrupt();
computationLimitThread = null;
}
@@ -312,7 +309,7 @@ public class AbacusController implements PluginListener {
}
@FXML
public void performScan(){
public void performScan() {
PluginManager abacusPluginManager = abacus.getPluginManager();
abacusPluginManager.removeAll();
abacusPluginManager.addInstantiated(new StandardPlugin(abacus.getPluginManager()));
@@ -339,7 +336,7 @@ public class AbacusController implements PluginListener {
for (ToggleablePlugin pluginEntry : enabledPlugins) {
if (!pluginEntry.isEnabled()) disabledPlugins.add(pluginEntry.getClassName());
}
if(computationLimitField.getText().matches("\\d*(\\.\\d+)?") && computationLimitField.getText().length() != 0)
if (computationLimitField.getText().matches("\\d*(\\.\\d+)?") && computationLimitField.getText().length() != 0)
configuration.setComputationDelay(Double.parseDouble(computationLimitField.getText()));
configuration.saveTo(CONFIG_FILE);
changesMade = false;
@@ -363,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));
}

View File

@@ -14,7 +14,7 @@ public class DocumentationCell extends ListCell<Documentation> {
private Label longDescription;
private TitledPane titledPane;
public DocumentationCell(){
public DocumentationCell() {
VBox vbox = new VBox();
vbox.setSpacing(10);
titledPane = new TitledPane();
@@ -41,7 +41,7 @@ public class DocumentationCell extends ListCell<Documentation> {
@Override
protected void updateItem(Documentation item, boolean empty) {
super.updateItem(item, empty);
if(empty){
if (empty) {
codeNameLabel.setText("");
nameLabel.setText("");
description.setText("");

View File

@@ -14,7 +14,7 @@ import javafx.beans.property.SimpleStringProperty
* @param parsed the parsed version of the input.
* @param output the output string.
*/
class HistoryModel(input: String, parsed: String, output: String){
class HistoryModel(input: String, parsed: String, output: String) {
/**
* The property that holds the input.

View File

@@ -12,7 +12,7 @@ import javafx.beans.property.SimpleBooleanProperty
* @param className the name of the class that this model concerns.
* @param enabled whether or not the model should start enabled.
*/
class ToggleablePlugin (val className: String, enabled: Boolean) {
class ToggleablePlugin(val className: String, enabled: Boolean) {
/**
* The property used to interact with JavaFX components.

View File

@@ -1 +1,2 @@
rootProject.name = 'abacus'
include 'core', 'fx'

View File

@@ -1,7 +0,0 @@
package org.nwapw.abacus.function;
public enum DocumentationType {
FUNCTION
}

View File

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

View File

@@ -1,106 +0,0 @@
package org.nwapw.abacus.window;
import org.nwapw.abacus.tree.TreeNode;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.List;
/**
* A table model to store data about the history of inputs
* in the calculator.
*/
public class HistoryTableModel extends AbstractTableModel {
/**
* Static array used to get the column names.
*/
public static final String[] COLUMN_NAMES = {
"Input",
"Parsed Input",
"Output"
};
/**
* Static array used to get the class of each column.
*/
public static final Class[] CLASS_TYPES = {
String.class,
TreeNode.class,
String.class
};
/**
* The list of entries.
*/
List<HistoryEntry> entries;
/**
* Creates a new empty history table model
*/
public HistoryTableModel() {
entries = new ArrayList<>();
}
/**
* Adds an entry to the model.
*
* @param entry the entry to add.
*/
public void addEntry(HistoryEntry entry) {
entries.add(entry);
}
@Override
public int getRowCount() {
return entries.size();
}
@Override
public int getColumnCount() {
return 3;
}
@Override
public String getColumnName(int columnIndex) {
return COLUMN_NAMES[columnIndex];
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return CLASS_TYPES[columnIndex];
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return entries.get(rowIndex).nthValue(columnIndex);
}
/**
* Class used specifically to hold data about
* the previous entries into the calculator.
*/
public static class HistoryEntry {
public String input;
public TreeNode parsedInput;
public String output;
public HistoryEntry(String input, TreeNode parsedInput, String output) {
this.input = input;
this.parsedInput = parsedInput;
this.output = output;
}
Object nthValue(int n) {
if (n == 0) return input;
if (n == 1) return parsedInput;
if (n == 2) return output;
return null;
}
}
}

View File

@@ -1,255 +0,0 @@
package org.nwapw.abacus.window;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.tree.TreeNode;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
/**
* The main UI window for the calculator.
*/
public class Window extends JFrame {
private static final String CALC_STRING = "Calculate";
private static final String SYNTAX_ERR_STRING = "Syntax Error";
private static final String EVAL_ERR_STRING = "Evaluation Error";
private static final String NUMBER_SYSTEM_LABEL = "Number Type:";
private static final String FUNCTION_LABEL = "Functions:";
/**
* Array of Strings to which the "calculate" button's text
* changes. For instance, in the graph tab, the name will
* be "Graph" and not "Calculate".
*/
private static final String[] BUTTON_NAMES = {
CALC_STRING,
CALC_STRING
};
/**
* Array of booleans that determine whether the input
* field and the input button are enabled at a particular
* index.
*/
private static boolean[] INPUT_ENABLED = {
true,
false
};
/**
* The instance of the Abacus class, used
* for interaction with plugins and configuration.
*/
private Abacus abacus;
/**
* The last output by the calculator.
*/
private String lastOutput;
/**
* The tabbed pane that separates calculator contexts.
*/
private JTabbedPane pane;
/**
* The panel where the output occurs.
*/
private JPanel calculationPanel;
/**
* The text area reserved for the last output.
*/
private JTextArea lastOutputArea;
/**
* The table used for storing history results.
*/
private JTable historyTable;
/**
* The table model used for managing history.
*/
private HistoryTableModel historyModel;
/**
* The scroll pane for the history area.
*/
private JScrollPane historyScroll;
/**
* The panel where the input occurs.
*/
private JPanel inputPanel;
/**
* The input text field.
*/
private JTextField inputField;
/**
* The "submit" button.
*/
private JButton inputEnterButton;
/**
* The side panel for separate configuration.
*/
private JPanel settingsPanel;
/**
* Panel for elements relating to number
* system selection.
*/
private JPanel numberSystemPanel;
/**
* The possible list of number systems.
*/
private JComboBox<String> numberSystemList;
/**
* The panel for elements relating to
* function selection.
*/
private JPanel functionSelectPanel;
/**
* The list of functions available to the user.
*/
private JComboBox<String> functionList;
/**
* Action listener that causes the input to be evaluated.
*/
private ActionListener evaluateListener = (event) -> {
TreeNode parsedExpression = abacus.parseString(inputField.getText());
if (parsedExpression == null) {
lastOutputArea.setText(SYNTAX_ERR_STRING);
return;
}
NumberInterface numberInterface = abacus.evaluateTree(parsedExpression);
if (numberInterface == null) {
lastOutputArea.setText(EVAL_ERR_STRING);
return;
}
lastOutput = numberInterface.toString();
historyModel.addEntry(new HistoryTableModel.HistoryEntry(inputField.getText(), parsedExpression, lastOutput));
historyTable.invalidate();
lastOutputArea.setText(lastOutput);
inputField.setText("");
};
/**
* Array of listeners that tell the input button how to behave
* at a given input tab.
*/
private ActionListener[] listeners = {
evaluateListener,
null
};
/**
* Creates a new window with the given manager.
*
* @param abacus the calculator instance to interact with other components.
*/
public Window(Abacus abacus) {
this();
this.abacus = abacus;
}
/**
* Creates a new window.
*/
private Window() {
super();
lastOutput = "";
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(320, 480);
inputField = new JTextField();
inputEnterButton = new JButton(CALC_STRING);
inputPanel = new JPanel();
inputPanel.setLayout(new BorderLayout());
inputPanel.add(inputField, BorderLayout.CENTER);
inputPanel.add(inputEnterButton, BorderLayout.SOUTH);
historyModel = new HistoryTableModel();
historyTable = new JTable(historyModel);
historyScroll = new JScrollPane(historyTable);
lastOutputArea = new JTextArea(lastOutput);
lastOutputArea.setEditable(false);
calculationPanel = new JPanel();
calculationPanel.setLayout(new BorderLayout());
calculationPanel.add(historyScroll, BorderLayout.CENTER);
calculationPanel.add(lastOutputArea, BorderLayout.SOUTH);
numberSystemList = new JComboBox<>();
numberSystemPanel = new JPanel();
numberSystemPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
numberSystemPanel.setLayout(new FlowLayout());
numberSystemPanel.add(new JLabel(NUMBER_SYSTEM_LABEL));
numberSystemPanel.add(numberSystemList);
numberSystemPanel.setMaximumSize(numberSystemPanel.getPreferredSize());
functionList = new JComboBox<>();
functionSelectPanel = new JPanel();
functionSelectPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
functionSelectPanel.setLayout(new FlowLayout());
functionSelectPanel.add(new JLabel(FUNCTION_LABEL));
functionSelectPanel.add(functionList);
functionSelectPanel.setMaximumSize(functionSelectPanel.getPreferredSize());
settingsPanel = new JPanel();
settingsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
settingsPanel.setLayout(new BoxLayout(settingsPanel, BoxLayout.PAGE_AXIS));
settingsPanel.add(numberSystemPanel);
settingsPanel.add(functionSelectPanel);
pane = new JTabbedPane();
pane.add("Calculator", calculationPanel);
pane.add("Settings", settingsPanel);
pane.addChangeListener(e -> {
int selectionIndex = pane.getSelectedIndex();
boolean enabled = INPUT_ENABLED[selectionIndex];
ActionListener listener = listeners[selectionIndex];
inputEnterButton.setText(BUTTON_NAMES[selectionIndex]);
inputField.setEnabled(enabled);
inputEnterButton.setEnabled(enabled);
for (ActionListener removingListener : inputEnterButton.getActionListeners()) {
inputEnterButton.removeActionListener(removingListener);
inputField.removeActionListener(removingListener);
}
if (listener != null) {
inputEnterButton.addActionListener(listener);
inputField.addActionListener(listener);
}
});
add(pane, BorderLayout.CENTER);
add(inputPanel, BorderLayout.SOUTH);
inputEnterButton.addActionListener(evaluateListener);
inputField.addActionListener(evaluateListener);
historyTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
Point clickPoint = e.getPoint();
if (e.getClickCount() == 2) {
int row = historyTable.rowAtPoint(clickPoint);
int column = historyTable.columnAtPoint(clickPoint);
String toCopy = historyTable.getValueAt(row, column).toString();
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(toCopy), null);
}
}
});
}
@Override
public void setVisible(boolean b) {
super.setVisible(b);
if (b) inputField.requestFocusInWindow();
}
}

View File

@@ -1,52 +0,0 @@
package org.nwapw.abacus.tree
/**
* A tree node that holds a function call.
*
* The function call node can hold any number of children, and passes the to the appropriate reducer,
* but that is its sole purpose.
*
* @param function the function string.
*/
data class FunctionNode(val function: String) : TreeNode() {
/**
* List of function parameters added to this node.
*/
val children: MutableList<TreeNode> = mutableListOf()
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
val children = Array<Any>(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)
}
}