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

Compare commits

...

99 Commits

Author SHA1 Message Date
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
ff7d90967e Fix toString errors failing unit tests. 2017-08-07 19:22:35 -07:00
355a91d690 Rewrite the Operator in Kotlin. 2017-08-07 19:16:51 -07:00
5f0fba15eb Add comments to the newly created Kotlin implementations. 2017-08-07 19:11:13 -07:00
3bdc0e2ae5 Rewrite tree nodes in Kotlin. Documentation pending. 2017-08-07 18:57:43 -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
e54b5cdd66 Add Kotlin support to gradle. 2017-08-07 17:33:56 -07:00
fd87cb66a3 Merge branch 'master' of github.com:DanilaFe/abacus 2017-08-07 14:27:29 -07:00
200f4c7288 Add a filter. 2017-08-07 14:12:41 -07:00
Arthur Drobot
1cd544e712 Merge branch 'master' of https://github.com/DanilaFe/abacus 2017-08-07 14:08:17 -07:00
Arthur Drobot
a8c70a6bbe Add checking of negative bases raised to non-integer powers in matchesParams in OP_CARET. 2017-08-07 14:05:15 -07:00
f28e915c9a Fix not clearing unloaded plugins. 2017-08-07 13:55:24 -07:00
7a0863380a Add a list of functions to the controller. 2017-08-07 13:55:13 -07:00
Arthur Drobot
8a9df051cf Optimize pow for integer bases. 2017-08-07 13:43:12 -07:00
Arthur Drobot
4eda15b3fb Modify some functions in PreciseNumber for consistency. 2017-08-07 13:41:45 -07:00
c9e0d4f8d3 Fix broken documentation,. 2017-08-07 11:18:01 -07:00
213d7af10b Merge branch 'remove-caching'
# Conflicts:
#	src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java
2017-08-07 11:14:11 -07:00
585a3839c1 Merge branch 'time-limit' 2017-08-07 11:12:12 -07:00
Arthur Drobot
d0ccb8b625 Improve some comments of NumberInterface functions. 2017-08-07 10:54:27 -07:00
Arthur Drobot
7d5efa1fe6 Add fromInt function into StandardPlugin and replace existing instantiations and promotions of NaiveNumbers with integer values with fromInt calls. 2017-08-07 10:33:16 -07:00
35254d3e99 Rename getPi to piFor. 2017-08-06 21:56:49 -07:00
44f018060d Remove the useless caching in the plugin manager and the maps in plugins 2017-08-06 21:55:09 -07:00
81 changed files with 1929 additions and 1800 deletions

View File

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

8
core/build.gradle Normal file
View File

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

View File

@@ -1,7 +1,6 @@
package org.nwapw.abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.fx.AbacusApplication;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.parsing.LexerTokenizer;
import org.nwapw.abacus.parsing.ShuntingYardParser;
@@ -61,10 +60,6 @@ public class Abacus {
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,7 @@
package org.nwapw.abacus.function;
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);
}
@@ -173,7 +174,7 @@ public abstract class NumberInterface {
/**
* Returns the least integer greater than or equal to the number.
*
* @return the least integer >= the number, if int can hold the value.
* @return the least integer greater or equal to the number, if int can hold the value.
*/
protected abstract NumberInterface ceilingInternal();
@@ -182,9 +183,9 @@ public abstract class NumberInterface {
* Also, checks if the thread has been interrupted, and if so, throws
* an exception.
*
* @return the least integer bigger or equal to the number, if int can hold the value.
* @return the least integer bigger or equal to the number.
*/
public final NumberInterface ceiling(){
public final NumberInterface ceiling() {
checkInterrupted();
return ceilingInternal();
}
@@ -192,7 +193,7 @@ public abstract class NumberInterface {
/**
* Return the greatest integer less than or equal to the number.
*
* @return the greatest integer smaller or equal the number, if int can hold the value.
* @return the greatest integer smaller or equal the number.
*/
protected abstract NumberInterface floorInternal();
@@ -201,9 +202,9 @@ public abstract class NumberInterface {
* Also, checks if the thread has been interrupted, and if so, throws
* an exception.
*
* @return the greatest int >= the number, if int can hold the value.
* @return the greatest int smaller than or equal to the number.
*/
public final NumberInterface floor(){
public final NumberInterface floor() {
checkInterrupted();
return floorInternal();
}
@@ -216,12 +217,13 @@ public abstract class NumberInterface {
protected abstract NumberInterface fractionalPartInternal();
/**
* Returns the fractional part of the number.
* 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
@@ -113,19 +128,18 @@ public class PreciseNumber extends NumberInterface {
String str = value.toPlainString();
int decimalIndex = str.indexOf('.');
if (decimalIndex != -1) {
return new PreciseNumber(str.substring(0, decimalIndex));
NumberInterface floor = new PreciseNumber(str.substring(0, decimalIndex));
if (signum() == -1) {
floor = floor.subtract(ONE);
}
return floor;
}
return this;
}
@Override
public NumberInterface fractionalPartInternal() {
String str = value.toPlainString();
int decimalIndex = str.indexOf('.');
if (decimalIndex != -1) {
return new PreciseNumber(str.substring(decimalIndex + 1));
}
return ZERO;
return this.subtractInternal(floorInternal());
}
@Override
@@ -148,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

@@ -162,7 +162,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
@@ -17,18 +15,6 @@ import java.util.Set;
*/
public abstract class Plugin {
/**
* A hash map of functions mapped to their string names.
*/
private Map<String, Function> functions;
/**
* A hash map of operators mapped to their string names.
*/
private Map<String, Operator> operators;
/**
* The map of the number implementations this plugin provides.
*/
private Map<String, NumberImplementation> numberImplementations;
/**
* The plugin manager in which to search for functions
* not inside this package,
@@ -49,69 +35,9 @@ public abstract class Plugin {
*/
public Plugin(PluginManager manager) {
this.manager = manager;
functions = new HashMap<>();
operators = new HashMap<>();
numberImplementations = new HashMap<>();
enabled = false;
}
/**
* Gets the list of functions provided by this plugin.
*
* @return the list of registered functions.
*/
public final Set<String> providedFunctions() {
return functions.keySet();
}
/**
* Gets the list of functions provided by this plugin.
*
* @return the list of registered functions.
*/
public final Set<String> providedOperators() {
return operators.keySet();
}
/**
* Gets the list of number implementations provided by this plugin.
*
* @return the list of registered number implementations.
*/
public final Set<String> providedNumberImplementations() {
return numberImplementations.keySet();
}
/**
* Gets a function under the given function name.
*
* @param functionName the name of the function to get
* @return the function, or null if this plugin doesn't provide it.
*/
public final Function getFunction(String functionName) {
return functions.get(functionName);
}
/**
* Gets an operator under the given operator name.
*
* @param operatorName the name of the operator to get.
* @return the operator, or null if this plugin doesn't provide it.
*/
public final Operator getOperator(String operatorName) {
return operators.get(operatorName);
}
/**
* Gets the number implementation under the given name.
*
* @param name the name of the number implementation to look up.
* @return the number implementation associated with that name, or null if the plugin doesn't provide it.
*/
public final NumberImplementation getNumberImplementation(String name) {
return numberImplementations.get(name);
}
/**
* Enables the function, loading the necessary instances
* of functions.
@@ -129,8 +55,6 @@ public abstract class Plugin {
public final void disable() {
if (!enabled) return;
onDisable();
functions.clear();
operators.clear();
enabled = false;
}
@@ -142,7 +66,7 @@ public abstract class Plugin {
* @param toRegister the function implementation.
*/
protected final void registerFunction(String name, Function toRegister) {
functions.put(name, toRegister);
manager.registerFunction(name, toRegister);
}
/**
@@ -154,7 +78,7 @@ public abstract class Plugin {
* @param operator the operator to register.
*/
protected final void registerOperator(String name, Operator operator) {
operators.put(name, operator);
manager.registerOperator(name, operator);
}
/**
@@ -165,7 +89,17 @@ public abstract class Plugin {
* @param implementation the actual implementation class to register.
*/
protected final void registerNumberImplementation(String name, NumberImplementation implementation) {
numberImplementations.put(name, implementation);
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);
}
/**
@@ -204,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
@@ -213,7 +158,7 @@ public abstract class Plugin {
* @param forClass the class to which to find the pi instance.
* @return the pi value for the given class.
*/
protected final NumberInterface getPi(Class<? extends NumberInterface> forClass) {
protected final NumberInterface piFor(Class<? extends NumberInterface> forClass) {
return manager.piFor(forClass);
}

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
@@ -23,20 +28,21 @@ public class PluginManager {
*/
private Set<Plugin> plugins;
/**
* List of functions that have been cached,
* that is, found in a plugin and returned.
* The map of functions registered by the plugins.
*/
private Map<String, Function> cachedFunctions;
private Map<String, Function> registeredFunctions;
/**
* List of operators that have been cached,
* that is, found in a plugin and returned.
* The map of operators registered by the plugins
*/
private Map<String, Operator> cachedOperators;
private Map<String, Operator> registeredOperators;
/**
* The list of number implementations that have
* been cached, that is, found in a plugin and returned.
* The map of number implementations registered by the plugins.
*/
private Map<String, NumberImplementation> cachedNumberImplementations;
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.
@@ -46,18 +52,6 @@ public class PluginManager {
* The pi values for each implementation class that have already been computer.
*/
private Map<Class<? extends NumberInterface>, NumberInterface> cachedPi;
/**
* List of all functions loaded by the plugins.
*/
private Set<String> allFunctions;
/**
* List of all operators loaded by the plugins.
*/
private Set<String> allOperators;
/**
* List of all the number implementations loaded by the plugins.
*/
private Set<String> allNumberImplementations;
/**
* The list of plugin listeners attached to this instance.
*/
@@ -77,79 +71,105 @@ public class PluginManager {
this.abacus = abacus;
loadedPluginClasses = new HashSet<>();
plugins = new HashSet<>();
cachedFunctions = new HashMap<>();
cachedOperators = new HashMap<>();
cachedNumberImplementations = new HashMap<>();
registeredFunctions = new HashMap<>();
registeredOperators = new HashMap<>();
registeredNumberImplementations = new HashMap<>();
registeredDocumentation = new HashSet<>();
cachedInterfaceImplementations = new HashMap<>();
cachedPi = new HashMap<>();
allFunctions = new HashSet<>();
allOperators = new HashSet<>();
allNumberImplementations = new HashSet<>();
listeners = new HashSet<>();
}
/**
* Searches the plugin list for a certain value, retrieving the Plugin's
* list of items of the type using the setFunction and getting the value
* of it is available via getFunction. If the value is contained
* in the cache, it returns the cached value instead.
* Registers a function under the given name.
*
* @param plugins the plugin list to search.
* @param cache the cache to use
* @param setFunction the function to retrieve a set of available T's from the plugin
* @param getFunction the function to get the T value under the given name
* @param name the name to search for
* @param <T> the type of element being search
* @param <K> the type of key that the cache is indexed by.
* @return the retrieved element, or null if it was not found.
* @param name the name of the function.
* @param function the function to register.
*/
private static <T, K> T searchCached(Collection<Plugin> plugins, Map<K, T> cache,
java.util.function.Function<Plugin, Set<K>> setFunction,
java.util.function.BiFunction<Plugin, K, T> getFunction,
K name) {
if (cache.containsKey(name)) return cache.get(name);
public void registerFunction(String name, Function function) {
registeredFunctions.put(name, function);
}
T loadedValue = null;
for (Plugin plugin : plugins) {
if (setFunction.apply(plugin).contains(name)) {
loadedValue = getFunction.apply(plugin, name);
/**
* Registers an operator under the given name.
*
* @param name the name of the operator.
* @param operator the operator to register.
*/
public void registerOperator(String name, Operator operator) {
registeredOperators.put(name, operator);
}
/**
* Registers a number implementation under the given name.
*
* @param name the name of the number implementation.
* @param implementation the number implementation to register.
*/
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) {
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) {
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) {
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;
}
}
cache.put(name, loadedValue);
return loadedValue;
}
/**
* Gets a function under the given name.
*
* @param name the name of the function
* @return the function under the given name.
*/
public Function functionFor(String name) {
return searchCached(plugins, cachedFunctions, Plugin::providedFunctions, Plugin::getFunction, name);
}
/**
* Gets an operator under the given name.
*
* @param name the name of the operator.
* @return the operator under the given name.
*/
public Operator operatorFor(String name) {
return searchCached(plugins, cachedOperators, Plugin::providedOperators, Plugin::getOperator, name);
}
/**
* Gets the number implementation under the given name.
*
* @param name the name of the implementation.
* @return the implementation.
*/
public NumberImplementation numberImplementationFor(String name) {
return searchCached(plugins, cachedNumberImplementations, Plugin::providedNumberImplementations,
Plugin::getNumberImplementation, name);
if (toReturn == null) {
toReturn = new Documentation(name, "", "", "", type);
registerDocumentation(toReturn);
}
return toReturn;
}
/**
@@ -161,14 +181,11 @@ public class PluginManager {
public NumberImplementation interfaceImplementationFor(Class<? extends NumberInterface> name) {
if (cachedInterfaceImplementations.containsKey(name)) return cachedInterfaceImplementations.get(name);
NumberImplementation toReturn = null;
outside:
for (Plugin plugin : plugins) {
for (String implementationName : plugin.providedNumberImplementations()) {
NumberImplementation implementation = plugin.getNumberImplementation(implementationName);
if (implementation.getImplementation().equals(name)) {
toReturn = implementation;
break outside;
}
for (String key : registeredNumberImplementations.keySet()) {
NumberImplementation implementation = registeredNumberImplementations.get(key);
if (implementation.getImplementation() == name) {
toReturn = implementation;
break;
}
}
cachedInterfaceImplementations.put(name, toReturn);
@@ -218,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.
*/
@@ -227,12 +263,6 @@ public class PluginManager {
if (disabledPlugins.contains(plugin.getClass().getName())) continue;
plugin.enable();
}
for (Plugin plugin : plugins) {
if (disabledPlugins.contains(plugin.getClass().getName())) continue;
allFunctions.addAll(plugin.providedFunctions());
allOperators.addAll(plugin.providedOperators());
allNumberImplementations.addAll(plugin.providedNumberImplementations());
}
listeners.forEach(e -> e.onLoad(this));
}
@@ -246,14 +276,12 @@ public class PluginManager {
if (disabledPlugins.contains(plugin.getClass().getName())) continue;
plugin.disable();
}
cachedFunctions.clear();
cachedOperators.clear();
cachedNumberImplementations.clear();
registeredFunctions.clear();
registeredOperators.clear();
registeredNumberImplementations.clear();
registeredDocumentation.clear();
cachedInterfaceImplementations.clear();
cachedPi.clear();
allFunctions.clear();
allOperators.clear();
allNumberImplementations.clear();
listeners.forEach(e -> e.onUnload(this));
}
@@ -271,7 +299,7 @@ public class PluginManager {
* @return the set of all functions that were loaded.
*/
public Set<String> getAllFunctions() {
return allFunctions;
return registeredFunctions.keySet();
}
/**
@@ -280,7 +308,7 @@ public class PluginManager {
* @return the set of all operators that were loaded.
*/
public Set<String> getAllOperators() {
return allOperators;
return registeredOperators.keySet();
}
/**
@@ -289,7 +317,7 @@ public class PluginManager {
* @return the set of all implementations that were loaded.
*/
public Set<String> getAllNumberImplementations() {
return allNumberImplementations;
return registeredNumberImplementations.keySet();
}
/**

View File

@@ -0,0 +1,837 @@
package org.nwapw.abacus.plugin;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.function.BiFunction;
/**
* The plugin providing standard functions such as addition and subtraction to
* the calculator.
*/
public class StandardPlugin extends Plugin {
/**
* The addition operator, +
*/
public static final Operator OP_ADD = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length >= 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface sum = params[0];
for (int i = 1; i < params.length; i++) {
sum = sum.add(params[i]);
}
return sum;
}
});
/**
* The subtraction operator, -
*/
public static final Operator OP_SUBTRACT = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].subtract(params[1]);
}
});
/**
* The negation operator, -
*/
public static final Operator OP_NEGATE = new Operator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].negate();
}
});
/**
* The multiplication operator, *
*/
public static final Operator OP_MULTIPLY = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length >= 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface product = params[0];
for (int i = 1; i < params.length; i++) {
product = product.multiply(params[i]);
}
return product;
}
});
/**
* The combination operator.
*/
public static final Operator OP_NCR = new Operator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2 && params[0].fractionalPart().signum() == 0
&& params[1].fractionalPart().signum() == 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return OP_NPR.getFunction().apply(params).divide(OP_FACTORIAL.getFunction().apply(params[1]));
}
});
/**
* The implementation for double-based naive numbers.
*/
public static final NumberImplementation IMPLEMENTATION_NAIVE = new NumberImplementation(NaiveNumber.class, 0) {
@Override
public NumberInterface instanceForString(String string) {
return new NaiveNumber(string);
}
@Override
public NumberInterface instanceForPi() {
return new NaiveNumber(Math.PI);
}
};
/**
* The implementation for the infinite-precision BigDecimal.
*/
public static final NumberImplementation IMPLEMENTATION_PRECISE = new NumberImplementation(PreciseNumber.class, 0) {
@Override
public NumberInterface instanceForString(String string) {
return new PreciseNumber(string);
}
@Override
public NumberInterface instanceForPi() {
NumberInterface C = FUNCTION_SQRT.apply(new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
NumberInterface M = PreciseNumber.ONE;
NumberInterface L = new PreciseNumber("13591409");
NumberInterface X = M;
NumberInterface sum = L;
int termsNeeded = C.getMaxPrecision() / 13 + 1;
NumberInterface lSummand = new PreciseNumber("545140134");
NumberInterface xMultiplier = new PreciseNumber("262537412")
.multiply(new PreciseNumber("1000000000"))
.add(new PreciseNumber("640768000"))
.negate();
for (int i = 0; i < termsNeeded; i++) {
M = M
.multiply(new PreciseNumber((12 * i + 2) + ""))
.multiply(new PreciseNumber((12 * i + 6) + ""))
.multiply(new PreciseNumber((12 * i + 10) + ""))
.divide(new PreciseNumber(Math.pow(i + 1, 3) + ""));
L = L.add(lSummand);
X = X.multiply(xMultiplier);
sum = sum.add(M.multiply(L).divide(X));
}
return C.divide(sum);
}
};
/**
* Stores objects of NumberInterface with integer values for reuse.
*/
private final static HashMap<Class<? extends NumberInterface>, HashMap<Integer, NumberInterface>> integerValues = new HashMap<>();
/**
* The division operator, /
*/
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...
*/
public static final Function FUNCTION_EXP = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface maxError = params[0].getMaxError();
int n = 0;
if (params[0].signum() < 0) {
NumberInterface[] negatedParams = {params[0].negate()};
return fromInt(params[0].getClass(), 1).divide(applyInternal(negatedParams));
} else {
//We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!.
//right and left refer to lhs and rhs in the above inequality.
NumberInterface sum = fromInt(params[0].getClass(), 1);
NumberInterface nextNumerator = params[0];
NumberInterface left = params[0].multiply(fromInt(params[0].getClass(), 3).intPow(params[0].ceiling().intValue())), right = maxError;
do {
sum = sum.add(nextNumerator.divide(factorial(params[0].getClass(), n + 1)));
n++;
nextNumerator = nextNumerator.multiply(params[0]);
left = left.multiply(params[0]);
NumberInterface nextN = fromInt(params[0].getClass(), n + 1);
right = right.multiply(nextN);
//System.out.println(left + ", " + right);
}
while (left.compareTo(right) > 0);
//System.out.println(n+1);
return sum;
}
}
};
/**
* The sine function (the argument is interpreted in radians).
*/
public final Function functionSin = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface pi = piFor(params[0].getClass());
NumberInterface twoPi = pi.multiply(fromInt(pi.getClass(), 2));
NumberInterface theta = getSmallAngle(params[0], pi);
//System.out.println(theta);
if (theta.compareTo(pi.multiply(new NaiveNumber(1.5).promoteTo(twoPi.getClass()))) >= 0) {
theta = theta.subtract(twoPi);
} else if (theta.compareTo(pi.divide(fromInt(pi.getClass(), 2))) > 0) {
theta = pi.subtract(theta);
}
//System.out.println(theta);
return sinTaylor(theta);
}
};
/**
* The cosine function (the argument is in radians).
*/
public final Function functionCos = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(params[0]));
}
};
/**
* The tangent function (the argument is in radians).
*/
public final Function functionTan = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(params[0]).divide(functionCos.apply(params[0]));
}
};
/**
* The secant function (the argument is in radians).
*/
public final Function functionSec = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return fromInt(params[0].getClass(), 1).divide(functionCos.apply(params[0]));
}
};
/**
* The cosecant function (the argument is in radians).
*/
public final Function functionCsc = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return fromInt(params[0].getClass(), 1).divide(functionSin.apply(params[0]));
}
};
/**
* The cotangent function (the argument is in radians).
*/
public final Function functionCot = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionCos.apply(params[0]).divide(functionSin.apply(params[0]));
}
};
/**
* 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);
}
/**
* Returns a partial sum of a series whose terms are given by the nthTermFunction, evaluated at x.
*
* @param x the value at which the series is evaluated.
* @param nthTermFunction the function that returns the nth term of the series, in the format term(x, n).
* @param n the number of terms in the partial sum.
* @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 = fromInt(x.getClass(), 0);
for (int i = 0; i <= n; i++) {
sum = sum.add(nthTermFunction.apply(i, x));
}
return sum;
}
/**
* A factorial function that uses memoization for each number class; it efficiently
* computes factorials of non-negative integers.
*
* @param numberClass type of number to return.
* @param n non-negative integer.
* @return a number of numClass with value n factorial.
*/
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(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(fromInt(numberClass, list.size())));
}
}
return list.get(n);
}
/**
* Returns the value of the Taylor series for sin (centered at 0) at x.
*
* @param x where the series is evaluated.
* @return the value of the series
*/
private static NumberInterface sinTaylor(NumberInterface x) {
NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x;
NumberInterface maxError = x.getMaxError();
int n = 1;
do {
n += 2;
power = power.multiply(multiplier);
currentTerm = power.divide(factorial(x.getClass(), n));
sum = sum.add(currentTerm);
} while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0);
return sum;
}
/**
* Returns an equivalent angle in the interval [0, 2pi)
*
* @param phi an angle (in radians).
* @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(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) {
theta = twoPi.subtract(theta);
}
return theta;
}
/**
* Returns a number of class numType with value n.
*
* @param numType class of number to return.
* @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)) {
integerValues.put(numType, new HashMap<>());
}
if (!integerValues.get(numType).containsKey(n)) {
integerValues.get(numType).put(n, new NaiveNumber(n).promoteTo(numType));
}
return integerValues.get(numType).get(n);
}
@Override
public void onEnable() {
registerNumberImplementation("naive", IMPLEMENTATION_NAIVE);
registerNumberImplementation("precise", IMPLEMENTATION_PRECISE);
registerOperator("+", OP_ADD);
registerOperator("-", OP_SUBTRACT);
registerOperator("`", OP_NEGATE);
registerOperator("*", OP_MULTIPLY);
registerOperator("/", OP_DIVIDE);
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
public void onDisable() {
}
}

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

@@ -0,0 +1,14 @@
package org.nwapw.abacus.function
/**
* A single operator that can be used by Abacus.
*
* This is a data class that holds the information about a single operator, such as a plus or minus.
*
* @param associativity the associativity of this operator, used for order of operations;.
* @param type the type of this operator, used for parsing (infix / prefix / postfix and binary / unary)
* @param precedence the precedence of this operator, used for order of operations.
* @param function the function this operator applies to its arguments.
*/
data class Operator(val associativity: OperatorAssociativity, val type: OperatorType,
val precedence: Int, val function: Function)

View File

@@ -0,0 +1,26 @@
package org.nwapw.abacus.tree
/**
* A tree node that holds a binary operation.
*
* This node represents any binary operation, such as binary infix or binary postfix. The only
* currently implemented into Abacus is binary infix, but that has more to do with the parser than
* this class, which doesn't care about the order that its operation and nodes were found in text.
*
* @param operation the operation this node performs on its children.
* @param left the left node.
* @param right the right node.
*/
data class BinaryNode(val operation: String, val left: TreeNode? = null, val right: TreeNode?) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
val leftReduce = left?.reduce(reducer) ?: return null
val rightReduce = right?.reduce(reducer) ?: return null
return reducer.reduceNode(this, leftReduce, rightReduce)
}
override fun toString(): String {
return "(" + (left?.toString() ?: "null") + operation + (right?.toString() ?: "null") + ")"
}
}

View File

@@ -0,0 +1,52 @@
package org.nwapw.abacus.tree
/**
* A tree node that holds a function call.
*
* The function call node can hold any number of children, and passes the to the appropriate reducer,
* but that is its sole purpose.
*
* @param function the function string.
*/
data class FunctionNode(val function: String) : TreeNode() {
/**
* List of function parameters added to this node.
*/
val children: MutableList<TreeNode> = mutableListOf()
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
val children = Array<Any>(children.size, { children[it].reduce(reducer) ?: return null; })
return reducer.reduceNode(this, *children)
}
override fun toString(): String {
val buffer = StringBuffer()
buffer.append(function)
buffer.append('(')
for (i in 0 until children.size) {
buffer.append(children[i].toString())
buffer.append(if (i == children.size - 1) ")" else ",")
}
return buffer.toString()
}
/**
* Appends a child to this node's list of children.
*
* @node the node to append.
*/
fun appendChild(node: TreeNode) {
children.add(node)
}
/**
* Prepends a child to this node's list of children.
*
* @node the node to prepend.
*/
fun prependChild(node: TreeNode) {
children.add(0, node)
}
}

View File

@@ -0,0 +1,23 @@
package org.nwapw.abacus.tree
import org.nwapw.abacus.number.NumberInterface
/**
* A tree node that holds a single number value.
*
* This is a tree node that holds a single NumberInterface, which represents any number,
* and is not defined during compile time.
*
* @number the number value of this node.
*/
data class NumberNode(val number: NumberInterface) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
return reducer.reduceNode(this)
}
override fun toString(): String {
return number.toString()
}
}

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

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

View File

@@ -0,0 +1,23 @@
package org.nwapw.abacus.tree
/**
* A tree node that holds a unary operation.
*
* This node holds a single operator applied to a single parameter, and does not care
* whether the operation was found before or after the parameter in the text.
*
* @param operation the operation applied to the given node.
* @param applyTo the node to which the operation will be applied.
*/
data class UnaryNode(val operation: String, val applyTo: TreeNode? = null) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
val reducedChild = applyTo?.reduce(reducer) ?: return null
return reducer.reduceNode(this, reducedChild)
}
override fun toString(): String {
return "(" + (applyTo?.toString() ?: "null") + ")" + operation
}
}

View File

@@ -57,9 +57,19 @@
<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>
<Tab fx:id="functionListTab" text="Functions" closable="false">
<VBox spacing="10">
<padding>
<Insets left="10" right="10" top="10" bottom="10"/>
</padding>
<TextField fx:id="functionListSearchField" maxWidth="Infinity"/>
<ListView maxWidth="Infinity" fx:id="functionListView"/>
</VBox>
</Tab>
</TabPane>
</center>

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)`^0.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>

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

@@ -0,0 +1,44 @@
<!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;
}
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 }}

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

@@ -0,0 +1,10 @@
---
in_header: true
layout: page
title: About
permalink: /about/
---
Welcome to the Abacus project website.
This website is currently under construction, please, check
back later.

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.

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

@@ -0,0 +1,118 @@
---
---
@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 {
background-color: $background-color;
color: $clear-color;
padding: 10px;
text-decoration: none;
border-radius: 2px;
margin: 10px;
transition: background-color .25s;
&:hover {
background-color: $clear-color;
color: $background-color;
}
&.inverted {
background-color: $clear-color;
color: $background-color;
&:hover {
background-color: $background-color;
color: $clear-color;
}
}
}
}
h1, h2, h3, h4, h5, h6 {
font: {
family: $title-font;
}
}
h1 {
font-size: 50px;
}
nav {
box-sizing: border-box;
background-color: $clear-color;
width: 100%;
padding: 20px;
a {
text-decoration: none;
color: $background-color;
font-size: 20px;
margin-right: 10px;
&.primary-link {
font-size: 30px;
margin-right: 20px;
}
&:hover {
color: $accent-color;
}
transition: color .25s;
}
}
body {
background-color: $background-color;
font: {
family: $text-font;
}
margin: 0px;
}
.center {
box-sizing: border-box;
width: 100%;
max-width: $max-width;
@media (min-width: $max-width) {
margin: {
left: auto;
right: auto;
}
}
}
.content {
margin-top: 20px;
padding: 30px;
background-color: $clear-color;
}
code {
background-color: $code-color;
display: inline-block;
padding: 5px;
font-family: $code-font;
pre & {
padding: 10px;
display: block;
width: 100%;
}
}

6
docs/index.md Normal file
View File

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

10
fx/build.gradle Normal file
View File

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

View File

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

View File

@@ -1,10 +1,9 @@
package org.nwapw.abacus.fx;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxListCell;
@@ -13,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;
@@ -23,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;
/**
@@ -72,6 +76,8 @@ public class AbacusController implements PluginListener {
@FXML
private Tab settingsTab;
@FXML
private Tab functionListTab;
@FXML
private TableView<HistoryModel> historyTable;
@FXML
private TableColumn<HistoryModel, String> inputColumn;
@@ -93,6 +99,10 @@ public class AbacusController implements PluginListener {
private ListView<ToggleablePlugin> enabledPluginView;
@FXML
private TextField computationLimitField;
@FXML
private ListView<Documentation> functionListView;
@FXML
private TextField functionListSearchField;
/**
* The list of history entries, created by the users.
@@ -110,41 +120,25 @@ public class AbacusController implements PluginListener {
* and, when reloaded, get added to the plugin manager's black list.
*/
private ObservableList<ToggleablePlugin> enabledPlugins;
/**
* The list of functions that are registered in the calculator.
*/
private ObservableList<Documentation> functionList;
/**
* The filtered list displayed to the user.
*/
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) {
@@ -159,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;
}
@@ -176,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.
*/
@@ -184,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
@@ -201,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);
@@ -209,10 +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.matches(newValue))));
functionListView.setCellFactory(param -> new DocumentationCell());
historyData = FXCollections.observableArrayList();
historyTable.setItems(historyData);
numberImplementationOptions = FXCollections.observableArrayList();
@@ -223,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);
});
@@ -235,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;
@@ -272,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;
}
@@ -291,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);
@@ -306,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;
@@ -323,14 +353,19 @@ 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);
}
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
public void onUnload(PluginManager manager) {
functionList.clear();
enabledPlugins.clear();
numberImplementationOptions.clear();
}

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,76 +0,0 @@
package org.nwapw.abacus.function;
/**
* A class that represents a single infix operator.
*/
public class Operator {
/**
* The associativity of the operator.
*/
private OperatorAssociativity associativity;
/**
* The type of this operator.
*/
private OperatorType type;
/**
* The precedence of the operator.
*/
private int precedence;
/**
* The function that is called by this operator.
*/
private Function function;
/**
* Creates a new operator with the given parameters.
*
* @param associativity the associativity of the operator.
* @param operatorType the type of this operator, like binary infix or unary postfix.
* @param precedence the precedence of the operator.
* @param function the function that the operator calls.
*/
public Operator(OperatorAssociativity associativity, OperatorType operatorType, int precedence, Function function) {
this.associativity = associativity;
this.type = operatorType;
this.precedence = precedence;
this.function = function;
}
/**
* Gets the operator's associativity.
*
* @return the associativity.
*/
public OperatorAssociativity getAssociativity() {
return associativity;
}
/**
* Gets the operator's type.
*
* @return the type.
*/
public OperatorType getType() {
return type;
}
/**
* Gets the operator's precedence.
*
* @return the precedence.
*/
public int getPrecedence() {
return precedence;
}
/**
* Gets the operator's function.
*
* @return the function.
*/
public Function getFunction() {
return function;
}
}

View File

@@ -1,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,563 +0,0 @@
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.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.function.BiFunction;
/**
* The plugin providing standard functions such as addition and subtraction to
* the calculator.
*/
public class StandardPlugin extends Plugin {
/**
* The addition operator, +
*/
public static final Operator OP_ADD = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length >= 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface sum = params[0];
for (int i = 1; i < params.length; i++) {
sum = sum.add(params[i]);
}
return sum;
}
});
/**
* The subtraction operator, -
*/
public static final Operator OP_SUBTRACT = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].subtract(params[1]);
}
});
/**
* The negation operator, -
*/
public static final Operator OP_NEGATE = new Operator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].negate();
}
});
/**
* The multiplication operator, *
*/
public static final Operator OP_MULTIPLY = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length >= 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface product = params[0];
for (int i = 1; i < params.length; i++) {
product = product.multiply(params[i]);
}
return product;
}
});
/**
* 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(NaiveNumber.ZERO.promoteTo(params[1].getClass())) != 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(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0
&& params[0].signum() >= 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
if (params[0].signum() == 0) {
return (new NaiveNumber(1)).promoteTo(params[0].getClass());
}
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(new NaiveNumber(2).promoteTo(param.getClass()));
powersOf2++;
if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() != 1) {
break;
//No infinite loop for you.
}
} else {
param = param.multiply(new NaiveNumber(2).promoteTo(param.getClass()));
powersOf2--;
if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() != -1) {
break;
//No infinite loop for you.
}
}
}
return getLog2(param).multiply((new NaiveNumber(powersOf2)).promoteTo(param.getClass())).add(getLogPartialSum(param));
}
/**
* Returns the partial sum of the Taylor series for logx (around x=1).
* Automatically determines the number of terms needed based on the precision of x.
* @param x value at which the series is evaluated. 0 < x < 2. (x=2 is convergent but impractical.)
* @return the partial sum.
*/
private NumberInterface getLogPartialSum(NumberInterface x) {
NumberInterface maxError = 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 = (new NaiveNumber(1)).promoteTo(number.getClass());
//We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n)
//In the following, a=1/3^n, b=1/4^n, c = 1/n.
//a is also an error bound.
NumberInterface a = (new NaiveNumber(1)).promoteTo(number.getClass()), b = a, c = a;
NumberInterface sum = NaiveNumber.ZERO.promoteTo(number.getClass());
int n = 0;
while (a.compareTo(maxError) >= 1) {
n++;
a = a.divide((new NaiveNumber(3)).promoteTo(number.getClass()));
b = b.divide((new NaiveNumber(4)).promoteTo(number.getClass()));
c = NaiveNumber.ONE.promoteTo(number.getClass()).divide((new NaiveNumber(n)).promoteTo(number.getClass()));
sum = sum.add(a.add(b).multiply(c));
}
return sum;
}
};
/**
* 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.
*/
public static final NumberImplementation IMPLEMENTATION_NAIVE = new NumberImplementation(NaiveNumber.class, 0) {
@Override
public NumberInterface instanceForString(String string) {
return new NaiveNumber(string);
}
@Override
public NumberInterface instanceForPi() {
return new NaiveNumber(Math.PI);
}
};
/**
* The implementation for the infinite-precision BigDecimal.
*/
public static final NumberImplementation IMPLEMENTATION_PRECISE = new NumberImplementation(PreciseNumber.class, 0) {
@Override
public NumberInterface instanceForString(String string) {
return new PreciseNumber(string);
}
@Override
public NumberInterface instanceForPi() {
NumberInterface C = FUNCTION_SQRT.apply(new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
NumberInterface M = PreciseNumber.ONE;
NumberInterface L = new PreciseNumber("13591409");
NumberInterface X = M;
NumberInterface sum = L;
int termsNeeded = C.getMaxPrecision() / 13 + 1;
NumberInterface lSummand = new PreciseNumber("545140134");
NumberInterface xMultiplier = new PreciseNumber("262537412")
.multiply(new PreciseNumber("1000000000"))
.add(new PreciseNumber("640768000"))
.negate();
for (int i = 0; i < termsNeeded; i++) {
M = M
.multiply(new 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));
L = L.add(lSummand);
X = X.multiply(xMultiplier);
sum = sum.add(M.multiply(L).divide(X));
}
return C.divide(sum);
}
};
private static final HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
/**
* The exponential function, exp(1) = e^1 = 2.71...
*/
public static final Function FUNCTION_EXP = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface maxError = getMaxError(params[0]);
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;
} 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 nextNumerator = params[0];
NumberInterface left = params[0].multiply((new NaiveNumber(3)).promoteTo(params[0].getClass()).intPow(params[0].ceiling().intValue())), right = maxError;
do {
sum = sum.add(nextNumerator.divide(factorial(params[0].getClass(), n + 1)));
n++;
nextNumerator = nextNumerator.multiply(params[0]);
left = left.multiply(params[0]);
NumberInterface nextN = (new NaiveNumber(n + 1)).promoteTo(params[0].getClass());
right = right.multiply(nextN);
//System.out.println(left + ", " + right);
}
while (left.compareTo(right) > 0);
//System.out.println(n+1);
return sum;
}
}
};
/**
* The caret / pow operator, ^
*/
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);
}
@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());
return FUNCTION_EXP.apply(FUNCTION_LN.apply(FUNCTION_ABS.apply(params[0])).multiply(params[1]));
}
});
/**
* The sine function (the argument is interpreted in radians).
*/
public final Function functionSin = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface pi = getPi(params[0].getClass());
NumberInterface twoPi = pi.multiply(new NaiveNumber(2).promoteTo(pi.getClass()));
NumberInterface theta = getSmallAngle(params[0], pi);
//System.out.println(theta);
if (theta.compareTo(pi.multiply(new NaiveNumber(1.5).promoteTo(twoPi.getClass()))) >= 0) {
theta = theta.subtract(twoPi);
} else if (theta.compareTo(pi.divide(new NaiveNumber(2).promoteTo(pi.getClass()))) > 0) {
theta = pi.subtract(theta);
}
//System.out.println(theta);
return sinTaylor(theta);
}
};
/**
* The cosine function (the argument is in radians).
*/
public final Function functionCos = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(getPi(params[0].getClass()).divide(new NaiveNumber(2).promoteTo(params[0].getClass()))
.subtract(params[0]));
}
};
/**
* The tangent function (the argument is in radians).
*/
public final Function functionTan = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(params[0]).divide(functionCos.apply(params[0]));
}
};
/**
* The secant function (the argument is in radians).
*/
public final Function functionSec = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return NaiveNumber.ONE.promoteTo(params[0].getClass()).divide(functionCos.apply(params[0]));
}
};
/**
* The cosecant function (the argument is in radians).
*/
public final Function functionCsc = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return NaiveNumber.ONE.promoteTo(params[0].getClass()).divide(functionSin.apply(params[0]));
}
};
/**
* The cotangent function (the argument is in radians).
*/
public final Function functionCot = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionCos.apply(params[0]).divide(functionSin.apply(params[0]));
}
};
public StandardPlugin(PluginManager manager) {
super(manager);
}
/**
* Returns a partial sum of a series whose terms are given by the nthTermFunction, evaluated at x.
*
* @param x the value at which the series is evaluated.
* @param nthTermFunction the function that returns the nth term of the series, in the format term(x, n).
* @param n the number of terms in the partial sum.
* @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());
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 (new NaiveNumber(10)).promoteTo(number.getClass()).intPow(-number.getMaxPrecision());
}
/**
* A factorial function that uses memoization for each number class; it efficiently
* computes factorials of non-negative integers.
*
* @param numberClass type of number to return.
* @param n non-negative integer.
* @return a number of numClass with value n factorial.
*/
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));
}
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)));
}
}
return list.get(n);
}
/**
* Returns the value of the Taylor series for sin (centered at 0) at x.
*
* @param x where the series is evaluated.
* @return the value of the series
*/
private static NumberInterface sinTaylor(NumberInterface x) {
NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x;
NumberInterface maxError = getMaxError(x);
int n = 1;
do {
n += 2;
power = power.multiply(multiplier);
currentTerm = power.divide(factorial(x.getClass(), n));
sum = sum.add(currentTerm);
} while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0);
return sum;
}
/**
* Returns an equivalent angle in the interval [0, 2pi)
*
* @param phi an angle (in radians).
* @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 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) {
theta = twoPi.subtract(theta);
}
return theta;
}
@Override
public void onEnable() {
registerNumberImplementation("naive", IMPLEMENTATION_NAIVE);
registerNumberImplementation("precise", IMPLEMENTATION_PRECISE);
registerOperator("+", OP_ADD);
registerOperator("-", OP_SUBTRACT);
registerOperator("`", OP_NEGATE);
registerOperator("*", OP_MULTIPLY);
registerOperator("/", OP_DIVIDE);
registerOperator("^", OP_CARET);
registerOperator("!", OP_FACTORIAL);
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);
}
@Override
public void onDisable() {
}
}

View File

@@ -1,108 +0,0 @@
package org.nwapw.abacus.tree;
/**
* A tree node that represents an operation being applied to two operands.
*/
public class BinaryNode extends TreeNode {
/**
* The operation being applied.
*/
private String operation;
/**
* The left node of the operation.
*/
private TreeNode left;
/**
* The right node of the operation.
*/
private TreeNode right;
private BinaryNode() {
}
/**
* Creates a new operation node with the given operation
* and no child nodes.
*
* @param operation the operation.
*/
public BinaryNode(String operation) {
this(operation, null, null);
}
/**
* Creates a new operation node with the given operation
* and child nodes.
*
* @param operation the operation.
* @param left the left node of the expression.
* @param right the right node of the expression.
*/
public BinaryNode(String operation, TreeNode left, TreeNode right) {
this.operation = operation;
this.left = left;
this.right = right;
}
/**
* Gets the operation in this node.
*
* @return the operation in this node.
*/
public String getOperation() {
return operation;
}
/**
* Gets the left sub-expression of this node.
*
* @return the left node.
*/
public TreeNode getLeft() {
return left;
}
/**
* Sets the left sub-expression of this node.
*
* @param left the sub-expression to apply.
*/
public void setLeft(TreeNode left) {
this.left = left;
}
/**
* Gets the right sub-expression of this node.
*
* @return the right node.
*/
public TreeNode getRight() {
return right;
}
/**
* Sets the right sub-expression of this node.
*
* @param right the sub-expression to apply.
*/
public void setRight(TreeNode right) {
this.right = right;
}
@Override
public <T> T reduce(Reducer<T> reducer) {
T leftReduce = left.reduce(reducer);
T rightReduce = right.reduce(reducer);
if (leftReduce == null || rightReduce == null) return null;
return reducer.reduceNode(this, leftReduce, rightReduce);
}
@Override
public String toString() {
String leftString = left != null ? left.toString() : "null";
String rightString = right != null ? right.toString() : "null";
return "(" + leftString + operation + rightString + ")";
}
}

View File

@@ -1,85 +0,0 @@
package org.nwapw.abacus.tree;
import java.util.ArrayList;
import java.util.List;
/**
* A node that represents a function call.
*/
public class FunctionNode extends TreeNode {
/**
* The name of the function being called
*/
private String function;
/**
* The list of arguments to the function.
*/
private List<TreeNode> children;
/**
* Creates a function node with no function.
*/
private FunctionNode() {
}
/**
* Creates a new function node with the given function name.
*
* @param function the function name.
*/
public FunctionNode(String function) {
this.function = function;
children = new ArrayList<>();
}
/**
* Gets the function name for this node.
*
* @return the function name.
*/
public String getFunction() {
return function;
}
/**
* Adds a child to the end of this node's child list.
*
* @param node the child to add.
*/
public void appendChild(TreeNode node) {
children.add(node);
}
/**
* Adds a new child to the beginning of this node's child list.
*
* @param node the node to add.
*/
public void prependChild(TreeNode node) {
children.add(0, node);
}
@Override
public <T> T reduce(Reducer<T> reducer) {
Object[] reducedChildren = new Object[children.size()];
for (int i = 0; i < reducedChildren.length; i++) {
reducedChildren[i] = children.get(i).reduce(reducer);
if (reducedChildren[i] == null) return null;
}
return reducer.reduceNode(this, reducedChildren);
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append(function);
buffer.append("(");
for (int i = 0; i < children.size(); i++) {
buffer.append(children.get(i));
buffer.append(i == children.size() - 1 ? "" : ", ");
}
buffer.append(")");
return buffer.toString();
}
}

View File

@@ -1,49 +0,0 @@
package org.nwapw.abacus.tree;
import org.nwapw.abacus.number.NumberInterface;
/**
* A node implementation that represents a single number.
*/
public class NumberNode extends TreeNode {
/**
* The number that is represented by this number node.
*/
private NumberInterface number;
/**
* Creates a number node with no number.
*/
public NumberNode() {
number = null;
}
/**
* Creates a new number node with the given double value.
*
* @param newNumber the number for which to create a number node.
*/
public NumberNode(NumberInterface newNumber) {
this.number = newNumber;
}
/**
* Gets the number value of this node.
*
* @return the number value of this node.
*/
public NumberInterface getNumber() {
return number;
}
@Override
public <T> T reduce(Reducer<T> reducer) {
return reducer.reduceNode(this);
}
@Override
public String toString() {
return number != null ? number.toString() : "null";
}
}

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,17 +0,0 @@
package org.nwapw.abacus.tree;
/**
* An abstract class that represents an expression tree node.
*/
public abstract class TreeNode {
/**
* The function that reduces a tree to a single vale.
*
* @param reducer the reducer used to reduce the tree.
* @param <T> the type the reducer produces.
* @return the result of the reduction, or null on error.
*/
public abstract <T> T reduce(Reducer<T> reducer);
}

View File

@@ -1,63 +0,0 @@
package org.nwapw.abacus.tree;
public class UnaryNode extends TreeNode {
/**
* The operation this node will apply.
*/
private String operation;
/**
* The tree node to apply the operation to.
*/
private TreeNode applyTo;
/**
* Creates a new node with the given operation and no child.
*
* @param operation the operation for this node.
*/
public UnaryNode(String operation) {
this(operation, null);
}
/**
* Creates a new node with the given operation and child.
*
* @param operation the operation for this node.
* @param applyTo the node to apply the function to.
*/
public UnaryNode(String operation, TreeNode applyTo) {
this.operation = operation;
this.applyTo = applyTo;
}
@Override
public <T> T reduce(Reducer<T> reducer) {
Object reducedChild = applyTo.reduce(reducer);
if (reducedChild == null) return null;
return reducer.reduceNode(this, reducedChild);
}
/**
* Gets the operation of this node.
*
* @return the operation this node performs.
*/
public String getOperation() {
return operation;
}
/**
* Gets the node to which this node's operation applies.
*
* @return the tree node to which the operation will be applied.
*/
public TreeNode getApplyTo() {
return applyTo;
}
@Override
public String toString() {
return "(" + (applyTo == null ? "null" : applyTo.toString()) + ")" + operation;
}
}

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