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

Compare commits

..

92 Commits

Author SHA1 Message Date
192269ea9a Merge branch 'master' into variable-parsing 2017-08-29 18:11:30 -07:00
f134e5aa04 Merge pull request #5 from DanilaFe/dokka-setup
Add dokka plugin to generate documentation.
2017-08-29 18:09:36 -07:00
ece9f1ae04 Add dokka plugin to generate documentation. 2017-08-27 15:47:32 -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
1c448048f2 Fix typos created in removing the promoteTo calls. 2017-08-09 19:23:44 -07:00
fd21014c39 Phase out as many promoteTo calls as possible. 2017-08-09 19:04:32 -07:00
Arthur Drobot
fee9f091fd Revert "Write a RationalNumber class."
This reverts commit f3942b6760.
2017-08-09 18:43:52 -07:00
021e569491 Make the C in nCr capital. 2017-08-09 17:15:12 -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
e6f5af3727 Add optimization to NPR. 2017-08-09 12:47:22 -07:00
4e042bd0eb Implement two combinatorics operators. 2017-08-09 12:34:02 -07:00
d6f4838f05 Add a random function. 2017-08-09 11:51:12 -07:00
61475a24d9 Fix the weird scroll bars. 2017-08-09 11:22:01 -07:00
c498a5b643 Merge branch 'function-doc' 2017-08-09 11:10:00 -07:00
5e3daaed43 Rescan the plugins folder. 2017-08-09 11:02:21 -07:00
b99ad5a09a Add plugin removal from the plugin manager. 2017-08-09 10:30:04 -07:00
ff8701a7bf Deprecate the promotion functions. 2017-08-08 15:29:19 -07:00
Arthur Drobot
1d6957c4d9 Write a RationalNumber class. 2017-08-08 14:53:34 -07:00
33b175a3c6 Fix some documentation glitches. 2017-08-08 14:39:41 -07:00
c95a6df304 Change the sorting order. 2017-08-08 14:26:04 -07:00
3316f02e2b Add documentation. 2017-08-08 14:18:14 -07:00
6767a0e4aa Change the width property bound to the width. 2017-08-08 14:18:08 -07:00
400e4578a0 Add documentation browsing to the abacus controller. 2017-08-08 13:49:19 -07:00
9d92d0eebb Add registering documentation to plugins. 2017-08-08 13:49:04 -07:00
fdcf2b5c6d Fix documentation loading code. 2017-08-08 13:47:03 -07:00
8c3de54d0c Add a matches function. 2017-08-08 13:46:54 -07:00
5a57544067 Add a plugin registration function to Plugins. 2017-08-08 11:27:59 -07:00
61f40c72aa Add a registered documentation set. 2017-08-08 11:09:46 -07:00
ea5ff08c09 Add documentation type for a documentation entry. 2017-08-08 10:54:36 -07:00
5f80c0bf14 Add a documentation class. 2017-08-08 10:50:30 -07:00
7dcc80fcae Merge branch 'master' of github.com:DanilaFe/abacus 2017-08-08 10:19:45 -07:00
d10536155b Make the map private. 2017-08-08 10:13:35 -07:00
536cac7b23 Convert Reducer interface to Kotlin. 2017-08-08 10:11:30 -07:00
Arthur Drobot
b6e4c6d2ea Merge branch 'sig-fig' 2017-08-08 09:46:46 -07:00
Arthur Drobot
f8b3559cec Add some tests to pow. 2017-08-08 09:45:13 -07:00
Arthur Drobot
4cf4ba98a8 Adjust unit tests. Set the number of significant figures in the output to 50. 2017-08-08 09:27:10 -07:00
12710c625b Add missing comments. 2017-08-07 22:48:56 -07:00
e71b037195 Switch the ToggleablePlugin class to Kotlin to avoid boilerplate. 2017-08-07 22:44:16 -07:00
fe92929856 Switch HistoryModel to Kotlin to avoid boilerplate. 2017-08-07 22:39:35 -07:00
e61cfdca46 Create a documentation object class. 2017-08-07 22:30:32 -07:00
Arthur Drobot
68fbcd2d7c Modify exp to use the non-alternating series and take a reciprocal in the case of a negative argument. 2017-08-07 18:13:18 -07:00
Arthur Drobot
ed92b382f0 Rewrite precise number to limit significant figures rather than decimal places. Add getMaxError to NumberInterface; modify numbers and StandardPlugin appropriately. 2017-08-07 17:58:18 -07:00
75 changed files with 1380 additions and 902 deletions

View File

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

4
core/build.gradle Normal file
View File

@@ -0,0 +1,4 @@
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,11 @@
package org.nwapw.abacus.function;
/**
* Enum that holds the type of documentation that has been
* registered with Abacus.
*/
public enum DocumentationType {
FUNCTION
}

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

@@ -128,5 +128,9 @@ public class NaiveNumber extends NumberInterface {
return Double.toString(Math.round(value * shiftBy) / shiftBy);
}
@Override
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();
}
@@ -240,6 +242,7 @@ public abstract class NumberInterface {
* @param toClass the class to promote to.
* @return the resulting new instance.
*/
@Deprecated
protected abstract NumberInterface promoteToInternal(Class<? extends NumberInterface> toClass);
/**
@@ -250,9 +253,18 @@ public abstract class NumberInterface {
* @param toClass the class to promote to.
* @return the resulting new instance.
*/
@Deprecated
public final NumberInterface promoteTo(Class<? extends NumberInterface> toClass) {
checkInterrupted();
return promoteToInternal(toClass);
}
/**
* 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

@@ -1,7 +1,7 @@
package org.nwapw.abacus.number;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.math.MathContext;
/**
* A number that uses a BigDecimal to store its value,
@@ -22,6 +22,21 @@ public class PreciseNumber extends NumberInterface {
*/
public static final PreciseNumber TEN = new PreciseNumber(BigDecimal.TEN);
/**
* The number of extra significant figures kept in calculations before rounding for output.
*/
private static int numExtraInternalSigFigs = 15;
/**
* MathContext that is used when rounding a number prior to output.
*/
private static MathContext outputContext = new MathContext(50);
/**
* MathContext that is actually used in calculations.
*/
private static MathContext internalContext = new MathContext(outputContext.getPrecision() + numExtraInternalSigFigs);
/**
* The value of the PreciseNumber.
*/
@@ -48,7 +63,7 @@ public class PreciseNumber extends NumberInterface {
@Override
public int getMaxPrecision() {
return 65;
return internalContext.getPrecision();
}
@Override
@@ -58,7 +73,7 @@ public class PreciseNumber extends NumberInterface {
@Override
public NumberInterface divideInternal(NumberInterface divisor) {
return new PreciseNumber(value.divide(((PreciseNumber) divisor).value, this.getMaxPrecision(), RoundingMode.HALF_UP));
return new PreciseNumber(value.divide(((PreciseNumber) divisor).value, internalContext));
}
@Override
@@ -114,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;
@@ -147,7 +162,11 @@ public class PreciseNumber extends NumberInterface {
@Override
public String toString() {
BigDecimal rounded = value.setScale(getMaxPrecision() - 15, RoundingMode.HALF_UP);
return rounded.stripTrailingZeros().toPlainString();
return value.round(outputContext).toString();
}
@Override
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);
}};

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,7 +53,7 @@ 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) {
output.add(new Match<>("", TokenType.INTERNAL_FUNCTION_END));
@@ -143,7 +135,9 @@ 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()));
return new NumberNode(match.getContent());
} else if (matchType == TokenType.VARIABLE) {
return new VariableNode(match.getContent());
} else if (matchType == TokenType.FUNCTION) {
String functionName = match.getContent();
FunctionNode node = new FunctionNode(functionName);
@@ -162,7 +156,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

@@ -14,7 +14,7 @@ public abstract class NumberImplementation {
/**
* The list of paths through which this implementation can be promoted.
*/
protected Map<Class<? extends NumberInterface>, Function<NumberInterface, NumberInterface>> promotionPaths;
private Map<Class<? extends NumberInterface>, Function<NumberInterface, NumberInterface>> promotionPaths;
/**
* The implementation class for this implementation.
*/

View File

@@ -1,13 +1,11 @@
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.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
@@ -94,6 +92,16 @@ public abstract class Plugin {
manager.registerNumberImplementation(name, implementation);
}
/**
* 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) {
manager.registerDocumentation(documentation);
}
/**
* Searches the PluginManager for the given function name.
* This can be used by the plugins internally in order to call functions
@@ -130,6 +138,17 @@ public abstract class Plugin {
return manager.numberImplementationFor(name);
}
/**
* Searches the PluginManager for the given documentation name and type.
*
* @param name the name for which to search.
* @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) {
return manager.documentationFor(name, type);
}
/**
* Searches the plugin manager for a Pi value for the given number implementation.
* This is done so that number implementations with various degrees of precision

View File

@@ -1,12 +1,17 @@
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.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
@@ -34,6 +39,10 @@ public class PluginManager {
* The map of number implementations registered by the plugins.
*/
private Map<String, NumberImplementation> registeredNumberImplementations;
/**
* The map of documentation for functions registered by the plugins.
*/
private Set<Documentation> registeredDocumentation;
/**
* The list of number implementations that have been
* found by their implementation class.
@@ -65,6 +74,7 @@ public class PluginManager {
registeredFunctions = new HashMap<>();
registeredOperators = new HashMap<>();
registeredNumberImplementations = new HashMap<>();
registeredDocumentation = new HashSet<>();
cachedInterfaceImplementations = new HashMap<>();
cachedPi = new HashMap<>();
listeners = new HashSet<>();
@@ -72,58 +82,96 @@ 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 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) {
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 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) {
Documentation toReturn = null;
for (Documentation entry : registeredDocumentation) {
if (entry.getCodeName().equals(name) && entry.getType() == type) {
toReturn = entry;
break;
}
}
if (toReturn == null) {
toReturn = new Documentation(name, "", "", "", type);
registerDocumentation(toReturn);
}
return toReturn;
}
/**
* Gets the number implementation for the given implementation class.
*
@@ -133,9 +181,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;
}
@@ -187,6 +235,25 @@ 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;
plugins.removeIf(plugin -> plugin.getClass() == toRemove);
loadedPluginClasses.remove(toRemove);
}
/**
* Removes all plugins from this plugin manager.
*/
public void removeAll() {
loadedPluginClasses.clear();
plugins.clear();
}
/**
* Loads all the plugins in the PluginManager.
*/
@@ -212,6 +279,7 @@ public class PluginManager {
registeredFunctions.clear();
registeredOperators.clear();
registeredNumberImplementations.clear();
registeredDocumentation.clear();
cachedInterfaceImplementations.clear();
cachedPi.clear();
listeners.forEach(e -> e.onUnload(this));

View File

@@ -1,9 +1,6 @@
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.function.OperatorType;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber;
@@ -18,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, +
*/
@@ -89,156 +81,20 @@ public class StandardPlugin extends Plugin {
}
});
/**
* The division operator, /
* The combination operator.
*/
public static final Operator OP_DIVIDE = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() {
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[1].compareTo(NaiveNumber.ZERO.promoteTo(params[1].getClass())) != 0;
return params.length == 2 && params[0].fractionalPart().signum() == 0
&& params[1].fractionalPart().signum() == 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].divide(params[1]);
return OP_NPR.getFunction().apply(params).divide(OP_FACTORIAL.getFunction().apply(params[1]));
}
});
/**
* The factorial operator, !
*/
public static final Operator OP_FACTORIAL = new Operator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0, 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
&& params[0].fractionalPart().compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0
&& params[0].signum() >= 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
if (params[0].signum() == 0) {
return fromInt(params[0].getClass(), 1);
}
NumberInterface factorial = params[0];
NumberInterface multiplier = params[0];
//It is necessary to later prevent calls of factorial on anything but non-negative integers.
while ((multiplier = multiplier.subtract(NaiveNumber.ONE.promoteTo(multiplier.getClass()))).signum() == 1) {
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()));
}*/
}
});
/**
* The absolute value function, abs(-3) = 3
*/
public static final Function FUNCTION_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()));
}
};
/**
* The natural log function.
*/
public static final Function FUNCTION_LN = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1 && params[0].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) > 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface param = params[0];
int powersOf2 = 0;
while (FUNCTION_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(fromInt(param.getClass(), 2));
powersOf2++;
if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() != 1) {
break;
//No infinite loop for you.
}
} else {
param = param.multiply(fromInt(param.getClass(), 2));
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 = getMaxError(x);
x = x.subtract(NaiveNumber.ONE.promoteTo(x.getClass())); //Terms used are for log(x+1).
NumberInterface currentNumerator = x, currentTerm = x, sum = x;
int n = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) {
n++;
currentNumerator = currentNumerator.multiply(x).negate();
currentTerm = currentNumerator.divide(new NaiveNumber(n).promoteTo(x.getClass()));
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 = getMaxError(number);
//NumberInterface errorBound = fromInt(number.getClass(), 1);
//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 = fromInt(number.getClass(), 1), b = a, c = a;
NumberInterface sum = NaiveNumber.ZERO.promoteTo(number.getClass());
int n = 0;
while (a.compareTo(maxError) >= 1) {
n++;
a = a.divide(fromInt(number.getClass(), 3));
b = b.divide(fromInt(number.getClass(), 4));
c = NaiveNumber.ONE.promoteTo(number.getClass()).divide((new NaiveNumber(n)).promoteTo(number.getClass()));
sum = sum.add(a.add(b).multiply(c));
}
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())));
}
};
/**
* The implementation for double-based naive numbers.
*/
@@ -278,10 +134,10 @@ public class StandardPlugin extends Plugin {
.negate();
for (int i = 0; i < termsNeeded; i++) {
M = M
.multiply(new NaiveNumber(12 * i + 2).promoteTo(PreciseNumber.class))
.multiply(new NaiveNumber(12 * i + 6).promoteTo(PreciseNumber.class))
.multiply(new NaiveNumber(12 * i + 10).promoteTo(PreciseNumber.class))
.divide(new NaiveNumber(Math.pow(i + 1, 3)).promoteTo(PreciseNumber.class));
.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));
@@ -289,6 +145,238 @@ public class StandardPlugin extends Plugin {
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, /
*/
public static final Operator OP_DIVIDE = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2 && params[1].compareTo(fromInt(params[0].getClass(), 0)) != 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].divide(params[1]);
}
});
/**
* The factorial operator, !
*/
public static final Operator OP_FACTORIAL = new Operator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0, new Function() {
//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
&& params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0
&& params[0].signum() >= 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
if (params[0].signum() == 0) {
return fromInt(params[0].getClass(), 1);
}
NumberInterface one = fromInt(params[0].getClass(), 1);
NumberInterface factorial = params[0];
NumberInterface multiplier = params[0];
//It is necessary to later prevent calls of factorial on anything but non-negative integers.
while ((multiplier = multiplier.subtract(one)).signum() == 1) {
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()));
}*/
}
});
/**
* The permutation operator.
*/
public static final Operator OP_NPR = 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) {
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) {
remainingMultiplications = params[0].subtract(remainingMultiplications);
}
while (remainingMultiplications.signum() > 0) {
total = total.multiply(multiplyBy);
remainingMultiplications = remainingMultiplications.subtract(fromInt(params[0].getClass(), 1));
multiplyBy = multiplyBy.subtract(fromInt(params[0].getClass(), 1));
}
return total;
}
});
/**
* The absolute value function, abs(-3) = 3
*/
public static final Function FUNCTION_ABS = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].multiply(fromInt(params[0].getClass(), params[0].signum()));
}
};
/**
* The natural log function.
*/
public static final Function FUNCTION_LN = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1 && params[0].compareTo(fromInt(params[0].getClass(), 0)) > 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface param = params[0];
NumberInterface one = fromInt(param.getClass(), 1);
int powersOf2 = 0;
while (FUNCTION_ABS.apply(param.subtract(one)).compareTo(new NaiveNumber(0.1).promoteTo(param.getClass())) >= 0) {
if (param.subtract(one).signum() == 1) {
param = param.divide(fromInt(param.getClass(), 2));
powersOf2++;
if (param.subtract(one).signum() != 1) {
break;
//No infinite loop for you.
}
} else {
param = param.multiply(fromInt(param.getClass(), 2));
powersOf2--;
if (param.subtract(one).signum() != -1) {
break;
//No infinite loop for you.
}
}
}
return getLog2(param).multiply(fromInt(param.getClass(), powersOf2)).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 = x.getMaxError();
x = x.subtract(fromInt(x.getClass(), 1)); //Terms used are for log(x+1).
NumberInterface currentNumerator = x, currentTerm = x, sum = x;
int n = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) {
n++;
currentNumerator = currentNumerator.multiply(x).negate();
currentTerm = currentNumerator.divide(fromInt(x.getClass(), n));
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 = number.getMaxError();
//NumberInterface errorBound = fromInt(number.getClass(), 1);
//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 = fromInt(number.getClass(), 1), b = a, c = a;
NumberInterface sum = fromInt(number.getClass(), 0);
NumberInterface one = fromInt(number.getClass(), 1);
int n = 0;
while (a.compareTo(maxError) >= 1) {
n++;
a = a.divide(fromInt(number.getClass(), 3));
b = b.divide(fromInt(number.getClass(), 4));
c = one.divide(fromInt(number.getClass(), n));
sum = sum.add(a.add(b).multiply(c));
}
return sum;
}
};
/**
* Gets a random number smaller or equal to the given number's integer value.
*/
public static final Function FUNCTION_RAND_INT = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return fromInt(params[0].getClass(), (int) Math.round(Math.random() * params[0].floor().intValue()));
}
};
/**
* The caret / pow operator, ^
*/
public static final Operator OP_CARET = new Operator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
NumberInterface zero = fromInt(params[0].getClass(), 0);
return params.length == 2
&& !(params[0].compareTo(zero) == 0
&& params[1].compareTo(zero) == 0)
&& !(params[0].signum() == -1 && params[1].fractionalPart().compareTo(zero) != 0);
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface zero = fromInt(params[0].getClass(), 0);
if (params[0].compareTo(zero) == 0)
return zero;
else if (params[1].compareTo(zero) == 0)
return fromInt(params[0].getClass(), 1);
//Detect integer bases:
if (params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0
&& FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[0].getClass(), Integer.MAX_VALUE)) < 0
&& FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[1].getClass(), 1)) >= 0) {
NumberInterface[] newParams = {params[0], params[1].fractionalPart()};
return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(newParams));
}
return FUNCTION_EXP.apply(FUNCTION_LN.apply(FUNCTION_ABS.apply(params[0])).multiply(params[1]));
}
});
/**
* The square root function.
*/
public static final Function FUNCTION_SQRT = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return OP_CARET.getFunction().apply(params[0], ((new NaiveNumber(0.5)).promoteTo(params[0].getClass())));
}
};
private static final HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
/**
* The exponential function, exp(1) = e^1 = 2.71...
@@ -301,20 +389,15 @@ public class StandardPlugin extends Plugin {
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface maxError = getMaxError(params[0]);
NumberInterface maxError = params[0].getMaxError();
int n = 0;
if (params[0].signum() <= 0) {
NumberInterface currentTerm = NaiveNumber.ONE.promoteTo(params[0].getClass()), sum = currentTerm;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) {
n++;
currentTerm = currentTerm.multiply(params[0]).divide((new NaiveNumber(n)).promoteTo(params[0].getClass()));
sum = sum.add(currentTerm);
}
return sum;
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 = NaiveNumber.ONE.promoteTo(params[0].getClass());
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 {
@@ -322,7 +405,7 @@ public class StandardPlugin extends Plugin {
n++;
nextNumerator = nextNumerator.multiply(params[0]);
left = left.multiply(params[0]);
NumberInterface nextN = (new NaiveNumber(n + 1)).promoteTo(params[0].getClass());
NumberInterface nextN = fromInt(params[0].getClass(), n + 1);
right = right.multiply(nextN);
//System.out.println(left + ", " + right);
}
@@ -332,34 +415,6 @@ public class StandardPlugin extends Plugin {
}
}
};
/**
* The caret / pow operator, ^
*/
public static final Operator OP_CARET = new Operator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2
&& !(params[0].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0
&& params[1].compareTo(NaiveNumber.ZERO.promoteTo(params[1].getClass())) == 0)
&& !(params[0].signum() == -1 && params[1].fractionalPart().compareTo(NaiveNumber.ZERO.promoteTo(params[1].getClass())) != 0);
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
if (params[0].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0)
return NaiveNumber.ZERO.promoteTo(params[0].getClass());
else if (params[1].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0)
return NaiveNumber.ONE.promoteTo(params[1].getClass());
//Detect integer bases:
if(params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0
&& FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), Integer.MAX_VALUE)) < 0
&& FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[1].getClass(), 1)) >= 0){
NumberInterface[] newParams = {params[0], params[1].fractionalPart()};
return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(newParams));
}
return FUNCTION_EXP.apply(FUNCTION_LN.apply(FUNCTION_ABS.apply(params[0])).multiply(params[1]));
}
});
/**
* The sine function (the argument is interpreted in radians).
*/
@@ -424,7 +479,7 @@ public class StandardPlugin extends Plugin {
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return NaiveNumber.ONE.promoteTo(params[0].getClass()).divide(functionCos.apply(params[0]));
return fromInt(params[0].getClass(), 1).divide(functionCos.apply(params[0]));
}
};
/**
@@ -438,7 +493,7 @@ public class StandardPlugin extends Plugin {
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return NaiveNumber.ONE.promoteTo(params[0].getClass()).divide(functionSin.apply(params[0]));
return fromInt(params[0].getClass(), 1).divide(functionSin.apply(params[0]));
}
};
/**
@@ -456,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);
}
@@ -469,23 +662,13 @@ public class StandardPlugin extends Plugin {
* @return the value of the partial sum that has the same class as x.
*/
private static NumberInterface sumSeries(NumberInterface x, BiFunction<Integer, NumberInterface, NumberInterface> nthTermFunction, int n) {
NumberInterface sum = NaiveNumber.ZERO.promoteTo(x.getClass());
NumberInterface sum = fromInt(x.getClass(), 0);
for (int i = 0; i <= n; i++) {
sum = sum.add(nthTermFunction.apply(i, x));
}
return sum;
}
/**
* Returns the maximum error based on the precision of the class of number.
*
* @param number Any instance of the NumberInterface in question (should return an appropriate precision).
* @return the maximum error.
*/
private static NumberInterface getMaxError(NumberInterface number) {
return fromInt(number.getClass(), 10).intPow(-number.getMaxPrecision());
}
/**
* A factorial function that uses memoization for each number class; it efficiently
* computes factorials of non-negative integers.
@@ -497,13 +680,13 @@ public class StandardPlugin extends Plugin {
public static NumberInterface factorial(Class<? extends NumberInterface> numberClass, int n) {
if (!FACTORIAL_LISTS.containsKey(numberClass)) {
FACTORIAL_LISTS.put(numberClass, new ArrayList<>());
FACTORIAL_LISTS.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass));
FACTORIAL_LISTS.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass));
FACTORIAL_LISTS.get(numberClass).add(fromInt(numberClass, 1));
FACTORIAL_LISTS.get(numberClass).add(fromInt(numberClass, 1));
}
ArrayList<NumberInterface> list = FACTORIAL_LISTS.get(numberClass);
if (n >= list.size()) {
while (list.size() < n + 16) {
list.add(list.get(list.size() - 1).multiply(new NaiveNumber(list.size()).promoteTo(numberClass)));
list.add(list.get(list.size() - 1).multiply(fromInt(numberClass, list.size())));
}
}
return list.get(n);
@@ -517,7 +700,7 @@ public class StandardPlugin extends Plugin {
*/
private static NumberInterface sinTaylor(NumberInterface x) {
NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x;
NumberInterface maxError = getMaxError(x);
NumberInterface maxError = x.getMaxError();
int n = 1;
do {
n += 2;
@@ -535,7 +718,7 @@ public class StandardPlugin extends Plugin {
* @return theta in [0, 2pi) that differs from phi by a multiple of 2pi.
*/
private static NumberInterface getSmallAngle(NumberInterface phi, NumberInterface pi) {
NumberInterface twoPi = pi.multiply(new NaiveNumber("2").promoteTo(phi.getClass()));
NumberInterface twoPi = pi.multiply(fromInt(pi.getClass(), 2));
NumberInterface theta = FUNCTION_ABS.apply(phi).subtract(twoPi
.multiply(FUNCTION_ABS.apply(phi).divide(twoPi).floor())); //Now theta is in [0, 2pi).
if (phi.signum() < 0) {
@@ -546,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);
@@ -573,16 +757,77 @@ public class StandardPlugin extends Plugin {
registerOperator("^", OP_CARET);
registerOperator("!", OP_FACTORIAL);
registerOperator("nPr", OP_NPR);
registerOperator("nCr", OP_NCR);
registerFunction("abs", FUNCTION_ABS);
registerFunction("exp", FUNCTION_EXP);
registerFunction("ln", FUNCTION_LN);
registerFunction("sqrt", FUNCTION_SQRT);
registerFunction("sin", functionSin);
registerFunction("cos", functionCos);
registerFunction("tan", functionTan);
registerFunction("sec", functionSec);
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 " +
"from zero of a number.", "Given a number, this function finds the distance form " +
"zero of a number, effectively turning negative numbers into positive ones.\n\n" +
"Example: abs(-2) -> 2", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("exp", "Exponentiate", "Brings e to the given power.",
"This function evaluates e to the power of the given value, and is the inverse " +
"of the natural logarithm.\n\n" +
"Example: exp(1) -> 2.718...", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("ln", "Natural Logarithm", "Gets the natural " +
"logarithm of the given value.", "The natural logarithm of a number is " +
"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 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.", "Example: sin(pi/6) -> 0.5", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("cos", "Cosine", "Computes the cosine of the given angle, " +
"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.", "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.", "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.", "Example: csc(pi/6) -> 2", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("cot", "Cotangent", "Computes the cotangent of the given angle, " +
"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

@@ -27,7 +27,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];

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), OPEN_PARENTH(7), CLOSE_PARENTH(8);
/**
* The priority by which this token gets sorted.

View File

@@ -0,0 +1,26 @@
package org.nwapw.abacus.function
/**
* A data class used for storing information about a function.
*
* The Documentation class holds the information necessary to display the information
* about a function to the user.
*
* @param codeName the name of the function as it occurs in code.
* @param name the name of the function in English.
* @param description the short description of this function.
* @param longDescription the full description of this function.
* @param type the things this documentation maps to.
*/
data class Documentation(val codeName: String, val name: String,
val description: String, val longDescription: String,
val type: DocumentationType) {
fun matches(other: String): Boolean {
return codeName.toLowerCase().contains(other.toLowerCase()) ||
name.toLowerCase().contains(other.toLowerCase()) ||
description.toLowerCase().contains(other.toLowerCase()) ||
longDescription.toLowerCase().contains(other.toLowerCase())
}
}

View File

@@ -16,7 +16,7 @@ data class FunctionNode(val function: String) : TreeNode() {
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; })
val children = Array<Any>(children.size, { children[it].reduce(reducer) ?: return null; })
return reducer.reduceNode(this, *children)
}
@@ -36,7 +36,7 @@ data class FunctionNode(val function: String) : TreeNode() {
*
* @node the node to append.
*/
fun appendChild(node: TreeNode){
fun appendChild(node: TreeNode) {
children.add(node)
}
@@ -45,7 +45,7 @@ data class FunctionNode(val function: String) : TreeNode() {
*
* @node the node to prepend.
*/
fun prependChild(node: TreeNode){
fun prependChild(node: TreeNode) {
children.add(0, node)
}

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() {
data 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

@@ -0,0 +1,19 @@
package org.nwapw.abacus.tree
/**
* Reducer interface that takes a tree and returns a single value.
*
* 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> {
/**
* Reduces the given tree node, given its already reduced children.
*
* @param treeNode the tree node to reduce.
* @param children the list of children, of type 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,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.
*/
data 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

@@ -57,6 +57,7 @@
<Button text="Apply" onAction="#performSave"/>
<Button text="Reload Plugins" onAction="#performReload"/>
<Button text="Apply and Reload" onAction="#performSaveAndReload"/>
<Button text="Scan Plugins" onAction="#performScan"/>
</FlowPane>
</GridPane>
</Tab>

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,69 +36,72 @@ 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)", "19424263952412559365842088360176992193662086");
testOutput("exp300", "exp(300)", "19424263952412559365842088360176992193662086");
testOutput("exp300", "exp(300)", "1.9424263952412559365842088360176992193662086");
testOutput("exp(-500)", "exp((500)`)", "7.1245764067412855315491573771227552469277568");
}
@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");
testOutput("2^-1", "(2^(1)`)", "0.5");
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)`^.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
---

7
fx/build.gradle Normal file
View File

@@ -0,0 +1,7 @@
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,8 +1,6 @@
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;
@@ -14,6 +12,8 @@ import javafx.util.Callback;
import javafx.util.StringConverter;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.function.Documentation;
import org.nwapw.abacus.function.DocumentationType;
import org.nwapw.abacus.number.ComputationInterruptedException;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.plugin.ClassFinder;
@@ -24,7 +24,10 @@ import org.nwapw.abacus.tree.TreeNode;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -97,7 +100,7 @@ public class AbacusController implements PluginListener {
@FXML
private TextField computationLimitField;
@FXML
private ListView<String> functionListView;
private ListView<Documentation> functionListView;
@FXML
private TextField functionListSearchField;
@@ -120,46 +123,22 @@ public class AbacusController implements PluginListener {
/**
* The list of functions that are registered in the calculator.
*/
private ObservableList<String> functionList;
private ObservableList<Documentation> functionList;
/**
* The filtered list displayed to the user.
*/
private FilteredList<String> functionFilter;
private FilteredList<Documentation> functionFilter;
/**
* 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) {
@@ -174,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;
}
@@ -191,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.
*/
@@ -199,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
@@ -216,7 +219,7 @@ public class AbacusController implements PluginListener {
Callback<TableColumn<HistoryModel, String>, TableCell<HistoryModel, String>> cellFactory =
param -> new CopyableCell<>();
Callback<ListView<ToggleablePlugin>, ListCell<ToggleablePlugin>> pluginCellFactory =
param -> new CheckBoxListCell<>(ToggleablePlugin::enabledProperty, new StringConverter<ToggleablePlugin>() {
param -> new CheckBoxListCell<>(ToggleablePlugin::getEnabledProperty, new StringConverter<ToggleablePlugin>() {
@Override
public String toString(ToggleablePlugin object) {
return object.getClassName().substring(object.getClassName().lastIndexOf('.') + 1);
@@ -224,15 +227,15 @@ public class AbacusController implements PluginListener {
@Override
public ToggleablePlugin fromString(String string) {
return new ToggleablePlugin(true, string);
return new ToggleablePlugin(string, true);
}
});
functionList = FXCollections.observableArrayList();
functionFilter = new FilteredList<>(functionList, (s) -> true);
functionListView.setItems(functionFilter);
functionListSearchField.textProperty().addListener((observable, oldValue, newValue) ->
functionFilter.setPredicate((newValue.length() == 0) ? ((s) -> true) : ((s) -> s.contains(newValue))));
functionFilter.setPredicate((newValue.length() == 0) ? ((s) -> true) : ((s) -> s.matches(newValue))));
functionListView.setCellFactory(param -> new DocumentationCell());
historyData = FXCollections.observableArrayList();
historyTable.setItems(historyData);
numberImplementationOptions = FXCollections.observableArrayList();
@@ -243,11 +246,11 @@ public class AbacusController implements PluginListener {
enabledPluginView.setItems(enabledPlugins);
enabledPluginView.setCellFactory(pluginCellFactory);
inputColumn.setCellFactory(cellFactory);
inputColumn.setCellValueFactory(cell -> cell.getValue().inputProperty());
inputColumn.setCellValueFactory(cell -> cell.getValue().getInputProperty());
parsedColumn.setCellFactory(cellFactory);
parsedColumn.setCellValueFactory(cell -> cell.getValue().parsedProperty());
parsedColumn.setCellValueFactory(cell -> cell.getValue().getParsedProperty());
outputColumn.setCellFactory(cellFactory);
outputColumn.setCellValueFactory(cell -> cell.getValue().outputProperty());
outputColumn.setCellValueFactory(cell -> cell.getValue().getOutputProperty());
coreTabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue.equals(settingsTab)) alertIfApplyNeeded(true);
});
@@ -255,17 +258,11 @@ public class AbacusController implements PluginListener {
abacus = new Abacus(new Configuration(CONFIG_FILE));
PluginManager abacusPluginManager = abacus.getPluginManager();
abacusPluginManager.addListener(this);
abacusPluginManager.addInstantiated(new StandardPlugin(abacus.getPluginManager()));
try {
ClassFinder.loadJars("plugins").forEach(abacusPluginManager::addClass);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
abacusPluginManager.reload();
performScan();
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;
}
@@ -311,6 +308,19 @@ public class AbacusController implements PluginListener {
reloadAlertShown = false;
}
@FXML
public void performScan() {
PluginManager abacusPluginManager = abacus.getPluginManager();
abacusPluginManager.removeAll();
abacusPluginManager.addInstantiated(new StandardPlugin(abacus.getPluginManager()));
try {
ClassFinder.loadJars("plugins").forEach(abacusPluginManager::addClass);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
abacusPluginManager.reload();
}
@FXML
public void performReload() {
alertIfApplyNeeded(true);
@@ -326,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;
@@ -343,12 +353,14 @@ public class AbacusController implements PluginListener {
numberImplementationBox.getSelectionModel().select(toSelect);
for (Class<?> pluginClass : abacus.getPluginManager().getLoadedPluginClasses()) {
String fullName = pluginClass.getName();
ToggleablePlugin plugin = new ToggleablePlugin(!disabledPlugins.contains(fullName), fullName);
plugin.enabledProperty().addListener(e -> changesMade = true);
ToggleablePlugin plugin = new ToggleablePlugin(fullName, !disabledPlugins.contains(fullName));
plugin.getEnabledProperty().addListener(e -> changesMade = true);
enabledPlugins.add(plugin);
}
functionList.addAll(manager.getAllFunctions());
functionList.sort(String::compareTo);
PluginManager pluginManager = abacus.getPluginManager();
functionList.addAll(manager.getAllFunctions().stream().map(name -> pluginManager.documentationFor(name, DocumentationType.FUNCTION))
.collect(Collectors.toCollection(ArrayList::new)));
functionList.sort(Comparator.comparing(Documentation::getCodeName));
}
@Override

View File

@@ -0,0 +1,59 @@
package org.nwapw.abacus.fx;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.VBox;
import org.nwapw.abacus.function.Documentation;
public class DocumentationCell extends ListCell<Documentation> {
private Label codeNameLabel;
private Label nameLabel;
private Label description;
private Label longDescription;
private TitledPane titledPane;
public DocumentationCell() {
VBox vbox = new VBox();
vbox.setSpacing(10);
titledPane = new TitledPane();
codeNameLabel = new Label();
nameLabel = new Label();
description = new Label();
longDescription = new Label();
codeNameLabel.setWrapText(true);
nameLabel.setWrapText(true);
description.setWrapText(true);
longDescription.setWrapText(true);
vbox.getChildren().add(codeNameLabel);
vbox.getChildren().add(nameLabel);
vbox.getChildren().add(description);
vbox.getChildren().add(longDescription);
titledPane.textProperty().bindBidirectional(codeNameLabel.textProperty());
titledPane.setContent(vbox);
titledPane.setExpanded(false);
titledPane.prefWidthProperty().bind(widthProperty().subtract(32));
visibleProperty().addListener((a, b, c) -> titledPane.setExpanded(false));
}
@Override
protected void updateItem(Documentation item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
codeNameLabel.setText("");
nameLabel.setText("");
description.setText("");
longDescription.setText("");
setGraphic(null);
} else {
codeNameLabel.setText(item.getCodeName());
nameLabel.setText(item.getName());
description.setText(item.getDescription());
longDescription.setText(item.getLongDescription());
setGraphic(titledPane);
}
titledPane.setExpanded(false);
}
}

View File

@@ -0,0 +1,32 @@
package org.nwapw.abacus.fx
import javafx.beans.property.SimpleStringProperty
/**
* A model representing an input / output in the calculator.
*
* The HistoryModel class stores a record of a single user-provided input,
* its parsed form as it was interpreted by the calculator, and the output
* that was provided by the calculator. These are represented as properties
* to allow easy access by JavaFX cells.
*
* @param input the user input
* @param parsed the parsed version of the input.
* @param output the output string.
*/
class HistoryModel(input: String, parsed: String, output: String) {
/**
* The property that holds the input.
*/
val inputProperty = SimpleStringProperty(input)
/**
* The property that holds the parsed input.
*/
val parsedProperty = SimpleStringProperty(parsed)
/**
* The property that holds the output.
*/
val outputProperty = SimpleStringProperty(output)
}

View File

@@ -0,0 +1,31 @@
package org.nwapw.abacus.fx
import javafx.beans.property.SimpleBooleanProperty
/**
* A model representing a plugin that can be disabled or enabled.
*
* ToggleablePlugin is a model that is used to present to the user the option
* of disabling / enabling plugins. The class name in this plugin is stored if
* its "enabledPropery" is false, essentially blacklisting the plugin.
*
* @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) {
/**
* The property used to interact with JavaFX components.
*/
val enabledProperty = SimpleBooleanProperty(enabled)
/**
* Checks whether this plugin is currently enabled or not.
*
* @return true if it is enabled, false otherwise.
*/
fun isEnabled(): Boolean {
return enabledProperty.value
}
}

View File

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

View File

@@ -1,97 +0,0 @@
package org.nwapw.abacus.fx;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* The data model used for storing history entries.
*/
public class HistoryModel {
/**
* The property used for displaying the column
* for the user input.
*/
private final StringProperty input;
/**
* The property used for displaying the column
* that contains the parsed input.
*/
private final StringProperty parsed;
/**
* The property used for displaying the column
* that contains the program output.
*/
private final StringProperty output;
/**
* Creates a new history model with the given variables.
*
* @param input the user input
* @param parsed the parsed input
* @param output the program output.
*/
public HistoryModel(String input, String parsed, String output) {
this.input = new SimpleStringProperty();
this.parsed = new SimpleStringProperty();
this.output = new SimpleStringProperty();
this.input.setValue(input);
this.parsed.setValue(parsed);
this.output.setValue(output);
}
/**
* Gets the input property.
*
* @return the input property.
*/
public StringProperty inputProperty() {
return input;
}
/**
* Gets the input.
*
* @return the input.
*/
public String getInput() {
return input.get();
}
/**
* Gets the parsed input property.
*
* @return the parsed input property.
*/
public StringProperty parsedProperty() {
return parsed;
}
/**
* Gets the parsed input.
*
* @return the parsed input.
*/
public String getParsed() {
return parsed.get();
}
/**
* Gets the output property.
*
* @return the output property.
*/
public StringProperty outputProperty() {
return output;
}
/**
* Gets the program output.
*
* @return the output.
*/
public String getOutput() {
return output.get();
}
}

View File

@@ -1,60 +0,0 @@
package org.nwapw.abacus.fx;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
/**
* Class that represents an entry in the plugin check box list.
* The changes from this property are written to the config on application.
*/
public class ToggleablePlugin {
/**
* The property that determines whether the plugin will be enabled.
*/
private final BooleanProperty enabled;
/**
* The name of the class this entry toggles.
*/
private final String className;
/**
* Creates a new toggleable plugin with the given properties.
*
* @param enabled the enabled / disabled state at the beginning.
* @param className the name of the class this plugin toggles.
*/
public ToggleablePlugin(boolean enabled, String className) {
this.enabled = new SimpleBooleanProperty();
this.enabled.setValue(enabled);
this.className = className;
}
/**
* Gets the enabled property of this plugin.
*
* @return the enabled property.
*/
public BooleanProperty enabledProperty() {
return enabled;
}
/**
* Checks if this plugin entry should be enabled.
*
* @return whether this plugin will be enabled.
*/
public boolean isEnabled() {
return enabled.get();
}
/**
* Gets the class name this plugin toggles.
*
* @return the class name that should be disabled.
*/
public String getClassName() {
return className;
}
}

View File

@@ -1,19 +0,0 @@
package org.nwapw.abacus.tree;
/**
* Interface used to reduce a tree into a single value.
*
* @param <T> the value to reduce into.
*/
public interface Reducer<T> {
/**
* Reduces the given tree into a single value of type T.
*
* @param node the node being passed in to be reduced.
* @param children the already-reduced children of this node.
* @return the resulting value from the reduce.
*/
public T reduceNode(TreeNode node, Object... children);
}

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