mirror of
https://github.com/DanilaFe/abacus
synced 2026-01-25 16:15:19 +00:00
Compare commits
297 Commits
provider-r
...
context
| Author | SHA1 | Date | |
|---|---|---|---|
| 00f8475044 | |||
| f0efae21be | |||
| 9f11fd20a2 | |||
| 1667edc72b | |||
| 5d2a988f75 | |||
| 91978686e6 | |||
| 9a8d0afc19 | |||
| 5aba5c350b | |||
| 21b7bd5e2b | |||
| f2ac7b109a | |||
| 67d240b8f6 | |||
| dc4eee6342 | |||
| 6909f210d6 | |||
| 059226a4d4 | |||
| ef1890f24d | |||
| 782669a32b | |||
| 924849bd8b | |||
| 91986112a1 | |||
| 58fea9c52b | |||
| 863be5bcfc | |||
| f0e38fed87 | |||
| fd246f935c | |||
| 6604af5b0f | |||
| d49a763e8f | |||
| 48a4d8adc2 | |||
| 5417b45106 | |||
| 585cabc478 | |||
| 28802cfed3 | |||
| 428df8bfd3 | |||
| 146f3994ef | |||
| daffdb6b42 | |||
| 178f59ef7b | |||
| 61616a428a | |||
| 9c77fa8aeb | |||
| 9ddfeb02cf | |||
| bc4a26aafb | |||
| 1f6aa70230 | |||
| e62722ce2f | |||
| ce82fd56dd | |||
| 6a65e66935 | |||
| e172108476 | |||
| 2b700d3911 | |||
| f7c07ca04d | |||
| ecb5139e70 | |||
| 453cd0ea77 | |||
| 7a296e4e8b | |||
| cbceee4abc | |||
| 8ea34b8f6e | |||
| 672252ef41 | |||
| 824f391fc7 | |||
| 63a160659a | |||
| 879d09e5b8 | |||
| ae0ec0c375 | |||
| 337a38a07d | |||
| fbfc68ebfe | |||
| 01e7a03444 | |||
| 0cb180284a | |||
| 192269ea9a | |||
| f134e5aa04 | |||
| e3c37cf10a | |||
| 823c788148 | |||
| ece9f1ae04 | |||
| fbc12ec41c | |||
| 385a64eace | |||
| c2feedee32 | |||
| b98b08b872 | |||
| f8eb051583 | |||
| 20b2e77ee1 | |||
| bfc1ed5819 | |||
| 9d52d55e68 | |||
| 07d7343339 | |||
| 73075c57b9 | |||
| 5b1a48c02e | |||
| ca2681cc21 | |||
| 8a3c614602 | |||
| da1c78945e | |||
| 225a926f86 | |||
| f83f2a7aaa | |||
| d04adf4da5 | |||
| 1f0addccea | |||
| 1a47e07e97 | |||
| 26305c3bae | |||
| 6b9252f902 | |||
| bc26ad0b88 | |||
| c5cd0f81ad | |||
| ac19c7b230 | |||
| 40c80db914 | |||
| 00462281fe | |||
| 01f80bbb53 | |||
| 553c7354c1 | |||
| 50ede6460c | |||
| beb583a231 | |||
| e0ff229df4 | |||
| 1c751353f1 | |||
| 0a15043b63 | |||
| 21e059c1ca | |||
| 16faceb3cc | |||
| 251da90d57 | |||
| a7536b198f | |||
| 66bda5db39 | |||
| 2425adaea5 | |||
| f5d913d527 | |||
| beae822d76 | |||
| bcd3e3b68a | |||
| 8b42acfbb1 | |||
| 952eeac704 | |||
| b57f24854e | |||
| 6758771c45 | |||
| 415a05288a | |||
| 334439e075 | |||
| 852776566d | |||
| 07020de1f9 | |||
| 06c2397a09 | |||
| 69fe28d5e4 | |||
| 367c282497 | |||
| 2876eda2ca | |||
| 4ec8c7a535 | |||
| 5167946fe2 | |||
| 418e896108 | |||
| d3012b9624 | |||
| bf7881faab | |||
| b75e92838d | |||
| 620f087e38 | |||
| 205d5dbc77 | |||
| d861444d13 | |||
| eac2a9ed6b | |||
| 31c61fdf95 | |||
| 8f251d2d13 | |||
| 99ffd51a43 | |||
| 00c51c62fd | |||
| 400ed6e70a | |||
|
|
5902ba8c5c | ||
|
|
efdc4af31b | ||
| 2e0b1201a3 | |||
|
|
d94edaa33f | ||
|
|
1e6cf08ec2 | ||
| 7c378b401a | |||
| 1c448048f2 | |||
| fd21014c39 | |||
|
|
fee9f091fd | ||
| 021e569491 | |||
|
|
4226df72f5 | ||
|
|
1f0e6a7ce4 | ||
|
|
efe76a6fdc | ||
|
|
ca6d8d2ba2 | ||
| e6f5af3727 | |||
| 4e042bd0eb | |||
| d6f4838f05 | |||
| 61475a24d9 | |||
| c498a5b643 | |||
| 5e3daaed43 | |||
| b99ad5a09a | |||
| ff8701a7bf | |||
|
|
1d6957c4d9 | ||
| 33b175a3c6 | |||
| c95a6df304 | |||
| 3316f02e2b | |||
| 6767a0e4aa | |||
| 400e4578a0 | |||
| 9d92d0eebb | |||
| fdcf2b5c6d | |||
| 8c3de54d0c | |||
| 5a57544067 | |||
| 61f40c72aa | |||
| ea5ff08c09 | |||
| 5f80c0bf14 | |||
| 7dcc80fcae | |||
| d10536155b | |||
| 536cac7b23 | |||
|
|
b6e4c6d2ea | ||
|
|
f8b3559cec | ||
|
|
4cf4ba98a8 | ||
| 12710c625b | |||
| e71b037195 | |||
| fe92929856 | |||
| e61cfdca46 | |||
| ff7d90967e | |||
| 355a91d690 | |||
| 5f0fba15eb | |||
| 3bdc0e2ae5 | |||
|
|
68fbcd2d7c | ||
|
|
ed92b382f0 | ||
| e54b5cdd66 | |||
| fd87cb66a3 | |||
| 200f4c7288 | |||
|
|
1cd544e712 | ||
|
|
a8c70a6bbe | ||
| f28e915c9a | |||
| 7a0863380a | |||
|
|
8a9df051cf | ||
|
|
4eda15b3fb | ||
| c9e0d4f8d3 | |||
| 213d7af10b | |||
| 585a3839c1 | |||
| 205b73f62c | |||
| 1cd332b97d | |||
| 314552f95a | |||
| c5ec521996 | |||
| 4712bbfded | |||
| 7ae7f6d9a5 | |||
|
|
d0ccb8b625 | ||
|
|
7d5efa1fe6 | ||
| 35254d3e99 | |||
| 44f018060d | |||
|
|
6a15c266c4 | ||
| 9f61fc5dbe | |||
| bae6ee5526 | |||
| 4f94700aef | |||
| b7152da58d | |||
| d17a8a9fa7 | |||
| 71c9f0d141 | |||
| fb02984e60 | |||
| a9ac4681f0 | |||
| 62d7053441 | |||
| f3cbb600ac | |||
| abc0e2d59f | |||
| f7d1be086b | |||
| 21a925d6d2 | |||
| 0d21898f20 | |||
| 3e39087fde | |||
| a984f2960d | |||
| a6832e09f4 | |||
| 0bcb3b25d9 | |||
| 2f5f967be4 | |||
| 72a2a8f1c1 | |||
| 58fc94e9d0 | |||
| 9cedb100ad | |||
| 99be2d80f1 | |||
| 2523b9b04b | |||
| cd60c9d52f | |||
| 23a3eb88f1 | |||
| 508e98413d | |||
| d06f611a2e | |||
| c541eaab97 | |||
| 0058ec9c71 | |||
|
|
f8bf60f383 | ||
|
|
4369eba107 | ||
| 385a0c960d | |||
|
|
d7ae1a80f1 | ||
|
|
e4a45c0ec4 | ||
| 22cf99d23d | |||
| 39b36f84e0 | |||
| b036b6c242 | |||
| eb3410f854 | |||
| f967053e3d | |||
| 75824a2a77 | |||
|
|
8df468e04a | ||
|
|
e029ab1fea | ||
|
|
eff7be0204 | ||
| eb51d5d3e4 | |||
| 8ae28f2dab | |||
| 0bade4a7df | |||
|
|
9d5f9d901c | ||
|
|
dad546c5b5 | ||
| f0e1b85dcf | |||
| 37261c2f58 | |||
|
|
95845a1585 | ||
|
|
8cf0c94947 | ||
| 20f6e0b0b2 | |||
| 4056013d1f | |||
| c7b5d4c4fc | |||
| be28e26607 | |||
| 2f1ed5f0d1 | |||
| 2615273d28 | |||
| 6e1d2ce629 | |||
| 44b8efd9bc | |||
| 2502c90837 | |||
| e49f28a850 | |||
| 88e4a87d81 | |||
| cda09518c3 | |||
| 56510d97de | |||
|
|
86533d53c9 | ||
| c2ae0b4138 | |||
| 16938b4e06 | |||
| d964fbfb6f | |||
| 9713f24ed2 | |||
| 5de9453bec | |||
| d205651332 | |||
| 6f99f07150 | |||
| 2cf41c1029 | |||
| 76677ef494 | |||
| 0cd40b028a | |||
| 7cb04a1222 | |||
| 0a97eeb442 | |||
| 05d0755526 | |||
| 0b97a935bf | |||
| 211e963db0 | |||
| d8145acc8f | |||
| 63b8162a9b | |||
| c655c63233 | |||
| 27ff1a47b5 | |||
| 2941252f7d | |||
| 44ed0199d4 | |||
| 5b582a7dbe | |||
| a02086e791 | |||
| 21d88fe256 | |||
| 3d61ead0f6 |
@@ -5,10 +5,10 @@ Summer project for NWAPW.
|
||||
Created by Arthur Drobot, Danila Fedorin and Riley Jones.
|
||||
|
||||
## Project Description
|
||||
Abacus is a calculator built with extensibility and usability in mind. It provides a plugin interface, via Java, as Lua provides too difficult to link up to the Java core. The description of the internals of the project can be found on the wiki page.
|
||||
Abacus is a calculator built with extensibility and usability in mind. It provides a plugin interface, via Java, as Lua proves too difficult to link up to the Java core. The description of the internals of the project can be found on the wiki page.
|
||||
|
||||
## Current State
|
||||
Abacus is being built for the Northwest Advanced Programming Workshop, a 3 week program in which students work in treams to complete a single project, following principles of agile development. Because of its short timeframe, Abacus is not even close to completed state. Below is a list of the current features and problems.
|
||||
Abacus is being built for the Northwest Advanced Programming Workshop, a 3 week program in which students work in teams to complete a single project, following principles of agile development. Because of its short timeframe, Abacus is not even close to completed state. Below is a list of the current features and problems.
|
||||
- [x] Basic number class
|
||||
- [x] Implementation of basic functions
|
||||
- [x] Implementation of `exp`, `ln`, `sqrt` using the basic functions and Taylor Series
|
||||
|
||||
34
build.gradle
34
build.gradle
@@ -1,15 +1,29 @@
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'application'
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.1.3'
|
||||
ext.dokka_version = '0.9.15'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.moandjiezana.toml:toml4j:0.7.1'
|
||||
testCompile 'junit:junit:4.12'
|
||||
subprojects {
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'org.jetbrains.dokka'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
// Define the main class for the application
|
||||
mainClassName = 'org.nwapw.abacus.Abacus'
|
||||
|
||||
|
||||
3
core/build.gradle
Normal file
3
core/build.gradle
Normal file
@@ -0,0 +1,3 @@
|
||||
dependencies {
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.nwapw.abacus.function;
|
||||
|
||||
/**
|
||||
* Enum that holds the type of documentation that has been
|
||||
* registered with Abacus.
|
||||
*/
|
||||
public enum DocumentationType {
|
||||
|
||||
FUNCTION, TREE_VALUE_FUNCTION
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.nwapw.abacus.function;
|
||||
|
||||
/**
|
||||
* Exception thrown if the function parameters do not match
|
||||
* requirements.
|
||||
*/
|
||||
public class DomainException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new DomainException.
|
||||
* @param reason the reason for which the exception is thrown.
|
||||
*/
|
||||
public DomainException(String reason) {
|
||||
super(reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DomainException with a default message.
|
||||
*/
|
||||
public DomainException(){
|
||||
this("Domain Error");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.nwapw.abacus.function;
|
||||
|
||||
/**
|
||||
* An exception thrown primarily from Tree Value operators and functions,
|
||||
* which have to deal with the result of a Reducer as well as the results
|
||||
* of Applicable.
|
||||
*/
|
||||
public class EvaluationException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new EvaluationException with the default string.
|
||||
*/
|
||||
public EvaluationException() {
|
||||
super("Error evaluating expression.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new EvaluationError with the given message string.
|
||||
* @param message the message string.
|
||||
*/
|
||||
public EvaluationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,5 +4,5 @@ package org.nwapw.abacus.function;
|
||||
* The type of an operator, describing how it should behave.
|
||||
*/
|
||||
public enum OperatorType {
|
||||
BINARY_INFIX, UNARY_POSTFIX
|
||||
BINARY_INFIX, UNARY_POSTFIX, UNARY_PREFIX
|
||||
}
|
||||
@@ -15,7 +15,7 @@ public class ValueNode<T> extends PatternNode<T> {
|
||||
/**
|
||||
* Creates a new node that matches the given character.
|
||||
*
|
||||
* @param value
|
||||
* @param value the character value of the node.
|
||||
*/
|
||||
public ValueNode(char value) {
|
||||
this.value = value;
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.nwapw.abacus.number;
|
||||
|
||||
/**
|
||||
* Exception thrown when the computation is interrupted by
|
||||
* the user.
|
||||
*/
|
||||
public class ComputationInterruptedException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new exception of this type.
|
||||
*/
|
||||
public ComputationInterruptedException() {
|
||||
super("Computation interrupted by user.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package org.nwapw.abacus.number;
|
||||
/**
|
||||
* An implementation of NumberInterface using a double.
|
||||
*/
|
||||
public class NaiveNumber implements NumberInterface {
|
||||
public class NaiveNumber extends NumberInterface {
|
||||
|
||||
/**
|
||||
* The number zero.
|
||||
@@ -26,6 +26,7 @@ public class NaiveNumber implements NumberInterface {
|
||||
public NaiveNumber(String value) {
|
||||
this(Double.parseDouble(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new NaiveNumber with the given value.
|
||||
*
|
||||
@@ -41,32 +42,32 @@ public class NaiveNumber implements NumberInterface {
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface multiply(NumberInterface multiplier) {
|
||||
public NumberInterface multiplyInternal(NumberInterface multiplier) {
|
||||
return new NaiveNumber(value * ((NaiveNumber) multiplier).value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface divide(NumberInterface divisor) {
|
||||
public NumberInterface divideInternal(NumberInterface divisor) {
|
||||
return new NaiveNumber(value / ((NaiveNumber) divisor).value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface add(NumberInterface summand) {
|
||||
public NumberInterface addInternal(NumberInterface summand) {
|
||||
return new NaiveNumber(value + ((NaiveNumber) summand).value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface subtract(NumberInterface subtrahend) {
|
||||
public NumberInterface subtractInternal(NumberInterface subtrahend) {
|
||||
return new NaiveNumber(value - ((NaiveNumber) subtrahend).value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface negate() {
|
||||
public NumberInterface negateInternal() {
|
||||
return new NaiveNumber(-value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface intPow(int exponent) {
|
||||
public NumberInterface intPowInternal(int exponent) {
|
||||
if (exponent == 0) {
|
||||
return NaiveNumber.ONE;
|
||||
}
|
||||
@@ -94,32 +95,23 @@ public class NaiveNumber implements NumberInterface {
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface ceiling() {
|
||||
public NumberInterface ceilingInternal() {
|
||||
return new NaiveNumber(Math.ceil(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface floor() {
|
||||
public NumberInterface floorInternal() {
|
||||
return new NaiveNumber(Math.floor(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface fractionalPart() {
|
||||
public NumberInterface fractionalPartInternal() {
|
||||
return new NaiveNumber(value - Math.floor(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return (int)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface promoteTo(Class<? extends NumberInterface> toClass) {
|
||||
if (toClass == this.getClass()) return this;
|
||||
else if (toClass == PreciseNumber.class) {
|
||||
return new PreciseNumber(Double.toString(value));
|
||||
}
|
||||
return null;
|
||||
return (int) value;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
@@ -127,5 +119,9 @@ public class NaiveNumber implements NumberInterface {
|
||||
return Double.toString(Math.round(value * shiftBy) / shiftBy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface getMaxError() {
|
||||
return new NaiveNumber(Math.pow(10, -18));
|
||||
}
|
||||
}
|
||||
|
||||
247
core/src/main/java/org/nwapw/abacus/number/NumberInterface.java
Executable file
247
core/src/main/java/org/nwapw/abacus/number/NumberInterface.java
Executable file
@@ -0,0 +1,247 @@
|
||||
package org.nwapw.abacus.number;
|
||||
|
||||
/**
|
||||
* An interface used to represent a number.
|
||||
*/
|
||||
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())
|
||||
throw new ComputationInterruptedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum precision to which this number operates.
|
||||
*
|
||||
* @return the precision.
|
||||
*/
|
||||
public abstract int getMaxPrecision();
|
||||
|
||||
/**
|
||||
* Multiplies this number by another, returning
|
||||
* a new number instance.
|
||||
*
|
||||
* @param multiplier the multiplier
|
||||
* @return the result of the multiplication.
|
||||
*/
|
||||
protected abstract NumberInterface multiplyInternal(NumberInterface multiplier);
|
||||
|
||||
/**
|
||||
* Multiplies this number by another, returning
|
||||
* a new number instance. Also, checks if the
|
||||
* thread has been interrupted, and if so, throws
|
||||
* an exception.
|
||||
*
|
||||
* @param multiplier the multiplier
|
||||
* @return the result of the multiplication.
|
||||
*/
|
||||
public final NumberInterface multiply(NumberInterface multiplier) {
|
||||
checkInterrupted();
|
||||
return multiplyInternal(multiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Divides this number by another, returning
|
||||
* a new number instance.
|
||||
*
|
||||
* @param divisor the divisor
|
||||
* @return the result of the division.
|
||||
*/
|
||||
protected abstract NumberInterface divideInternal(NumberInterface divisor);
|
||||
|
||||
/**
|
||||
* Divides this number by another, returning
|
||||
* a new number instance. Also, checks if the
|
||||
* thread has been interrupted, and if so, throws
|
||||
* an exception.
|
||||
*
|
||||
* @param divisor the divisor
|
||||
* @return the result of the division.
|
||||
*/
|
||||
public final NumberInterface divide(NumberInterface divisor) {
|
||||
checkInterrupted();
|
||||
return divideInternal(divisor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds this number to another, returning
|
||||
* a new number instance.
|
||||
*
|
||||
* @param summand the summand
|
||||
* @return the result of the summation.
|
||||
*/
|
||||
protected abstract NumberInterface addInternal(NumberInterface summand);
|
||||
|
||||
/**
|
||||
* Adds this number to another, returning
|
||||
* a new number instance. Also, checks if the
|
||||
* thread has been interrupted, and if so, throws
|
||||
* an exception.
|
||||
*
|
||||
* @param summand the summand
|
||||
* @return the result of the summation.
|
||||
*/
|
||||
public final NumberInterface add(NumberInterface summand) {
|
||||
checkInterrupted();
|
||||
return addInternal(summand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts another number from this number,
|
||||
* a new number instance.
|
||||
*
|
||||
* @param subtrahend the subtrahend.
|
||||
* @return the result of the subtraction.
|
||||
*/
|
||||
protected abstract NumberInterface subtractInternal(NumberInterface subtrahend);
|
||||
|
||||
/**
|
||||
* Subtracts another number from this number,
|
||||
* a new number instance. Also, checks if the
|
||||
* thread has been interrupted, and if so, throws
|
||||
* an exception.
|
||||
*
|
||||
* @param subtrahend the subtrahend.
|
||||
* @return the result of the subtraction.
|
||||
*/
|
||||
public final NumberInterface subtract(NumberInterface subtrahend) {
|
||||
checkInterrupted();
|
||||
return subtractInternal(subtrahend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of this number with
|
||||
* the sign flipped.
|
||||
*
|
||||
* @return the new instance.
|
||||
*/
|
||||
protected abstract NumberInterface negateInternal();
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new instance of this number with
|
||||
* the sign flipped. Also, checks if the
|
||||
* thread has been interrupted, and if so, throws
|
||||
* an exception.
|
||||
*
|
||||
* @return the new instance.
|
||||
*/
|
||||
public final NumberInterface negate() {
|
||||
checkInterrupted();
|
||||
return negateInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Raises this number to an integer power.
|
||||
*
|
||||
* @param exponent the exponent to which to take the number.
|
||||
* @return the resulting value.
|
||||
*/
|
||||
protected abstract NumberInterface intPowInternal(int exponent);
|
||||
|
||||
/**
|
||||
* Raises this number to an integer power. Also, checks if the
|
||||
* thread has been interrupted, and if so, throws
|
||||
* an exception.
|
||||
*
|
||||
* @param exponent the exponent to which to take the number.
|
||||
* @return the resulting value.
|
||||
*/
|
||||
public final NumberInterface intPow(int exponent) {
|
||||
checkInterrupted();
|
||||
return intPowInternal(exponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this number to another.
|
||||
*
|
||||
* @param number the number to compare to.
|
||||
* @return same as Integer.compare();
|
||||
*/
|
||||
public abstract int compareTo(NumberInterface number);
|
||||
|
||||
/**
|
||||
* Same as Math.signum().
|
||||
*
|
||||
* @return 1 if this number is positive, -1 if this number is negative, 0 if this number is 0.
|
||||
*/
|
||||
public abstract int signum();
|
||||
|
||||
/**
|
||||
* Returns the least integer greater than or equal to the number.
|
||||
*
|
||||
* @return the least integer greater or equal to the number, if int can hold the value.
|
||||
*/
|
||||
protected abstract NumberInterface ceilingInternal();
|
||||
|
||||
/**
|
||||
* Returns the least integer greater than or equal to the number.
|
||||
* Also, checks if the thread has been interrupted, and if so, throws
|
||||
* an exception.
|
||||
*
|
||||
* @return the least integer bigger or equal to the number.
|
||||
*/
|
||||
public final NumberInterface ceiling() {
|
||||
checkInterrupted();
|
||||
return ceilingInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the greatest integer less than or equal to the number.
|
||||
*
|
||||
* @return the greatest integer smaller or equal the number.
|
||||
*/
|
||||
protected abstract NumberInterface floorInternal();
|
||||
|
||||
/**
|
||||
* Return the greatest integer less than or equal to the number.
|
||||
* Also, checks if the thread has been interrupted, and if so, throws
|
||||
* an exception.
|
||||
*
|
||||
* @return the greatest int smaller than or equal to the number.
|
||||
*/
|
||||
public final NumberInterface floor() {
|
||||
checkInterrupted();
|
||||
return floorInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fractional part of the number.
|
||||
*
|
||||
* @return the fractional part of the number.
|
||||
*/
|
||||
protected abstract NumberInterface fractionalPartInternal();
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
checkInterrupted();
|
||||
return fractionalPartInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer representation of this number, discarding any fractional part,
|
||||
* if int can hold the value.
|
||||
*
|
||||
* @return the integer value of this number.
|
||||
*/
|
||||
public abstract int intValue();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package org.nwapw.abacus.number;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.math.MathContext;
|
||||
|
||||
public class PreciseNumber implements NumberInterface {
|
||||
/**
|
||||
* A number that uses a BigDecimal to store its value,
|
||||
* leading to infinite possible precision.
|
||||
*/
|
||||
public class PreciseNumber extends NumberInterface {
|
||||
|
||||
/**
|
||||
* The number one.
|
||||
@@ -18,6 +22,21 @@ public class PreciseNumber implements 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.
|
||||
*/
|
||||
@@ -44,31 +63,31 @@ public class PreciseNumber implements NumberInterface {
|
||||
|
||||
@Override
|
||||
public int getMaxPrecision() {
|
||||
return 65;
|
||||
return internalContext.getPrecision();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface multiply(NumberInterface multiplier) {
|
||||
public NumberInterface multiplyInternal(NumberInterface multiplier) {
|
||||
return new PreciseNumber(this.value.multiply(((PreciseNumber) multiplier).value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface divide(NumberInterface divisor) {
|
||||
return new PreciseNumber(value.divide(((PreciseNumber) divisor).value, this.getMaxPrecision(), RoundingMode.HALF_UP));
|
||||
public NumberInterface divideInternal(NumberInterface divisor) {
|
||||
return new PreciseNumber(value.divide(((PreciseNumber) divisor).value, internalContext));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface add(NumberInterface summand) {
|
||||
public NumberInterface addInternal(NumberInterface summand) {
|
||||
return new PreciseNumber(value.add(((PreciseNumber) summand).value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface subtract(NumberInterface subtrahend) {
|
||||
public NumberInterface subtractInternal(NumberInterface subtrahend) {
|
||||
return new PreciseNumber(value.subtract(((PreciseNumber) subtrahend).value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface intPow(int exponent) {
|
||||
public NumberInterface intPowInternal(int exponent) {
|
||||
if (exponent == 0) {
|
||||
return PreciseNumber.ONE;
|
||||
}
|
||||
@@ -95,33 +114,32 @@ public class PreciseNumber implements NumberInterface {
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface ceiling() {
|
||||
public NumberInterface ceilingInternal() {
|
||||
String str = value.toPlainString();
|
||||
int decimalIndex = str.indexOf('.');
|
||||
if(decimalIndex != -1){
|
||||
if (decimalIndex != -1) {
|
||||
return this.floor().add(ONE);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface floor() {
|
||||
public NumberInterface floorInternal() {
|
||||
String str = value.toPlainString();
|
||||
int decimalIndex = str.indexOf('.');
|
||||
if(decimalIndex != -1){
|
||||
return new PreciseNumber(str.substring(0, decimalIndex));
|
||||
if (decimalIndex != -1) {
|
||||
NumberInterface floor = new PreciseNumber(str.substring(0, decimalIndex));
|
||||
if (signum() == -1) {
|
||||
floor = floor.subtract(ONE);
|
||||
}
|
||||
return floor;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface fractionalPart() {
|
||||
String str = value.toPlainString();
|
||||
int decimalIndex = str.indexOf('.');
|
||||
if(decimalIndex != -1){
|
||||
return new PreciseNumber(str.substring(decimalIndex + 1));
|
||||
}
|
||||
return ZERO;
|
||||
public NumberInterface fractionalPartInternal() {
|
||||
return this.subtractInternal(floorInternal());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,21 +148,17 @@ public class PreciseNumber implements NumberInterface {
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface negate() {
|
||||
public NumberInterface negateInternal() {
|
||||
return new PreciseNumber(value.negate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface promoteTo(Class<? extends NumberInterface> toClass) {
|
||||
if (toClass == this.getClass()) {
|
||||
return this;
|
||||
}
|
||||
return null;
|
||||
public String toString() {
|
||||
return value.round(outputContext).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
BigDecimal rounded = value.setScale(getMaxPrecision() - 15, RoundingMode.HALF_UP);
|
||||
return rounded.stripTrailingZeros().toPlainString();
|
||||
public NumberInterface getMaxError() {
|
||||
return new PreciseNumber(value.ulp()).multiplyInternal(TEN.intPowInternal(value.precision() - internalContext.getPrecision()));
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListen
|
||||
register(" ", TokenType.WHITESPACE);
|
||||
register(",", TokenType.COMMA);
|
||||
register("[0-9]*(\\.[0-9]+)?", TokenType.NUM);
|
||||
register("[a-zA-Z]+", TokenType.VARIABLE);
|
||||
register("\\(", TokenType.OPEN_PARENTH);
|
||||
register("\\)", TokenType.CLOSE_PARENTH);
|
||||
}};
|
||||
@@ -49,9 +50,15 @@ public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListen
|
||||
for (String operator : manager.getAllOperators()) {
|
||||
lexer.register(Pattern.sanitize(operator), TokenType.OP);
|
||||
}
|
||||
for (String operator : manager.getAllTreeValueOperators()) {
|
||||
lexer.register(Pattern.sanitize(operator), TokenType.TREE_VALUE_OP);
|
||||
}
|
||||
for (String function : manager.getAllFunctions()) {
|
||||
lexer.register(Pattern.sanitize(function), TokenType.FUNCTION);
|
||||
}
|
||||
for (String function : manager.getAllTreeValueFunctions()) {
|
||||
lexer.register(Pattern.sanitize(function), TokenType.TREE_VALUE_FUNCTION);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,9 +66,15 @@ public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListen
|
||||
for (String operator : manager.getAllOperators()) {
|
||||
lexer.unregister(Pattern.sanitize(operator), TokenType.OP);
|
||||
}
|
||||
for (String operator : manager.getAllTreeValueOperators()) {
|
||||
lexer.unregister(Pattern.sanitize(operator), TokenType.TREE_VALUE_OP);
|
||||
}
|
||||
for (String function : manager.getAllFunctions()) {
|
||||
lexer.unregister(Pattern.sanitize(function), TokenType.FUNCTION);
|
||||
}
|
||||
for (String function : manager.getAllTreeValueFunctions()) {
|
||||
lexer.unregister(Pattern.sanitize(function), TokenType.TREE_VALUE_FUNCTION);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.nwapw.abacus.parsing;
|
||||
|
||||
import org.nwapw.abacus.Abacus;
|
||||
import org.nwapw.abacus.function.Operator;
|
||||
import org.nwapw.abacus.function.OperatorAssociativity;
|
||||
import org.nwapw.abacus.function.OperatorType;
|
||||
@@ -17,10 +16,6 @@ import java.util.*;
|
||||
*/
|
||||
public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListener {
|
||||
|
||||
/**
|
||||
* The Abacus instance used to create number instances.
|
||||
*/
|
||||
private Abacus abacus;
|
||||
/**
|
||||
* Map of operator precedences, loaded from the plugin operators.
|
||||
*/
|
||||
@@ -35,12 +30,9 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
|
||||
private Map<String, OperatorType> typeMap;
|
||||
|
||||
/**
|
||||
* Creates a new Shunting Yard parser with the given Abacus instance.
|
||||
*
|
||||
* @param abacus the abacus instance.
|
||||
* Creates a new Shunting Yard parser.
|
||||
*/
|
||||
public ShuntingYardParser(Abacus abacus) {
|
||||
this.abacus = abacus;
|
||||
public ShuntingYardParser() {
|
||||
precedenceMap = new HashMap<>();
|
||||
associativityMap = new HashMap<>();
|
||||
typeMap = new HashMap<>();
|
||||
@@ -55,15 +47,18 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
|
||||
public List<Match<TokenType>> intoPostfix(List<Match<TokenType>> from) {
|
||||
ArrayList<Match<TokenType>> output = new ArrayList<>();
|
||||
Stack<Match<TokenType>> tokenStack = new Stack<>();
|
||||
TokenType previousType;
|
||||
TokenType matchType = null;
|
||||
while (!from.isEmpty()) {
|
||||
Match<TokenType> match = from.remove(0);
|
||||
TokenType matchType = match.getType();
|
||||
if (matchType == TokenType.NUM) {
|
||||
previousType = matchType;
|
||||
matchType = match.getType();
|
||||
if (matchType == TokenType.NUM || matchType == TokenType.VARIABLE) {
|
||||
output.add(match);
|
||||
} else if (matchType == TokenType.FUNCTION) {
|
||||
} else if (matchType == TokenType.FUNCTION || matchType == TokenType.TREE_VALUE_FUNCTION) {
|
||||
output.add(new Match<>("", TokenType.INTERNAL_FUNCTION_END));
|
||||
tokenStack.push(match);
|
||||
} else if (matchType == TokenType.OP) {
|
||||
} else if (matchType == TokenType.OP || matchType == TokenType.TREE_VALUE_OP) {
|
||||
String tokenString = match.getContent();
|
||||
OperatorType type = typeMap.get(tokenString);
|
||||
int precedence = precedenceMap.get(tokenString);
|
||||
@@ -74,13 +69,22 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
|
||||
continue;
|
||||
}
|
||||
|
||||
while (!tokenStack.empty()) {
|
||||
if (tokenString.equals("-") && (previousType == null || previousType == TokenType.OP ||
|
||||
previousType == TokenType.TREE_VALUE_OP || previousType == TokenType.OPEN_PARENTH)) {
|
||||
from.add(0, new Match<>("`", TokenType.OP));
|
||||
continue;
|
||||
}
|
||||
|
||||
while (!tokenStack.empty() && type == OperatorType.BINARY_INFIX) {
|
||||
Match<TokenType> otherMatch = tokenStack.peek();
|
||||
TokenType otherMatchType = otherMatch.getType();
|
||||
if (!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break;
|
||||
if (!(otherMatchType == TokenType.OP ||
|
||||
otherMatchType == TokenType.TREE_VALUE_OP ||
|
||||
otherMatchType == TokenType.FUNCTION ||
|
||||
otherMatchType == TokenType.TREE_VALUE_FUNCTION)) break;
|
||||
|
||||
if (otherMatchType == TokenType.OP) {
|
||||
int otherPrecedence = precedenceMap.get(match.getContent());
|
||||
if (otherMatchType == TokenType.OP || otherMatchType == TokenType.TREE_VALUE_OP) {
|
||||
int otherPrecedence = precedenceMap.get(otherMatch.getContent());
|
||||
if (otherPrecedence < precedence ||
|
||||
(associativity == OperatorAssociativity.RIGHT && otherPrecedence == precedence)) {
|
||||
break;
|
||||
@@ -103,8 +107,11 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
|
||||
}
|
||||
while (!tokenStack.empty()) {
|
||||
Match<TokenType> match = tokenStack.peek();
|
||||
TokenType matchType = match.getType();
|
||||
if (!(matchType == TokenType.OP || matchType == TokenType.FUNCTION)) return null;
|
||||
TokenType newMatchType = match.getType();
|
||||
if (!(newMatchType == TokenType.OP ||
|
||||
newMatchType == TokenType.TREE_VALUE_OP ||
|
||||
newMatchType == TokenType.FUNCTION ||
|
||||
newMatchType == TokenType.TREE_VALUE_FUNCTION)) return null;
|
||||
output.add(tokenStack.pop());
|
||||
}
|
||||
return output;
|
||||
@@ -120,28 +127,43 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
|
||||
if (matches.size() == 0) return null;
|
||||
Match<TokenType> match = matches.remove(0);
|
||||
TokenType matchType = match.getType();
|
||||
if (matchType == TokenType.OP) {
|
||||
if (matchType == TokenType.OP || matchType == TokenType.TREE_VALUE_OP) {
|
||||
String operator = match.getContent();
|
||||
OperatorType type = typeMap.get(operator);
|
||||
if (type == OperatorType.BINARY_INFIX) {
|
||||
TreeNode right = constructRecursive(matches);
|
||||
TreeNode left = constructRecursive(matches);
|
||||
if (left == null || right == null) return null;
|
||||
else return new BinaryInfixNode(operator, left, right);
|
||||
if (matchType == TokenType.OP) {
|
||||
return new NumberBinaryNode(operator, left, right);
|
||||
} else {
|
||||
return new TreeValueBinaryNode(operator, left, right);
|
||||
}
|
||||
} else {
|
||||
TreeNode applyTo = constructRecursive(matches);
|
||||
if (applyTo == null) return null;
|
||||
else return new UnaryPrefixNode(operator, applyTo);
|
||||
if (matchType == TokenType.OP) {
|
||||
return new NumberUnaryNode(operator, applyTo);
|
||||
} else {
|
||||
return new TreeValueUnaryNode(operator, applyTo);
|
||||
}
|
||||
}
|
||||
} else if (matchType == TokenType.NUM) {
|
||||
return new NumberNode(abacus.numberFromString(match.getContent()));
|
||||
} else if (matchType == TokenType.FUNCTION) {
|
||||
return new NumberNode(match.getContent());
|
||||
} else if (matchType == TokenType.VARIABLE) {
|
||||
return new VariableNode(match.getContent());
|
||||
} else if (matchType == TokenType.FUNCTION || matchType == TokenType.TREE_VALUE_FUNCTION) {
|
||||
String functionName = match.getContent();
|
||||
FunctionNode node = new FunctionNode(functionName);
|
||||
CallNode node;
|
||||
if (matchType == TokenType.FUNCTION) {
|
||||
node = new FunctionNode(functionName);
|
||||
} else {
|
||||
node = new TreeValueFunctionNode(functionName);
|
||||
}
|
||||
while (!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END) {
|
||||
TreeNode argument = constructRecursive(matches);
|
||||
if (argument == null) return null;
|
||||
node.prependChild(argument);
|
||||
node.getChildren().add(0, argument);
|
||||
}
|
||||
if (matches.isEmpty()) return null;
|
||||
matches.remove(0);
|
||||
@@ -153,8 +175,10 @@ 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;
|
||||
Collections.reverse(tokens);
|
||||
return constructRecursive(tokens);
|
||||
TreeNode constructedTree = constructRecursive(tokens);
|
||||
return tokens.size() == 0 ? constructedTree : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,6 +189,12 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
|
||||
associativityMap.put(operator, operatorInstance.getAssociativity());
|
||||
typeMap.put(operator, operatorInstance.getType());
|
||||
}
|
||||
for (String operator : manager.getAllTreeValueOperators()) {
|
||||
Operator operatorInstance = manager.treeValueOperatorFor(operator);
|
||||
precedenceMap.put(operator, operatorInstance.getPrecedence());
|
||||
associativityMap.put(operator, operatorInstance.getAssociativity());
|
||||
typeMap.put(operator, operatorInstance.getType());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -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<String, Function<NumberInterface, NumberInterface>> promotionPaths;
|
||||
/**
|
||||
* The implementation class for this implementation.
|
||||
*/
|
||||
@@ -26,10 +26,11 @@ public abstract class NumberImplementation {
|
||||
|
||||
/**
|
||||
* Creates a new number implementation with the given data.
|
||||
*
|
||||
* @param implementation the implementation class.
|
||||
* @param priority the priority, higher -> more likely to be converted into.
|
||||
* @param priority the priority, higher means more likely to be converted into.
|
||||
*/
|
||||
public NumberImplementation(Class<? extends NumberInterface> implementation, int priority){
|
||||
public NumberImplementation(Class<? extends NumberInterface> implementation, int priority) {
|
||||
this.implementation = implementation;
|
||||
this.priority = priority;
|
||||
promotionPaths = new HashMap<>();
|
||||
@@ -37,30 +38,34 @@ public abstract class NumberImplementation {
|
||||
|
||||
/**
|
||||
* Gets the list of all promotion paths this implementation can take.
|
||||
*
|
||||
* @return the map of documentation paths.
|
||||
*/
|
||||
public final Map<Class<? extends NumberInterface>, Function<NumberInterface, NumberInterface>> getPromotionPaths(){
|
||||
public final Map<String, Function<NumberInterface, NumberInterface>> getPromotionPaths() {
|
||||
return promotionPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the implementation class used by this implementation.
|
||||
*
|
||||
* @return the implementation class.
|
||||
*/
|
||||
public final Class<? extends NumberInterface> getImplementation(){
|
||||
public final Class<? extends NumberInterface> getImplementation() {
|
||||
return implementation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the priority of this number implementation.
|
||||
*
|
||||
* @return the priority.
|
||||
*/
|
||||
public final int getPriority(){
|
||||
public final int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract function to create a new instance from a string.
|
||||
*
|
||||
* @param string the string to create a number from.
|
||||
* @return the resulting number.
|
||||
*/
|
||||
@@ -68,6 +73,7 @@ public abstract class NumberImplementation {
|
||||
|
||||
/**
|
||||
* Get the instance of pi with the given implementation.
|
||||
*
|
||||
* @return pi
|
||||
*/
|
||||
public abstract NumberInterface instanceForPi();
|
||||
@@ -1,13 +1,8 @@
|
||||
package org.nwapw.abacus.plugin;
|
||||
|
||||
import org.nwapw.abacus.function.Function;
|
||||
import org.nwapw.abacus.function.Operator;
|
||||
import org.nwapw.abacus.function.*;
|
||||
import org.nwapw.abacus.number.NumberInterface;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A plugin class that can be externally implemented and loaded via the
|
||||
* plugin manager. Plugins provide functionality to the calculator
|
||||
@@ -17,18 +12,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 +32,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 +52,6 @@ public abstract class Plugin {
|
||||
public final void disable() {
|
||||
if (!enabled) return;
|
||||
onDisable();
|
||||
functions.clear();
|
||||
operators.clear();
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
@@ -141,8 +62,19 @@ public abstract class Plugin {
|
||||
* @param name the name to register by.
|
||||
* @param toRegister the function implementation.
|
||||
*/
|
||||
protected final void registerFunction(String name, Function toRegister) {
|
||||
functions.put(name, toRegister);
|
||||
protected final void registerFunction(String name, NumberFunction toRegister) {
|
||||
manager.registerFunction(name, toRegister);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be used in load(). Registers a tree value function abstract class
|
||||
* with the plugin internally, which makes it accessible to the plugin manager.
|
||||
*
|
||||
* @param name the name to register by.
|
||||
* @param toRegister the tree value function implementation.
|
||||
*/
|
||||
protected final void registerTreeValueFunction(String name, TreeValueFunction toRegister) {
|
||||
manager.registerTreeValueFunction(name, toRegister);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,18 +85,41 @@ public abstract class Plugin {
|
||||
* @param name the name of the operator.
|
||||
* @param operator the operator to register.
|
||||
*/
|
||||
protected final void registerOperator(String name, Operator operator) {
|
||||
operators.put(name, operator);
|
||||
protected final void registerOperator(String name, NumberOperator operator) {
|
||||
manager.registerOperator(name, operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be used in load(). Registers an operator
|
||||
* with the plugin internally, which makes it accessible
|
||||
* to the plugin manager.
|
||||
*
|
||||
* @param name the name of the tree value operator.
|
||||
* @param operator the tree value operator to register.
|
||||
*/
|
||||
protected final void registerTreeValueOperator(String name, TreeValueOperator operator) {
|
||||
manager.registerTreeValueOperator(name, operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be used in load(). Registers a new number implementation with the plugin.
|
||||
* This makes it accessible to the plugin manager.
|
||||
* @param name the name of the implementation.
|
||||
*
|
||||
* @param name the name of the implementation.
|
||||
* @param implementation the actual implementation class to register.
|
||||
*/
|
||||
protected final void registerNumberImplementation(String name, NumberImplementation implementation){
|
||||
numberImplementations.put(name, implementation);
|
||||
protected final void registerNumberImplementation(String name, NumberImplementation 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,10 +130,22 @@ public abstract class Plugin {
|
||||
* @param name the name for which to search
|
||||
* @return the resulting function, or null if none was found for that name.
|
||||
*/
|
||||
protected final Function functionFor(String name) {
|
||||
protected final NumberFunction functionFor(String name) {
|
||||
return manager.functionFor(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the PluginManager for the given function name.
|
||||
* This can be used by the plugins internally in order to call functions
|
||||
* they do not provide.
|
||||
*
|
||||
* @param name the name for which to search.
|
||||
* @return the resulting tree value function, or null if none was found for that name.
|
||||
*/
|
||||
protected final TreeValueFunction treeValueFunctionFor(String name) {
|
||||
return manager.treeValueFunctionFor(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the PluginManager for the given operator name.
|
||||
* This can be used by the plugins internally in order to call
|
||||
@@ -187,10 +154,22 @@ public abstract class Plugin {
|
||||
* @param name the name for which to search
|
||||
* @return the resulting operator, or null if none was found for that name.
|
||||
*/
|
||||
protected final Operator operatorFor(String name) {
|
||||
protected final NumberOperator operatorFor(String name) {
|
||||
return manager.operatorFor(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the PluginManager for the given tree value operator name.
|
||||
* This can be used by the plugins internally in order to call
|
||||
* operations they do not provide.
|
||||
*
|
||||
* @param name the name for which to search.
|
||||
* @return the resulting tree value operator, or null if none was found for that name.
|
||||
*/
|
||||
protected final TreeValueOperator treeValueOperatorFor(String name) {
|
||||
return manager.treeValueOperatorFor(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the PluginManager for the given number implementation
|
||||
* name. This can be used by the plugins internally in order to find
|
||||
@@ -199,19 +178,31 @@ public abstract class Plugin {
|
||||
* @param name the name for which to search.
|
||||
* @return the resulting number implementation, or null if none was found.
|
||||
*/
|
||||
protected final NumberImplementation numberImplementationFor(String name){
|
||||
protected final NumberImplementation numberImplementationFor(String name) {
|
||||
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
|
||||
* can provide their own pi values, without losing said precision by
|
||||
* promoting NaiveNumbers.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
425
core/src/main/java/org/nwapw/abacus/plugin/PluginManager.java
Normal file
425
core/src/main/java/org/nwapw/abacus/plugin/PluginManager.java
Normal file
@@ -0,0 +1,425 @@
|
||||
package org.nwapw.abacus.plugin;
|
||||
|
||||
import org.nwapw.abacus.Abacus;
|
||||
import org.nwapw.abacus.function.*;
|
||||
import org.nwapw.abacus.number.NumberInterface;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
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
|
||||
* to interact with each other and the calculator.
|
||||
*/
|
||||
public class PluginManager {
|
||||
|
||||
/**
|
||||
* List of classes loaded by this manager.
|
||||
*/
|
||||
private Set<Class<?>> loadedPluginClasses;
|
||||
/**
|
||||
* A list of loaded plugins.
|
||||
*/
|
||||
private Set<Plugin> plugins;
|
||||
/**
|
||||
* The map of functions registered by the plugins.
|
||||
*/
|
||||
private Map<String, NumberFunction> registeredFunctions;
|
||||
/**
|
||||
* The map of tree value functions regstered by the plugins.
|
||||
*/
|
||||
private Map<String, TreeValueFunction> registeredTreeValueFunctions;
|
||||
/**
|
||||
* The map of operators registered by the plugins
|
||||
*/
|
||||
private Map<String, NumberOperator> registeredOperators;
|
||||
/**
|
||||
* The map of tree value operators registered by the plugins.
|
||||
*/
|
||||
private Map<String, TreeValueOperator> registeredTreeValueOperators;
|
||||
/**
|
||||
* The map of number implementations registered by the plugins.
|
||||
*/
|
||||
private Map<String, NumberImplementation> registeredNumberImplementations;
|
||||
/**
|
||||
* The map of documentation for functions registered by the plugins.
|
||||
*/
|
||||
private Set<Documentation> registeredDocumentation;
|
||||
/**
|
||||
* The list of number implementation names.
|
||||
*/
|
||||
private Map<Class<? extends NumberInterface>, String> interfaceImplementationNames;
|
||||
/**
|
||||
* The list of number implementations.
|
||||
*/
|
||||
private Map<Class<? extends NumberInterface>, NumberImplementation> interfaceImplementations;
|
||||
/**
|
||||
* The pi values for each implementation class that have already been computer.
|
||||
*/
|
||||
private Map<Class<? extends NumberInterface>, NumberInterface> cachedPi;
|
||||
/**
|
||||
* The list of plugin listeners attached to this instance.
|
||||
*/
|
||||
private Set<PluginListener> listeners;
|
||||
/**
|
||||
* The abacus instance used to access other
|
||||
* components of the application.
|
||||
*/
|
||||
private Abacus abacus;
|
||||
|
||||
/**
|
||||
* Creates a new plugin manager.
|
||||
*
|
||||
* @param abacus the abacus instance.
|
||||
*/
|
||||
public PluginManager(Abacus abacus) {
|
||||
this.abacus = abacus;
|
||||
loadedPluginClasses = new HashSet<>();
|
||||
plugins = new HashSet<>();
|
||||
registeredFunctions = new HashMap<>();
|
||||
registeredTreeValueFunctions = new HashMap<>();
|
||||
registeredOperators = new HashMap<>();
|
||||
registeredTreeValueOperators = new HashMap<>();
|
||||
registeredNumberImplementations = new HashMap<>();
|
||||
registeredDocumentation = new HashSet<>();
|
||||
interfaceImplementations = new HashMap<>();
|
||||
interfaceImplementationNames = new HashMap<>();
|
||||
cachedPi = new HashMap<>();
|
||||
listeners = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a function under the given name.
|
||||
*
|
||||
* @param name the name of the function.
|
||||
* @param function the function to register.
|
||||
*/
|
||||
public void registerFunction(String name, NumberFunction function) {
|
||||
registeredFunctions.put(name, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a tree value function under the given name.
|
||||
*
|
||||
* @param name the name of the function.
|
||||
* @param function the function to register.
|
||||
*/
|
||||
public void registerTreeValueFunction(String name, TreeValueFunction function) {
|
||||
registeredTreeValueFunctions.put(name, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an operator under the given name.
|
||||
*
|
||||
* @param name the name of the operator.
|
||||
* @param operator the operator to register.
|
||||
*/
|
||||
public void registerOperator(String name, NumberOperator operator) {
|
||||
registeredOperators.put(name, operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a tree value operator under the given name.
|
||||
*
|
||||
* @param name the name of the tree value operator.
|
||||
* @param operator the tree value operator to register.
|
||||
*/
|
||||
public void registerTreeValueOperator(String name, TreeValueOperator operator) {
|
||||
registeredTreeValueOperators.put(name, operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a number implementation under the given name.
|
||||
*
|
||||
* @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);
|
||||
interfaceImplementationNames.put(implementation.getImplementation(), name);
|
||||
interfaceImplementations.put(implementation.getImplementation(), 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 NumberFunction functionFor(String name) {
|
||||
return registeredFunctions.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tree value function registered under the given name.
|
||||
*
|
||||
* @param name the name of the function.
|
||||
* @return the function, or null if it was not found.
|
||||
*/
|
||||
public TreeValueFunction treeValueFunctionFor(String name) {
|
||||
return registeredTreeValueFunctions.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the operator registered under the given name.
|
||||
*
|
||||
* @param name the name of the operator.
|
||||
* @return the operator, or null if it was not found.
|
||||
*/
|
||||
public NumberOperator operatorFor(String name) {
|
||||
return registeredOperators.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tree value operator registered under the given name.
|
||||
*
|
||||
* @param name the name of the tree value operator.
|
||||
* @return the operator, or null if it was not found.
|
||||
*/
|
||||
public TreeValueOperator treeValueOperatorFor(String name) {
|
||||
return registeredTreeValueOperators.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number implementation registered under the given name.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
if (toReturn == null) {
|
||||
toReturn = new Documentation(name, "", "", "", type);
|
||||
registerDocumentation(toReturn);
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number implementation for the given implementation class.
|
||||
*
|
||||
* @param name the class for which to find the implementation.
|
||||
* @return the implementation.
|
||||
*/
|
||||
public NumberImplementation interfaceImplementationFor(Class<? extends NumberInterface> name) {
|
||||
return interfaceImplementations.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number implementation name for the given implementation class.
|
||||
*
|
||||
* @param name the class for which to find the implementation name.
|
||||
* @return the implementation name.
|
||||
*/
|
||||
public String interfaceImplementationNameFor(Class<? extends NumberInterface> name) {
|
||||
return interfaceImplementationNames.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mathematical constant pi for the given implementation class.
|
||||
*
|
||||
* @param forClass the class for which to find pi.
|
||||
* @return pi
|
||||
*/
|
||||
public NumberInterface piFor(Class<? extends NumberInterface> forClass) {
|
||||
if (cachedPi.containsKey(forClass)) return cachedPi.get(forClass);
|
||||
NumberImplementation implementation = interfaceImplementationFor(forClass);
|
||||
NumberInterface generatedPi = null;
|
||||
if (implementation != null) {
|
||||
generatedPi = implementation.instanceForPi();
|
||||
}
|
||||
cachedPi.put(forClass, generatedPi);
|
||||
return generatedPi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an instance of Plugin that already has been instantiated.
|
||||
*
|
||||
* @param plugin the plugin to add.
|
||||
*/
|
||||
public void addInstantiated(Plugin plugin) {
|
||||
if (loadedPluginClasses.contains(plugin.getClass())) return;
|
||||
plugins.add(plugin);
|
||||
loadedPluginClasses.add(plugin.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a class of plugin, and adds it to this
|
||||
* plugin manager.
|
||||
*
|
||||
* @param newClass the new class to instantiate.
|
||||
*/
|
||||
public void addClass(Class<?> newClass) {
|
||||
if (!Plugin.class.isAssignableFrom(newClass) || newClass == Plugin.class) return;
|
||||
try {
|
||||
addInstantiated((Plugin) newClass.getConstructor(PluginManager.class).newInstance(this));
|
||||
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public void load() {
|
||||
Set<String> disabledPlugins = abacus.getConfiguration().getDisabledPlugins();
|
||||
for (Plugin plugin : plugins) {
|
||||
if (disabledPlugins.contains(plugin.getClass().getName())) continue;
|
||||
plugin.enable();
|
||||
}
|
||||
listeners.forEach(e -> e.onLoad(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads all the plugins in the PluginManager.
|
||||
*/
|
||||
public void unload() {
|
||||
listeners.forEach(e -> e.onUnload(this));
|
||||
Set<String> disabledPlugins = abacus.getConfiguration().getDisabledPlugins();
|
||||
for (Plugin plugin : plugins) {
|
||||
if (disabledPlugins.contains(plugin.getClass().getName())) continue;
|
||||
plugin.disable();
|
||||
}
|
||||
registeredFunctions.clear();
|
||||
registeredTreeValueFunctions.clear();
|
||||
registeredOperators.clear();
|
||||
registeredTreeValueOperators.clear();
|
||||
registeredNumberImplementations.clear();
|
||||
registeredDocumentation.clear();
|
||||
interfaceImplementationNames.clear();
|
||||
interfaceImplementations.clear();
|
||||
cachedPi.clear();
|
||||
listeners.forEach(e -> e.onUnload(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads all the plugins in the PluginManager.
|
||||
*/
|
||||
public void reload() {
|
||||
unload();
|
||||
load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the functions loaded by the Plugin Manager.
|
||||
*
|
||||
* @return the set of all functions that were loaded.
|
||||
*/
|
||||
public Set<String> getAllFunctions() {
|
||||
return registeredFunctions.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the tree vlaue functions loaded by the PluginManager.
|
||||
*
|
||||
* @return the set of all the tree value functions that were loaded.
|
||||
*/
|
||||
public Set<String> getAllTreeValueFunctions() {
|
||||
return registeredTreeValueFunctions.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the operators loaded by the Plugin Manager.
|
||||
*
|
||||
* @return the set of all operators that were loaded.
|
||||
*/
|
||||
public Set<String> getAllOperators() {
|
||||
return registeredOperators.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the tree value operators loaded by the PluginManager.
|
||||
*
|
||||
* @return the set of all tree value operators that were loaded.
|
||||
*/
|
||||
public Set<String> getAllTreeValueOperators() {
|
||||
return registeredTreeValueOperators.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the number implementations loaded by the Plugin Manager.
|
||||
*
|
||||
* @return the set of all implementations that were loaded.
|
||||
*/
|
||||
public Set<String> getAllNumberImplementations() {
|
||||
return registeredNumberImplementations.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a plugin change listener to this plugin manager.
|
||||
*
|
||||
* @param listener the listener to add.
|
||||
*/
|
||||
public void addListener(PluginListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the plugin change listener from this plugin manager.
|
||||
*
|
||||
* @param listener the listener to remove.
|
||||
*/
|
||||
public void removeListener(PluginListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all the plugin class files that have been
|
||||
* added to the plugin manager.
|
||||
*
|
||||
* @return the list of all the added plugin classes.
|
||||
*/
|
||||
public Set<Class<?>> getLoadedPluginClasses() {
|
||||
return loadedPluginClasses;
|
||||
}
|
||||
|
||||
}
|
||||
843
core/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java
Executable file
843
core/src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java
Executable file
@@ -0,0 +1,843 @@
|
||||
package org.nwapw.abacus.plugin;
|
||||
|
||||
import org.nwapw.abacus.context.MutableEvaluationContext;
|
||||
import org.nwapw.abacus.function.*;
|
||||
import org.nwapw.abacus.number.NaiveNumber;
|
||||
import org.nwapw.abacus.number.NumberInterface;
|
||||
import org.nwapw.abacus.number.PreciseNumber;
|
||||
import org.nwapw.abacus.tree.TreeNode;
|
||||
import org.nwapw.abacus.tree.VariableNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* The plugin providing standard functions such as addition and subtraction to
|
||||
* the calculator.
|
||||
*/
|
||||
public class StandardPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* The set operator.
|
||||
*/
|
||||
public final TreeValueOperator opSet = new TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, TreeNode[] params) {
|
||||
return params.length == 2 && params[0] instanceof VariableNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, TreeNode[] params) {
|
||||
String assignTo = ((VariableNode) params[0]).getVariable();
|
||||
NumberInterface value = params[1].reduce(context.getReducer());
|
||||
if(value == null) throw new EvaluationException();
|
||||
context.setVariable(assignTo, value);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The define operator.
|
||||
*/
|
||||
public final TreeValueOperator opDefine = new TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, TreeNode[] params) {
|
||||
return params.length == 2 && params[0] instanceof VariableNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, TreeNode[] params) {
|
||||
String assignTo = ((VariableNode) params[0]).getVariable();
|
||||
context.setDefinition(assignTo, params[1]);
|
||||
NumberInterface value = params[1].reduce(context.getReducer());
|
||||
if(value == null) throw new EvaluationException();
|
||||
return value;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The addition operator, +
|
||||
*/
|
||||
public static final NumberOperator OP_ADD = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params[0].add(params[1]);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The subtraction operator, -
|
||||
*/
|
||||
public static final NumberOperator OP_SUBTRACT = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params[0].subtract(params[1]);
|
||||
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The negation operator, -
|
||||
*/
|
||||
public static final NumberOperator OP_NEGATE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0) {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params[0].negate();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The multiplication operator, *
|
||||
*/
|
||||
public static final NumberOperator OP_MULTIPLY = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params[0].multiply(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() {
|
||||
MutableEvaluationContext dummyContext = new MutableEvaluationContext(null, this, null);
|
||||
NumberInterface C = FUNCTION_SQRT.apply(dummyContext, 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);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The division operator, /
|
||||
*/
|
||||
public static final NumberOperator OP_DIVIDE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 2 && params[1].compareTo(context.getInheritedNumberImplementation().instanceForString(Integer.toString(0))) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params[0].divide(params[1]);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The factorial operator, !
|
||||
*/
|
||||
public static final NumberOperator OP_FACTORIAL = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0) {
|
||||
//private HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> storedList = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>();
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1
|
||||
&& params[0].fractionalPart().compareTo(context.getInheritedNumberImplementation().instanceForString("0")) == 0
|
||||
&& params[0].signum() >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||
if (params[0].signum() == 0) {
|
||||
return implementation.instanceForString("1");
|
||||
}
|
||||
NumberInterface one = implementation.instanceForString("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 NumberOperator OP_NPR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 2 && params[0].fractionalPart().signum() == 0
|
||||
&& params[1].fractionalPart().signum() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||
if (params[0].compareTo(params[1]) < 0 ||
|
||||
params[0].signum() < 0 ||
|
||||
(params[0].signum() == 0 && params[1].signum() != 0)) return implementation.instanceForString("0");
|
||||
NumberInterface total = implementation.instanceForString("1");
|
||||
NumberInterface multiplyBy = params[0];
|
||||
NumberInterface remainingMultiplications = params[1];
|
||||
NumberInterface halfway = params[0].divide(implementation.instanceForString("2"));
|
||||
if (remainingMultiplications.compareTo(halfway) > 0) {
|
||||
remainingMultiplications = params[0].subtract(remainingMultiplications);
|
||||
}
|
||||
while (remainingMultiplications.signum() > 0) {
|
||||
total = total.multiply(multiplyBy);
|
||||
remainingMultiplications = remainingMultiplications.subtract(implementation.instanceForString("1"));
|
||||
multiplyBy = multiplyBy.subtract(implementation.instanceForString("1"));
|
||||
}
|
||||
return total;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The combination operator.
|
||||
*/
|
||||
public static final NumberOperator OP_NCR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 2 && params[0].fractionalPart().signum() == 0
|
||||
&& params[1].fractionalPart().signum() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return OP_NPR.apply(context, params).divide(OP_FACTORIAL.apply(context, params[1]));
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The absolute value function, abs(-3) = 3
|
||||
*/
|
||||
public static final NumberFunction FUNCTION_ABS = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params[0].multiply(context.getInheritedNumberImplementation().instanceForString(Integer.toString(params[0].signum())));
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The natural log function.
|
||||
*/
|
||||
public static final NumberFunction FUNCTION_LN = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1 && params[0].compareTo(context.getInheritedNumberImplementation().instanceForString("0")) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||
NumberInterface param = params[0];
|
||||
NumberInterface one = implementation.instanceForString("1");
|
||||
int powersOf2 = 0;
|
||||
while (FUNCTION_ABS.apply(context, param.subtract(one)).compareTo(implementation.instanceForString(".1")) >= 0) {
|
||||
if (param.subtract(one).signum() == 1) {
|
||||
param = param.divide(implementation.instanceForString("2"));
|
||||
powersOf2++;
|
||||
if (param.subtract(one).signum() != 1) {
|
||||
break;
|
||||
//No infinite loop for you.
|
||||
}
|
||||
} else {
|
||||
param = param.multiply(implementation.instanceForString("2"));
|
||||
powersOf2--;
|
||||
if (param.subtract(one).signum() != -1) {
|
||||
break;
|
||||
//No infinite loop for you.
|
||||
}
|
||||
}
|
||||
}
|
||||
return getLog2(context.getInheritedNumberImplementation(), param).multiply(implementation.instanceForString(Integer.toString(powersOf2))).add(getLogPartialSum(context, 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(MutableEvaluationContext context, NumberInterface x) {
|
||||
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||
NumberInterface maxError = x.getMaxError();
|
||||
x = x.subtract(implementation.instanceForString("1")); //Terms used are for log(x+1).
|
||||
NumberInterface currentNumerator = x, currentTerm = x, sum = x;
|
||||
int n = 1;
|
||||
while (FUNCTION_ABS.apply(context, currentTerm).compareTo(maxError) > 0) {
|
||||
n++;
|
||||
currentNumerator = currentNumerator.multiply(x).negate();
|
||||
currentTerm = currentNumerator.divide(implementation.instanceForString(Integer.toString(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(NumberImplementation implementation, NumberInterface number) {
|
||||
NumberInterface maxError = number.getMaxError();
|
||||
//NumberInterface errorBound = implementation.instanceForString("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 = implementation.instanceForString("1"), b = a, c = a;
|
||||
NumberInterface sum = implementation.instanceForString("0");
|
||||
NumberInterface one = implementation.instanceForString("1");
|
||||
int n = 0;
|
||||
while (a.compareTo(maxError) >= 1) {
|
||||
n++;
|
||||
a = a.divide(implementation.instanceForString("3"));
|
||||
b = b.divide(implementation.instanceForString("4"));
|
||||
c = one.divide(implementation.instanceForString(Integer.toString(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 NumberFunction FUNCTION_RAND_INT = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return context.getInheritedNumberImplementation().instanceForString(Long.toString(Math.round(Math.random() * params[0].floor().intValue())));
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The caret / pow operator, ^
|
||||
*/
|
||||
public static final NumberOperator OP_CARET = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2) {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
NumberInterface zero = context.getInheritedNumberImplementation().instanceForString("0");
|
||||
return params.length == 2
|
||||
&& !(params[0].compareTo(zero) == 0
|
||||
&& params[1].compareTo(zero) == 0)
|
||||
&& !(params[0].signum() == -1 && params[1].fractionalPart().compareTo(zero) != 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||
NumberInterface zero = implementation.instanceForString("0");
|
||||
if (params[0].compareTo(zero) == 0)
|
||||
return zero;
|
||||
else if (params[1].compareTo(zero) == 0)
|
||||
return implementation.instanceForString("1");
|
||||
//Detect integer bases:
|
||||
if (params[0].fractionalPart().compareTo(implementation.instanceForString("0")) == 0
|
||||
&& FUNCTION_ABS.apply(context, params[1]).compareTo(implementation.instanceForString(Integer.toString(Integer.MAX_VALUE))) < 0
|
||||
&& FUNCTION_ABS.apply(context, params[1]).compareTo(implementation.instanceForString("1")) >= 0) {
|
||||
NumberInterface[] newParams = {params[0], params[1].fractionalPart()};
|
||||
return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(context, newParams));
|
||||
}
|
||||
return FUNCTION_EXP.apply(context, FUNCTION_LN.apply(context, FUNCTION_ABS.apply(context, params[0])).multiply(params[1]));
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The square root function.
|
||||
*/
|
||||
public static final NumberFunction FUNCTION_SQRT = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return OP_CARET.apply(context, params[0], context.getInheritedNumberImplementation().instanceForString(".5"));
|
||||
}
|
||||
};
|
||||
private static final HashMap<NumberImplementation, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
|
||||
/**
|
||||
* The exponential function, exp(1) = e^1 = 2.71...
|
||||
*/
|
||||
public static final NumberFunction FUNCTION_EXP = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||
NumberInterface maxError = params[0].getMaxError();
|
||||
int n = 0;
|
||||
if (params[0].signum() < 0) {
|
||||
NumberInterface[] negatedParams = {params[0].negate()};
|
||||
return implementation.instanceForString("1").divide(applyInternal(context, 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 = implementation.instanceForString("1");
|
||||
NumberInterface nextNumerator = params[0];
|
||||
NumberInterface left = params[0].multiply(implementation.instanceForString("3").intPow(params[0].ceiling().intValue())), right = maxError;
|
||||
do {
|
||||
sum = sum.add(nextNumerator.divide(factorial(implementation, n + 1)));
|
||||
n++;
|
||||
nextNumerator = nextNumerator.multiply(params[0]);
|
||||
left = left.multiply(params[0]);
|
||||
NumberInterface nextN = implementation.instanceForString(Integer.toString(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 NumberFunction functionSin = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||
NumberInterface pi = piFor(params[0].getClass());
|
||||
NumberInterface twoPi = pi.multiply(implementation.instanceForString("2"));
|
||||
NumberInterface theta = getSmallAngle(context, params[0], pi);
|
||||
//System.out.println(theta);
|
||||
if (theta.compareTo(pi.multiply(implementation.instanceForString("1.5"))) >= 0) {
|
||||
theta = theta.subtract(twoPi);
|
||||
} else if (theta.compareTo(pi.divide(implementation.instanceForString("2"))) > 0) {
|
||||
theta = pi.subtract(theta);
|
||||
}
|
||||
//System.out.println(theta);
|
||||
return sinTaylor(context, theta);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The cosine function (the argument is in radians).
|
||||
*/
|
||||
public final NumberFunction functionCos = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return functionSin.apply(context, piFor(params[0].getClass()).divide(context.getInheritedNumberImplementation().instanceForString("2"))
|
||||
.subtract(params[0]));
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The tangent function (the argument is in radians).
|
||||
*/
|
||||
public final NumberFunction functionTan = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return functionSin.apply(context, params[0]).divide(functionCos.apply(context, params[0]));
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The secant function (the argument is in radians).
|
||||
*/
|
||||
public final NumberFunction functionSec = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return context.getInheritedNumberImplementation().instanceForString("1").divide(functionCos.apply(context, params[0]));
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The cosecant function (the argument is in radians).
|
||||
*/
|
||||
public final NumberFunction functionCsc = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return context.getInheritedNumberImplementation().instanceForString("1").divide(functionSin.apply(context, params[0]));
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The cotangent function (the argument is in radians).
|
||||
*/
|
||||
public final NumberFunction functionCot = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return functionCos.apply(context, params[0]).divide(functionSin.apply(context, params[0]));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The arcsine function (return type in radians).
|
||||
*/
|
||||
public final NumberFunction functionArcsin = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1
|
||||
&& FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||
if (FUNCTION_ABS.apply(context, params[0]).compareTo(implementation.instanceForString(".8")) >= 0) {
|
||||
NumberInterface[] newParams = {FUNCTION_SQRT.apply(context, implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
|
||||
return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
|
||||
.subtract(applyInternal(context, newParams)).multiply(implementation.instanceForString(Integer.toString(params[0].signum())));
|
||||
}
|
||||
NumberInterface currentTerm = params[0], sum = currentTerm,
|
||||
multiplier = currentTerm.multiply(currentTerm), summandBound = sum.getMaxError().multiply(implementation.instanceForString("1").subtract(multiplier)),
|
||||
power = currentTerm, coefficient = implementation.instanceForString("1");
|
||||
int exponent = 1;
|
||||
while (FUNCTION_ABS.apply(context, currentTerm).compareTo(summandBound) > 0) {
|
||||
exponent += 2;
|
||||
power = power.multiply(multiplier);
|
||||
coefficient = coefficient.multiply(implementation.instanceForString(Integer.toString(exponent - 2)))
|
||||
.divide(implementation.instanceForString(Integer.toString(exponent - 1)));
|
||||
currentTerm = power.multiply(coefficient).divide(implementation.instanceForString(Integer.toString(exponent)));
|
||||
sum = sum.add(currentTerm);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The arccosine function.
|
||||
*/
|
||||
public final NumberFunction functionArccos = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1 && FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return piFor(params[0].getClass()).divide(context.getInheritedNumberImplementation().instanceForString("2"))
|
||||
.subtract(functionArcsin.apply(context, params));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The arccosecant function.
|
||||
*/
|
||||
public final NumberFunction functionArccsc = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1 && FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
NumberInterface[] reciprocalParamArr = {context.getInheritedNumberImplementation().instanceForString("1").divide(params[0])};
|
||||
return functionArcsin.apply(context, reciprocalParamArr);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The arcsecant function.
|
||||
*/
|
||||
public final NumberFunction functionArcsec = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1 && FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
NumberInterface[] reciprocalParamArr = {context.getInheritedNumberImplementation().instanceForString("1").divide(params[0])};
|
||||
return functionArccos.apply(context, reciprocalParamArr);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The arctangent function.
|
||||
*/
|
||||
public final NumberFunction functionArctan = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||
if (params[0].signum() == -1) {
|
||||
NumberInterface[] negatedParams = {params[0].negate()};
|
||||
return applyInternal(context, negatedParams).negate();
|
||||
}
|
||||
if (params[0].compareTo(implementation.instanceForString("1")) > 0) {
|
||||
NumberInterface[] reciprocalParams = {implementation.instanceForString("1").divide(params[0])};
|
||||
return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
|
||||
.subtract(applyInternal(context, reciprocalParams));
|
||||
}
|
||||
if (params[0].compareTo(implementation.instanceForString("1")) == 0) {
|
||||
return piFor(params[0].getClass()).divide(implementation.instanceForString("4"));
|
||||
}
|
||||
if (params[0].compareTo(implementation.instanceForString(".9")) >= 0) {
|
||||
NumberInterface[] newParams = {params[0].multiply(implementation.instanceForString("2"))
|
||||
.divide(implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
|
||||
return applyInternal(context, newParams).divide(implementation.instanceForString("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(context, currentTerm).compareTo(maxError) > 0) {
|
||||
n += 2;
|
||||
currentPower = currentPower.multiply(multiplier);
|
||||
currentTerm = currentPower.divide(implementation.instanceForString(Integer.toString(n)));
|
||||
sum = sum.add(currentTerm);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The arccotangent function. Range: (0, pi).
|
||||
*/
|
||||
public final NumberFunction functionArccot = new NumberFunction() {
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return piFor(params[0].getClass()).divide(context.getInheritedNumberImplementation().instanceForString("2"))
|
||||
.subtract(functionArctan.apply(context, params));
|
||||
}
|
||||
};
|
||||
|
||||
public StandardPlugin(PluginManager manager) {
|
||||
super(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* A factorial function that uses memoization for each number class; it efficiently
|
||||
* computes factorials of non-negative integers.
|
||||
*
|
||||
* @param implementation type of number to return.
|
||||
* @param n non-negative integer.
|
||||
* @return a number of numClass with value n factorial.
|
||||
*/
|
||||
public static NumberInterface factorial(NumberImplementation implementation, int n) {
|
||||
|
||||
if (!FACTORIAL_LISTS.containsKey(implementation)) {
|
||||
FACTORIAL_LISTS.put(implementation, new ArrayList<>());
|
||||
FACTORIAL_LISTS.get(implementation).add(implementation.instanceForString("1"));
|
||||
FACTORIAL_LISTS.get(implementation).add(implementation.instanceForString("1"));
|
||||
}
|
||||
ArrayList<NumberInterface> list = FACTORIAL_LISTS.get(implementation);
|
||||
if (n >= list.size()) {
|
||||
while (list.size() < n + 16) {
|
||||
list.add(list.get(list.size() - 1).multiply(implementation.instanceForString(Integer.toString(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(MutableEvaluationContext context, 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(context.getInheritedNumberImplementation(), n));
|
||||
sum = sum.add(currentTerm);
|
||||
} while (FUNCTION_ABS.apply(context, 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(MutableEvaluationContext context, NumberInterface phi, NumberInterface pi) {
|
||||
NumberInterface twoPi = pi.multiply(context.getInheritedNumberImplementation().instanceForString("2"));
|
||||
NumberInterface theta = FUNCTION_ABS.apply(context, phi).subtract(twoPi
|
||||
.multiply(FUNCTION_ABS.apply(context, 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);
|
||||
|
||||
registerTreeValueOperator("=", opSet);
|
||||
registerTreeValueOperator(":=", opDefine);
|
||||
|
||||
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() {
|
||||
FACTORIAL_LISTS.clear();
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ package org.nwapw.abacus.tree;
|
||||
public enum TokenType {
|
||||
|
||||
INTERNAL_FUNCTION_END(-1),
|
||||
ANY(0), WHITESPACE(1), COMMA(2), OP(3), NUM(4), FUNCTION(5), OPEN_PARENTH(6), CLOSE_PARENTH(7);
|
||||
ANY(0), WHITESPACE(1), COMMA(2), VARIABLE(3), OP(4), TREE_VALUE_OP(4),
|
||||
NUM(5), FUNCTION(6), TREE_VALUE_FUNCTION(6), OPEN_PARENTH(7), CLOSE_PARENTH(7);
|
||||
|
||||
/**
|
||||
* The priority by which this token gets sorted.
|
||||
114
core/src/main/kotlin/org/nwapw/abacus/Abacus.kt
Normal file
114
core/src/main/kotlin/org/nwapw/abacus/Abacus.kt
Normal file
@@ -0,0 +1,114 @@
|
||||
package org.nwapw.abacus
|
||||
|
||||
import org.nwapw.abacus.config.Configuration
|
||||
import org.nwapw.abacus.context.MutableEvaluationContext
|
||||
import org.nwapw.abacus.context.EvaluationContext
|
||||
import org.nwapw.abacus.number.PromotionManager
|
||||
import org.nwapw.abacus.parsing.LexerTokenizer
|
||||
import org.nwapw.abacus.parsing.ShuntingYardParser
|
||||
import org.nwapw.abacus.parsing.TreeBuilder
|
||||
import org.nwapw.abacus.plugin.PluginManager
|
||||
import org.nwapw.abacus.plugin.StandardPlugin
|
||||
import org.nwapw.abacus.tree.EvaluationResult
|
||||
import org.nwapw.abacus.tree.NumberReducer
|
||||
import org.nwapw.abacus.tree.TreeNode
|
||||
|
||||
/**
|
||||
* Core class to handle all mathematics.
|
||||
*
|
||||
* The main calculator class. This is responsible
|
||||
* for piecing together all of the components, allowing
|
||||
* their interaction with each other.
|
||||
*
|
||||
* @property configuration the configuration to use.
|
||||
*/
|
||||
class Abacus(val configuration: Configuration) {
|
||||
|
||||
/**
|
||||
* The tokenizer used to convert strings into tokens.
|
||||
*/
|
||||
private val tokenizer = LexerTokenizer()
|
||||
/**
|
||||
* Parser the parser used to convert tokens into trees.
|
||||
*/
|
||||
private val parser = ShuntingYardParser()
|
||||
/**
|
||||
* The plugin manager used to handle loading and unloading plugins.
|
||||
*/
|
||||
val pluginManager = PluginManager(this)
|
||||
/**
|
||||
* The tree builder that handles the conversion of strings into trees.
|
||||
*/
|
||||
val treeBuilder = TreeBuilder(tokenizer, parser)
|
||||
/**
|
||||
* The promotion manager used to convert between number implementations.
|
||||
*/
|
||||
val promotionManager = PromotionManager(this)
|
||||
|
||||
/**
|
||||
* The hidden, mutable implementation of the context.
|
||||
*/
|
||||
private val mutableContext = MutableEvaluationContext(numberImplementation = StandardPlugin.IMPLEMENTATION_NAIVE)
|
||||
/**
|
||||
* The base context from which calculations are started.
|
||||
*/
|
||||
val context: EvaluationContext
|
||||
get() = mutableContext
|
||||
|
||||
init {
|
||||
pluginManager.addListener(tokenizer)
|
||||
pluginManager.addListener(parser)
|
||||
pluginManager.addListener(promotionManager)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the Abacus core.
|
||||
*/
|
||||
fun reload(){
|
||||
pluginManager.reload()
|
||||
with(mutableContext) {
|
||||
numberImplementation = pluginManager.numberImplementationFor(configuration.numberImplementation)
|
||||
clearVariables()
|
||||
clearDefinitions()
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Merges the current context with the provided one, updating
|
||||
* variables and the like.
|
||||
* @param context the context to apply.
|
||||
*/
|
||||
fun applyToContext(context: EvaluationContext){
|
||||
mutableContext.apply(context)
|
||||
}
|
||||
/**
|
||||
* Parses a string into a tree structure using the main
|
||||
* tree builder.
|
||||
*
|
||||
* @param input the input string to parse
|
||||
* @return the resulting tree, null if the tree builder or the produced tree are null.
|
||||
*/
|
||||
fun parseString(input: String): TreeNode? = treeBuilder.fromString(input)
|
||||
/**
|
||||
* Evaluates the given tree.
|
||||
*
|
||||
* @param tree the tree to reduce, must not be null.
|
||||
* @return the evaluation result.
|
||||
*/
|
||||
fun evaluateTree(tree: TreeNode): EvaluationResult {
|
||||
return evaluateTreeWithContext(tree, context.mutableSubInstance())
|
||||
}
|
||||
/**
|
||||
* Evaluates the given tree using a different context than
|
||||
* the default one.
|
||||
*
|
||||
* @param tree the tree to reduce, must not be null.
|
||||
* @param context the context to use for the evaluation.
|
||||
* @return the evaluation result.
|
||||
*/
|
||||
fun evaluateTreeWithContext(tree: TreeNode, context: MutableEvaluationContext): EvaluationResult {
|
||||
val newReducer = NumberReducer(this, context)
|
||||
val evaluationValue = tree.reduce(newReducer)
|
||||
return EvaluationResult(evaluationValue, newReducer.context)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.nwapw.abacus.config
|
||||
|
||||
/**
|
||||
* A class that holds information that tells Abacus how to behave.
|
||||
*
|
||||
* Configuration stores information about how Abacus should behave, for
|
||||
* instance, what number implementation it should use and what
|
||||
* plugins should be ignored during loading.
|
||||
*
|
||||
* @property numberImplementation the number implementation Abacus should use for loading.
|
||||
* @param disabledPlugins the plugins that should be disabled and not loaded by the plugin manager.
|
||||
*/
|
||||
open class Configuration(var numberImplementation: String = "<default>", disabledPlugins: Array<String> = emptyArray()) {
|
||||
|
||||
/**
|
||||
* The set of disabled plugins that should be ignored by the plugin manager.
|
||||
*/
|
||||
val disabledPlugins = disabledPlugins.toMutableSet()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.nwapw.abacus.context
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* A delegate to accumulate a collection of elements in a [EvaluationContext] hierarchy.
|
||||
*
|
||||
* ChainAccumulateDelegate is similar to the [ChainSearchDelegate], however, it operates only on collections.
|
||||
* Instead of returning the most recent collection, it merges them into a [Set].
|
||||
*
|
||||
* @param T the type of element in the collection.
|
||||
* @property valueGetter the getter used to access the collection from the context.
|
||||
*/
|
||||
class ChainAccumulateDelegate<out T>(private val valueGetter: EvaluationContext.() -> Collection<T>) {
|
||||
|
||||
operator fun getValue(selfRef: Any, property: KProperty<*>): Set<T> {
|
||||
val set = mutableSetOf<T>()
|
||||
var currentRef: EvaluationContext = selfRef as? EvaluationContext ?: return set
|
||||
while(true) {
|
||||
set.addAll(currentRef.valueGetter())
|
||||
currentRef = currentRef.parent ?: break
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.nwapw.abacus.context
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* A delegate to search a hierarchy made up of [EvaluationContext].
|
||||
*
|
||||
* ChainSearchDelegate is a variable delegate written specifically for use in [EvaluationContext], because
|
||||
* of its hierarchical structure. Variables not found in the current context are searched
|
||||
* for in its parent, which continues recursively until the context being examined has no parent.
|
||||
* This class assists that logic, which is commonly re-used with different variable types, by calling
|
||||
* [valueGetter] on the current context, then its parent, etc.
|
||||
*
|
||||
* @param V the type of the property to search recursively.
|
||||
* @property valueGetter the getter lambda to access the value from the context.
|
||||
*/
|
||||
class ChainSearchDelegate<out V>(private val valueGetter: EvaluationContext.() -> V?) {
|
||||
|
||||
operator fun getValue(selfRef: Any, property: KProperty<*>): V? {
|
||||
var currentRef = selfRef as? EvaluationContext ?: return null
|
||||
var returnedValue = currentRef.valueGetter()
|
||||
while (returnedValue == null) {
|
||||
currentRef = currentRef.parent ?: break
|
||||
returnedValue = currentRef.valueGetter()
|
||||
}
|
||||
return returnedValue
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.nwapw.abacus.context
|
||||
|
||||
import org.nwapw.abacus.number.NumberInterface
|
||||
import org.nwapw.abacus.plugin.NumberImplementation
|
||||
import org.nwapw.abacus.tree.Reducer
|
||||
import org.nwapw.abacus.tree.TreeNode
|
||||
|
||||
/**
|
||||
* A context for the reduction of a [org.nwapw.abacus.tree.TreeNode] into a number.
|
||||
*
|
||||
* The reduction context is used to carry important state information captured at the beginning
|
||||
* of the reduction of an expression, such as the variables and the implementation in use.
|
||||
*
|
||||
* @property parent the parent of this context.
|
||||
* @property numberImplementation the implementation for numbers of this context.
|
||||
* @property reducer the reducer used by this context.
|
||||
*/
|
||||
open class EvaluationContext(val parent: EvaluationContext? = null,
|
||||
open val numberImplementation: NumberImplementation? = null,
|
||||
open val reducer: Reducer<NumberInterface>? = null) {
|
||||
|
||||
/**
|
||||
* The map of variables in this context.
|
||||
*/
|
||||
protected val variableMap = mutableMapOf<String, NumberInterface>()
|
||||
/**
|
||||
* The map of definitions in this context.
|
||||
*/
|
||||
protected val definitionMap = mutableMapOf<String, TreeNode>()
|
||||
|
||||
/**
|
||||
* The set of all variable names defined in this context.
|
||||
*/
|
||||
val variables: Set<String>
|
||||
get() = variableMap.keys
|
||||
|
||||
/**
|
||||
* The set of all definition names defined in this context.
|
||||
*/
|
||||
val definitions: Set<String>
|
||||
get() = definitionMap.keys
|
||||
|
||||
/**
|
||||
* The implementation inherited from this context's parent.
|
||||
*/
|
||||
val inheritedNumberImplementation: NumberImplementation?
|
||||
by ChainSearchDelegate { numberImplementation}
|
||||
|
||||
/**
|
||||
* The reducer inherited from this context's parent.
|
||||
*/
|
||||
val inheritedReducer: Reducer<NumberInterface>?
|
||||
by ChainSearchDelegate { reducer }
|
||||
|
||||
/**
|
||||
* The set of all variables in this context and its parents.
|
||||
*/
|
||||
val inheritedVariables: Set<String> by ChainAccumulateDelegate { variables }
|
||||
|
||||
/**
|
||||
* The set of all definition in this context and its parents.
|
||||
*/
|
||||
val inheritedDefinitions: Set<String> by ChainAccumulateDelegate { definitions }
|
||||
|
||||
/**
|
||||
* Create a new child instance of this context that is mutable.
|
||||
* @return the new child instance.
|
||||
*/
|
||||
fun mutableSubInstance(): MutableEvaluationContext = MutableEvaluationContext(this)
|
||||
|
||||
/**
|
||||
* Gets a variable stored in this context.
|
||||
*/
|
||||
fun getVariable(name: String): NumberInterface? {
|
||||
return variableMap[name] ?: parent?.getVariable(name)
|
||||
}
|
||||
/**
|
||||
* Gets the definition stored in this context.
|
||||
*/
|
||||
fun getDefinition(name: String): TreeNode? {
|
||||
return definitionMap[name] ?: parent?.getDefinition(name)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.nwapw.abacus.context
|
||||
|
||||
import org.nwapw.abacus.number.NumberInterface
|
||||
import org.nwapw.abacus.plugin.NumberImplementation
|
||||
import org.nwapw.abacus.tree.Reducer
|
||||
import org.nwapw.abacus.tree.TreeNode
|
||||
|
||||
/**
|
||||
* A reduction context that is mutable.
|
||||
* @param parent the parent of this context.
|
||||
* @param numberImplementation the number implementation used in this context.
|
||||
* @param reducer the reducer used in this context
|
||||
*/
|
||||
class MutableEvaluationContext(parent: EvaluationContext? = null,
|
||||
numberImplementation: NumberImplementation? = null,
|
||||
reducer: Reducer<NumberInterface>? = null) :
|
||||
EvaluationContext(parent, numberImplementation, reducer) {
|
||||
|
||||
override var numberImplementation: NumberImplementation? = super.numberImplementation
|
||||
override var reducer: Reducer<NumberInterface>? = super.reducer
|
||||
|
||||
/**
|
||||
* Writes data stored in the [other] context over data stored in this one.
|
||||
* @param other the context from which to copy data.
|
||||
*/
|
||||
fun apply(other: EvaluationContext) {
|
||||
if(other.numberImplementation != null) numberImplementation = other.numberImplementation
|
||||
if(other.reducer != null) reducer = other.reducer
|
||||
for(name in other.variables) {
|
||||
setVariable(name, other.getVariable(name) ?: continue)
|
||||
}
|
||||
for(name in other.definitions) {
|
||||
setDefinition(name, other.getDefinition(name) ?: continue)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a variable to a certain [value].
|
||||
* @param name the name of the variable.
|
||||
* @param value the value of the variable.
|
||||
*/
|
||||
fun setVariable(name: String, value: NumberInterface) {
|
||||
variableMap[name] = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a definition to a certain [value].
|
||||
* @param name the name of the definition.
|
||||
* @param value the value of the definition.
|
||||
*/
|
||||
fun setDefinition(name: String, value: TreeNode) {
|
||||
definitionMap[name] = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the variables defined in this context.
|
||||
*/
|
||||
fun clearVariables(){
|
||||
variableMap.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the definitions defined in this context.
|
||||
*/
|
||||
fun clearDefinitions(){
|
||||
definitionMap.clear()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.nwapw.abacus.function
|
||||
|
||||
import org.nwapw.abacus.function.applicable.Applicable
|
||||
import org.nwapw.abacus.number.NumberInterface
|
||||
|
||||
/**
|
||||
* A function that operates on numbers.
|
||||
*
|
||||
* This function takes some number of input NumberInterfaces and returns
|
||||
* another NumberInterface as a result.
|
||||
*/
|
||||
abstract class NumberFunction : Applicable<NumberInterface, NumberInterface>
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.nwapw.abacus.function
|
||||
|
||||
import org.nwapw.abacus.function.applicable.Applicable
|
||||
import org.nwapw.abacus.number.NumberInterface
|
||||
|
||||
/**
|
||||
* An operator that operates on NumberImplementations.
|
||||
*
|
||||
* This is simply an alias for Operator<NumberInterface, NumberInterface>.
|
||||
* @param associativity the associativity of the operator.
|
||||
* @param type the type of the operator (binary, unary, etc)
|
||||
* @param precedence the precedence of the operator.
|
||||
*/
|
||||
abstract class NumberOperator(associativity: OperatorAssociativity, type: OperatorType,
|
||||
precedence: Int) :
|
||||
Operator(associativity, type, precedence),
|
||||
Applicable<NumberInterface, NumberInterface>
|
||||
13
core/src/main/kotlin/org/nwapw/abacus/function/Operator.kt
Normal file
13
core/src/main/kotlin/org/nwapw/abacus/function/Operator.kt
Normal file
@@ -0,0 +1,13 @@
|
||||
package org.nwapw.abacus.function
|
||||
|
||||
/**
|
||||
* A single operator that can be used by Abacus.
|
||||
*
|
||||
* This is a class that holds the information about a single operator, such as a plus or minus.
|
||||
*
|
||||
* @param associativity the associativity of this operator, used for order of operations;.
|
||||
* @param type the type of this operator, used for parsing (infix / prefix / postfix and binary / unary)
|
||||
* @param precedence the precedence of this operator, used for order of operations.
|
||||
*/
|
||||
open class Operator(val associativity: OperatorAssociativity, val type: OperatorType,
|
||||
val precedence: Int)
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.nwapw.abacus.function
|
||||
|
||||
import org.nwapw.abacus.function.applicable.Applicable
|
||||
import org.nwapw.abacus.number.NumberInterface
|
||||
import org.nwapw.abacus.tree.TreeNode
|
||||
|
||||
/**
|
||||
* A function that operates on trees.
|
||||
*
|
||||
* A function that operates on parse tree nodes instead of on already simplified numbers.
|
||||
* Despite this, it returns a number, not a tree.
|
||||
*/
|
||||
abstract class TreeValueFunction : Applicable<TreeNode, NumberInterface>
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.nwapw.abacus.function
|
||||
|
||||
import org.nwapw.abacus.function.applicable.Applicable
|
||||
import org.nwapw.abacus.number.NumberInterface
|
||||
import org.nwapw.abacus.tree.TreeNode
|
||||
|
||||
/**
|
||||
* An operator that operates on trees.
|
||||
*
|
||||
* This operator operates on parse trees, returning, however a number.
|
||||
* @param associativity the associativity of the operator.
|
||||
* @param type the type of the operator (infix, postfix, etc)
|
||||
* @param precedence the precedence of the operator.
|
||||
*/
|
||||
abstract class TreeValueOperator(associativity: OperatorAssociativity, type: OperatorType,
|
||||
precedence: Int) :
|
||||
Operator(associativity, type, precedence),
|
||||
Applicable<TreeNode, NumberInterface>
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.nwapw.abacus.function.applicable
|
||||
|
||||
import org.nwapw.abacus.context.MutableEvaluationContext
|
||||
import org.nwapw.abacus.function.DomainException
|
||||
|
||||
/**
|
||||
* A class that can be applied to arguments.
|
||||
*
|
||||
* Applicable is a class that represents something that can be applied to one or more
|
||||
* arguments of the same type, and returns a single value from that application.
|
||||
* @param <T> the type of the parameters passed to this applicable.
|
||||
* @param <O> the return type of the applicable.
|
||||
*/
|
||||
interface Applicable<in T : Any, out O : Any> {
|
||||
|
||||
/**
|
||||
* Checks if the given applicable can be used with the given parameters.
|
||||
* @param params the parameter array to verify for compatibility.
|
||||
* @return whether the array can be used with applyInternal.
|
||||
*/
|
||||
fun matchesParams(context: MutableEvaluationContext, params: Array<out T>): Boolean
|
||||
|
||||
/**
|
||||
* Applies the applicable object to the given parameters,
|
||||
* without checking for compatibility.
|
||||
* @param params the parameters to apply to.
|
||||
* @return the result of the application.
|
||||
*/
|
||||
fun applyInternal(context: MutableEvaluationContext, params: Array<out T>): O
|
||||
|
||||
/**
|
||||
* If the parameters can be used with this applicable, returns
|
||||
* the result of the application of the applicable to the parameters.
|
||||
* Otherwise, returns null.
|
||||
* @param params the parameters to apply to.
|
||||
* @return the result of the operation, or null if parameters do not match.
|
||||
*/
|
||||
fun apply(context: MutableEvaluationContext, vararg params: T): O {
|
||||
if (!matchesParams(context, params)) throw DomainException()
|
||||
return applyInternal(context, params)
|
||||
}
|
||||
|
||||
}
|
||||
16
core/src/main/kotlin/org/nwapw/abacus/number/NumberUtils.kt
Normal file
16
core/src/main/kotlin/org/nwapw/abacus/number/NumberUtils.kt
Normal file
@@ -0,0 +1,16 @@
|
||||
@file:JvmName("NumberUtils")
|
||||
package org.nwapw.abacus.number
|
||||
|
||||
typealias PromotionFunction = java.util.function.Function<NumberInterface, NumberInterface>
|
||||
typealias PromotionPath = List<PromotionFunction>
|
||||
typealias NumberClass = Class<NumberInterface>
|
||||
|
||||
/**
|
||||
* Promote a number through this path. The functions in this path
|
||||
* are applied in order to the number, and the final result is returned.
|
||||
*
|
||||
* @param from the number to start from.
|
||||
*/
|
||||
fun PromotionPath.promote(from: NumberInterface): NumberInterface {
|
||||
return fold(from, { current, function -> function.apply(current) })
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.nwapw.abacus.number
|
||||
|
||||
import org.nwapw.abacus.Abacus
|
||||
import org.nwapw.abacus.plugin.NumberImplementation
|
||||
import org.nwapw.abacus.plugin.PluginListener
|
||||
import org.nwapw.abacus.plugin.PluginManager
|
||||
import java.util.function.Function
|
||||
|
||||
/**
|
||||
* A class that handles promotions based on priority and the
|
||||
* transition paths each implementation provides.
|
||||
*
|
||||
* @property abacus the Abacus instance to use to access other components.
|
||||
*/
|
||||
class PromotionManager(val abacus: Abacus) : PluginListener {
|
||||
|
||||
/**
|
||||
* The already computed paths
|
||||
*/
|
||||
val computePaths = mutableMapOf<Pair<NumberImplementation, NumberImplementation>, PromotionPath?>()
|
||||
|
||||
/**
|
||||
* Computes a path between a starting and an ending implementation.
|
||||
*
|
||||
* @param from the implementation to start from.
|
||||
* @param to the implementation to get to.
|
||||
* @return the resulting promotion path, or null if it is not found
|
||||
*/
|
||||
fun computePathBetween(from: NumberImplementation, to: NumberImplementation): PromotionPath? {
|
||||
val fromName = abacus.pluginManager.interfaceImplementationNameFor(from.implementation)
|
||||
val toName = abacus.pluginManager.interfaceImplementationNameFor(to.implementation)
|
||||
|
||||
if(fromName == toName) return listOf(Function { it })
|
||||
|
||||
if(from.promotionPaths.containsKey(toName))
|
||||
return listOf(from.promotionPaths[toName] ?: return null)
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* If a path between the given implementations has already been computed, uses
|
||||
* the already calculated path. Otherwise, calls [computePathBetween] to compute a new
|
||||
* path.
|
||||
*
|
||||
* @param from the implementation to start from.
|
||||
* @param to the implementation to get to.
|
||||
* @return the resulting promotion path, or null if it is not found
|
||||
*/
|
||||
fun getPathBetween(from: NumberImplementation, to: NumberImplementation): PromotionPath? {
|
||||
return computePaths.computeIfAbsent(from to to, {
|
||||
computePathBetween(it.first, it.second)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Promote all the numbers in the list to the same number implementation, to ensure
|
||||
* they can be used with each other. Finds the highest priority implementation
|
||||
* in the list, and promotes all other numbers to it.
|
||||
*
|
||||
* @param numbers the numbers to promote.
|
||||
* @return the resulting promotion result.
|
||||
*/
|
||||
fun promote(vararg numbers: NumberInterface): PromotionResult? {
|
||||
val pluginManager = abacus.pluginManager
|
||||
val implementations = numbers.map { pluginManager.interfaceImplementationFor(it.javaClass) }
|
||||
val highestPriority = implementations.sortedBy { it.priority }.last()
|
||||
return PromotionResult(items = numbers.map {
|
||||
if(it.javaClass == highestPriority.implementation) it
|
||||
else getPathBetween(pluginManager.interfaceImplementationFor(it.javaClass), highestPriority)
|
||||
?.promote(it) ?: return null
|
||||
}.toTypedArray(), promotedTo = highestPriority)
|
||||
}
|
||||
|
||||
override fun onLoad(manager: PluginManager?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onUnload(manager: PluginManager?) {
|
||||
computePaths.clear()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.nwapw.abacus.number
|
||||
|
||||
import org.nwapw.abacus.plugin.NumberImplementation
|
||||
|
||||
/**
|
||||
* The result of promoting an array of NumberInterfaces.
|
||||
*
|
||||
* @param promotedTo the implementation to which the numbers were promoted.
|
||||
* @param items the items the items resulting from the promotion.
|
||||
*/
|
||||
data class PromotionResult(val promotedTo: NumberImplementation, val items: Array<NumberInterface>)
|
||||
20
core/src/main/kotlin/org/nwapw/abacus/tree/BinaryNode.kt
Normal file
20
core/src/main/kotlin/org/nwapw/abacus/tree/BinaryNode.kt
Normal file
@@ -0,0 +1,20 @@
|
||||
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.
|
||||
*/
|
||||
abstract class BinaryNode(val operation: String, val left: TreeNode? = null, val right: TreeNode?) : TreeNode() {
|
||||
|
||||
override fun toString(): String {
|
||||
return "(" + (left?.toString() ?: "null") + operation + (right?.toString() ?: "null") + ")"
|
||||
}
|
||||
|
||||
}
|
||||
29
core/src/main/kotlin/org/nwapw/abacus/tree/CallNode.kt
Normal file
29
core/src/main/kotlin/org/nwapw/abacus/tree/CallNode.kt
Normal file
@@ -0,0 +1,29 @@
|
||||
package org.nwapw.abacus.tree
|
||||
|
||||
/**
|
||||
* Represents a more generic function call.
|
||||
*
|
||||
* This class does not specify how it should be reduced, allowing other classes
|
||||
* to extend this functionality.
|
||||
*
|
||||
* @param callTo the name of the things being called.
|
||||
*/
|
||||
abstract class CallNode(val callTo: String) : TreeNode() {
|
||||
|
||||
/**
|
||||
* The list of children this node has.
|
||||
*/
|
||||
val children: MutableList<TreeNode> = mutableListOf()
|
||||
|
||||
override fun toString(): String {
|
||||
val buffer = StringBuffer()
|
||||
buffer.append(callTo)
|
||||
buffer.append("(")
|
||||
for (i in 0 until children.size) {
|
||||
buffer.append(children[i].toString())
|
||||
buffer.append(if (i != children.size - 1) ", " else ")")
|
||||
}
|
||||
return buffer.toString()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.nwapw.abacus.tree
|
||||
|
||||
import org.nwapw.abacus.context.MutableEvaluationContext
|
||||
import org.nwapw.abacus.number.NumberInterface
|
||||
|
||||
data class EvaluationResult(val value: NumberInterface?, val resultingContext: MutableEvaluationContext)
|
||||
18
core/src/main/kotlin/org/nwapw/abacus/tree/FunctionNode.kt
Normal file
18
core/src/main/kotlin/org/nwapw/abacus/tree/FunctionNode.kt
Normal file
@@ -0,0 +1,18 @@
|
||||
package org.nwapw.abacus.tree
|
||||
|
||||
/**
|
||||
* A tree node that holds a function call.
|
||||
*
|
||||
* The function call node can hold any number of children, and passes the to the appropriate reducer,
|
||||
* but that is its sole purpose.
|
||||
*
|
||||
* @param function the function string.
|
||||
*/
|
||||
class FunctionNode(function: String) : CallNode(function) {
|
||||
|
||||
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
|
||||
val children = Array<Any>(children.size, { children[it].reduce(reducer) ?: return null; })
|
||||
return reducer.reduceNode(this, *children)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.nwapw.abacus.tree
|
||||
|
||||
/**
|
||||
* A binary operator node that reduces its children.
|
||||
*
|
||||
* NumberBinaryNode operates by simply reducing its children and
|
||||
* then using the result of that reduction to reduce itself.
|
||||
*
|
||||
* @param operation the operation this node performs.
|
||||
* @param left the left child of this node.
|
||||
* @param right the right child of this node.
|
||||
*/
|
||||
class NumberBinaryNode(operation: String, left: TreeNode?, right: TreeNode?)
|
||||
: BinaryNode(operation, left, right) {
|
||||
|
||||
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
|
||||
val left = left?.reduce(reducer) ?: return null
|
||||
val right = right?.reduce(reducer) ?: return null
|
||||
return reducer.reduceNode(this, left, right)
|
||||
}
|
||||
|
||||
}
|
||||
21
core/src/main/kotlin/org/nwapw/abacus/tree/NumberNode.kt
Normal file
21
core/src/main/kotlin/org/nwapw/abacus/tree/NumberNode.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
package org.nwapw.abacus.tree
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
class NumberNode(val number: String) : TreeNode() {
|
||||
|
||||
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
|
||||
return reducer.reduceNode(this)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return number
|
||||
}
|
||||
|
||||
}
|
||||
63
core/src/main/kotlin/org/nwapw/abacus/tree/NumberReducer.kt
Normal file
63
core/src/main/kotlin/org/nwapw/abacus/tree/NumberReducer.kt
Normal file
@@ -0,0 +1,63 @@
|
||||
package org.nwapw.abacus.tree
|
||||
|
||||
import org.nwapw.abacus.Abacus
|
||||
import org.nwapw.abacus.context.EvaluationContext
|
||||
import org.nwapw.abacus.number.NumberInterface
|
||||
|
||||
class NumberReducer(val abacus: Abacus, context: EvaluationContext) : Reducer<NumberInterface> {
|
||||
|
||||
val context = context.mutableSubInstance()
|
||||
|
||||
init {
|
||||
this.context.reducer = this
|
||||
}
|
||||
|
||||
override fun reduceNode(treeNode: TreeNode, vararg children: Any): NumberInterface? {
|
||||
val promotionManager = abacus.promotionManager
|
||||
return when(treeNode){
|
||||
is NumberNode -> {
|
||||
context.inheritedNumberImplementation?.instanceForString(treeNode.number)
|
||||
}
|
||||
is VariableNode -> {
|
||||
val variable = context.getVariable(treeNode.variable)
|
||||
if(variable != null) return variable
|
||||
val definition = context.getDefinition(treeNode.variable)
|
||||
if(definition != null) return definition.reduce(this)
|
||||
null
|
||||
}
|
||||
is NumberUnaryNode -> {
|
||||
val child = children[0] as NumberInterface
|
||||
context.numberImplementation = abacus.pluginManager.interfaceImplementationFor(child.javaClass)
|
||||
abacus.pluginManager.operatorFor(treeNode.operation)
|
||||
.apply(context, child)
|
||||
}
|
||||
is NumberBinaryNode -> {
|
||||
val left = children[0] as NumberInterface
|
||||
val right = children[1] as NumberInterface
|
||||
val promotionResult = promotionManager.promote(left, right) ?: return null
|
||||
context.numberImplementation = promotionResult.promotedTo
|
||||
abacus.pluginManager.operatorFor(treeNode.operation).apply(context, *promotionResult.items)
|
||||
}
|
||||
is FunctionNode -> {
|
||||
val promotionResult = promotionManager
|
||||
.promote(*children.map { it as NumberInterface }.toTypedArray()) ?: return null
|
||||
context.numberImplementation = promotionResult.promotedTo
|
||||
abacus.pluginManager.functionFor(treeNode.callTo).apply(context, *promotionResult.items)
|
||||
}
|
||||
is TreeValueUnaryNode -> {
|
||||
abacus.pluginManager.treeValueOperatorFor(treeNode.operation)
|
||||
.apply(context, treeNode.applyTo ?: return null)
|
||||
}
|
||||
is TreeValueBinaryNode -> {
|
||||
abacus.pluginManager.treeValueOperatorFor(treeNode.operation)
|
||||
.apply(context, treeNode.left ?: return null, treeNode.right ?: return null)
|
||||
}
|
||||
is TreeValueFunctionNode -> {
|
||||
abacus.pluginManager.treeValueFunctionFor(treeNode.callTo)
|
||||
.apply(context, *treeNode.children.toTypedArray())
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.nwapw.abacus.tree
|
||||
|
||||
/**
|
||||
* A unary operator node that reduces its children.
|
||||
*
|
||||
* NumberUnaryNode operates by simply reducing its child,
|
||||
* and using the result of that reduction to reduce itself.
|
||||
* @param operation the operation this node performs.
|
||||
* @param child the child this node should be applied to.
|
||||
*/
|
||||
class NumberUnaryNode(operation: String, child: TreeNode?)
|
||||
: UnaryNode(operation, child) {
|
||||
|
||||
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
|
||||
val child = applyTo?.reduce(reducer) ?: return null
|
||||
return reducer.reduceNode(this, child)
|
||||
}
|
||||
|
||||
}
|
||||
19
core/src/main/kotlin/org/nwapw/abacus/tree/Reducer.kt
Normal file
19
core/src/main/kotlin/org/nwapw/abacus/tree/Reducer.kt
Normal 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?
|
||||
|
||||
}
|
||||
10
core/src/main/kotlin/org/nwapw/abacus/tree/TreeNode.kt
Normal file
10
core/src/main/kotlin/org/nwapw/abacus/tree/TreeNode.kt
Normal 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?
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.nwapw.abacus.tree
|
||||
|
||||
/**
|
||||
* A tree node that represents a binary tree value operator.
|
||||
*
|
||||
*
|
||||
* The tree value operators operate on trees, and so this
|
||||
* node does not reduce its children. It is up to the implementation to handle
|
||||
* reduction.
|
||||
* @param operation the operation this node performs.
|
||||
* @param left the left child of this node.
|
||||
* @param right the right child of this node.
|
||||
*/
|
||||
class TreeValueBinaryNode(operation: String, left: TreeNode?, right: TreeNode?)
|
||||
: BinaryNode(operation, left, right) {
|
||||
|
||||
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
|
||||
return reducer.reduceNode(this)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.nwapw.abacus.tree
|
||||
|
||||
/**
|
||||
* A tree node that represents a tree value function call.
|
||||
*
|
||||
* This is in many ways similar to a simple FunctionNode, and the distinction
|
||||
* is mostly to help the reducer. Besides that, this class also does not
|
||||
* even attempt to reduce its children.
|
||||
*/
|
||||
class TreeValueFunctionNode(name: String) : CallNode(name) {
|
||||
|
||||
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
|
||||
return reducer.reduceNode(this)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.nwapw.abacus.tree
|
||||
|
||||
/**
|
||||
* A tree node that represents a unary tree value operator.
|
||||
*
|
||||
* The tree value operators operate on trees, and so this
|
||||
* node does not reduce its children. It is up to the implementation to handle
|
||||
* reduction.
|
||||
* @param operation the operation this node performs.
|
||||
* @param child the node the operation should be applied to.
|
||||
*/
|
||||
class TreeValueUnaryNode(operation: String, child: TreeNode?)
|
||||
: UnaryNode(operation, child) {
|
||||
|
||||
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
|
||||
return reducer.reduceNode(this);
|
||||
}
|
||||
|
||||
}
|
||||
18
core/src/main/kotlin/org/nwapw/abacus/tree/UnaryNode.kt
Normal file
18
core/src/main/kotlin/org/nwapw/abacus/tree/UnaryNode.kt
Normal file
@@ -0,0 +1,18 @@
|
||||
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.
|
||||
*/
|
||||
abstract class UnaryNode(val operation: String, val applyTo: TreeNode? = null) : TreeNode() {
|
||||
|
||||
override fun toString(): String {
|
||||
return "(" + (applyTo?.toString() ?: "null") + ")" + operation
|
||||
}
|
||||
|
||||
}
|
||||
21
core/src/main/kotlin/org/nwapw/abacus/tree/VariableNode.kt
Normal file
21
core/src/main/kotlin/org/nwapw/abacus/tree/VariableNode.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
package org.nwapw.abacus.tree
|
||||
|
||||
/**
|
||||
* A tree node that holds a placeholder variable.
|
||||
*
|
||||
* This node holds a variable string, and acts similarly to a number,
|
||||
* with the key difference of not actually holding a value at runtime.
|
||||
*
|
||||
* @param variable the actual variable name that this node represents.
|
||||
*/
|
||||
class VariableNode(val variable: String) : TreeNode() {
|
||||
|
||||
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
|
||||
return reducer.reduceNode(this)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return variable
|
||||
}
|
||||
|
||||
}
|
||||
111
core/src/test/java/org/nwapw/abacus/tests/CalculationTests.java
Executable file
111
core/src/test/java/org/nwapw/abacus/tests/CalculationTests.java
Executable file
@@ -0,0 +1,111 @@
|
||||
package org.nwapw.abacus.tests;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.nwapw.abacus.Abacus;
|
||||
import org.nwapw.abacus.config.Configuration;
|
||||
import org.nwapw.abacus.function.DomainException;
|
||||
import org.nwapw.abacus.number.NumberInterface;
|
||||
import org.nwapw.abacus.plugin.StandardPlugin;
|
||||
import org.nwapw.abacus.tree.TreeNode;
|
||||
|
||||
public class CalculationTests {
|
||||
|
||||
private static Abacus abacus = new Abacus(new Configuration( "precise", new String[]{}));
|
||||
|
||||
@BeforeClass
|
||||
public static void prepareTests() {
|
||||
abacus.getPluginManager().addInstantiated(new StandardPlugin(abacus.getPluginManager()));
|
||||
abacus.reload();
|
||||
}
|
||||
|
||||
private void testOutput(String input, String parseOutput, String output) {
|
||||
TreeNode parsedTree = abacus.parseString(input);
|
||||
Assert.assertNotNull(parsedTree);
|
||||
Assert.assertEquals(parsedTree.toString(), parseOutput);
|
||||
NumberInterface result = abacus.evaluateTree(parsedTree).getValue();
|
||||
Assert.assertNotNull(result);
|
||||
Assert.assertTrue(result.toString().startsWith(output));
|
||||
}
|
||||
|
||||
private void testDomainException(String input, String parseOutput) {
|
||||
TreeNode parsedTree = abacus.parseString(input);
|
||||
Assert.assertNotNull(parsedTree);
|
||||
Assert.assertEquals(parsedTree.toString(), parseOutput);
|
||||
try {
|
||||
abacus.evaluateTree(parsedTree);
|
||||
Assert.fail("Function did not throw DomainException.");
|
||||
} catch (DomainException e){ }
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddition() {
|
||||
testOutput("9.5+10", "(9.5+10)", "19.5");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubtraction() {
|
||||
testOutput("9.5-10", "(9.5-10)", "-0.5");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiplication() {
|
||||
testOutput("9.5*10", "(9.5*10)", "95");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDivision() {
|
||||
testOutput("9.5/2", "(9.5/2)", "4.75");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegation() {
|
||||
testOutput("-9.5", "(9.5)`", "-9.5");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFactorial() {
|
||||
testOutput("7!", "(7)!", "5040");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAbs() {
|
||||
testOutput("abs(-1)", "abs((1)`)", "1");
|
||||
testOutput("abs(1)", "abs(1)", "1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLn() {
|
||||
testDomainException("ln(-1)", "ln((1)`)");
|
||||
testOutput("ln2", "ln(2)", "0.6931471805599453094172321214581765680755");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSqrt() {
|
||||
testOutput("sqrt0", "sqrt(0)", "0");
|
||||
testOutput("sqrt4", "sqrt(4)", "2");
|
||||
testOutput("sqrt2", "sqrt(2)", "1.4142135623730950488016887242096980785696");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExp() {
|
||||
testOutput("exp0", "exp(0)", "1");
|
||||
testOutput("exp1", "exp(1)", "2.718281828459045235360287471352662497757247");
|
||||
testOutput("exp300", "exp(300)", "1.9424263952412559365842088360176992193662086");
|
||||
testOutput("exp(-500)", "exp((500)`)", "7.1245764067412855315491573771227552469277568");
|
||||
}
|
||||
|
||||
@Test
|
||||
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");
|
||||
testDomainException("0^0", "(0^0)");
|
||||
testDomainException("(-13)^.9999", "((13)`^.9999)");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,10 +4,9 @@ import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.nwapw.abacus.Abacus;
|
||||
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.config.Configuration;
|
||||
import org.nwapw.abacus.context.MutableEvaluationContext;
|
||||
import org.nwapw.abacus.function.*;
|
||||
import org.nwapw.abacus.lexing.pattern.Match;
|
||||
import org.nwapw.abacus.number.NumberInterface;
|
||||
import org.nwapw.abacus.parsing.LexerTokenizer;
|
||||
@@ -18,26 +17,48 @@ import java.util.List;
|
||||
|
||||
public class TokenizerTests {
|
||||
|
||||
private static Abacus abacus = new Abacus();
|
||||
private static Abacus abacus = new Abacus(new Configuration("precise", new String[]{}));
|
||||
private static LexerTokenizer lexerTokenizer = new LexerTokenizer();
|
||||
private static Function subtractFunction = new Function() {
|
||||
private static NumberFunction subtractFunction = new NumberFunction() {
|
||||
@Override
|
||||
protected boolean matchesParams(NumberInterface[] params) {
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params.length == 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NumberInterface applyInternal(NumberInterface[] params) {
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return params[0].subtract(params[1]);
|
||||
}
|
||||
};
|
||||
private static Plugin testPlugin = new Plugin(abacus.getPluginManager()) {
|
||||
@Override
|
||||
public void onEnable() {
|
||||
registerOperator("+", new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX,
|
||||
0, subtractFunction));
|
||||
registerOperator("-", new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX,
|
||||
0, subtractFunction));
|
||||
registerOperator("+", new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX,
|
||||
0) {
|
||||
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return subtractFunction.apply(context, params);
|
||||
}
|
||||
});
|
||||
registerOperator("-", new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX,
|
||||
0) {
|
||||
|
||||
@Override
|
||||
public boolean matchesParams(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberInterface applyInternal(MutableEvaluationContext context, NumberInterface[] params) {
|
||||
return subtractFunction.apply(context, params);
|
||||
}
|
||||
});
|
||||
registerFunction("subtract", subtractFunction);
|
||||
}
|
||||
|
||||
@@ -59,7 +80,7 @@ public class TokenizerTests {
|
||||
public static void prepareTests() {
|
||||
abacus.getPluginManager().addListener(lexerTokenizer);
|
||||
abacus.getPluginManager().addInstantiated(testPlugin);
|
||||
abacus.getPluginManager().load();
|
||||
abacus.reload();
|
||||
}
|
||||
|
||||
@Test
|
||||
7
docs/404.html
Normal file
7
docs/404.html
Normal 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
27
docs/Gemfile
Normal 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
58
docs/Gemfile.lock
Normal 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
43
docs/_config.yml
Normal 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/
|
||||
0
docs/_includes/footer.html
Normal file
0
docs/_includes/footer.html
Normal file
17
docs/_includes/head.html
Normal file
17
docs/_includes/head.html
Normal 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>
|
||||
|
||||
10
docs/_includes/header.html
Normal file
10
docs/_includes/header.html
Normal 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
17
docs/_layouts/base.html
Normal 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>
|
||||
159
docs/_layouts/home.html
Normal file
159
docs/_layouts/home.html
Normal file
@@ -0,0 +1,159 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
{% include head.html %}
|
||||
<style>
|
||||
body {
|
||||
margin: 0px;
|
||||
margin-top: 50px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: "Source Code Pro"
|
||||
}
|
||||
|
||||
img#logo {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
img#image_preview {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
max-width: 432px;
|
||||
}
|
||||
|
||||
div#buttons {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: white;
|
||||
color: #06e8a4;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background-color: #06e8a4;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.fullwidth {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.fullwidth img {
|
||||
max-width: 100%;
|
||||
max-height: 450px;
|
||||
margin: auto;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
div.white {
|
||||
background-color: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
div.green {
|
||||
background-color: #06e8a4;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.fullwidth div.double {
|
||||
height: 100%;
|
||||
text-align: left;
|
||||
width: 50%;
|
||||
box-sizing: border-box;
|
||||
padding: 40px;
|
||||
float: left;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
@media (max-width: 750px) {
|
||||
div.fullwidth div.double {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
}
|
||||
div.fullwidth img {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
div.fullwidth div.double h1, h2, h3, h4, h5, h6 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
<body>
|
||||
<img src="https://raw.githubusercontent.com/DanilaFe/abacus/master/image/logo.png" id="logo">
|
||||
<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" id="image_preview"/>
|
||||
<h2>Features</h2>
|
||||
<div class="fullwidth white">
|
||||
<div class="double">
|
||||
<img src="https://i.imgur.com/gmGJBBK.png">
|
||||
</div>
|
||||
<div class="double">
|
||||
<h2>Precision</h2>
|
||||
Abacus uses a mathematical tool called Taylor Series to determine values
|
||||
as accurate as the user desires. Of course, this comes with some
|
||||
performance issues with larger numbers. However, Abacus has been
|
||||
tested to generate the value of e correctly to a thousand digits.
|
||||
</div>
|
||||
</div>
|
||||
<div class="fullwidth green">
|
||||
<div class="double">
|
||||
<h2>Configurable and Customizable</h2>
|
||||
The very first idea for Abacus was inspired by how difficult it was
|
||||
to program a TI-84 calculator. Only two languages were available, TI-BASIC
|
||||
and Assembly, the latter having virtually no documentation. Determined
|
||||
to be better than a TI-84, Abacus implemented a plugin system that allows
|
||||
users to easily create and add plugins written in the same programming
|
||||
language as Abacus itself - Java. These plugins can access the full
|
||||
power of the language, and implement their own ways of handling numbers,
|
||||
as well as their own functions and even operators.<br><br>
|
||||
Besides the ability to add plugins, Abacus also adds some general
|
||||
options that can be used to make the user's experience more pleasant.
|
||||
For instance, it allows for a computation limit to be set in order
|
||||
to prevent excessively long evaluation: 8!!! is, for example, an expression
|
||||
that even Wolfram Alpha doesn't compute accurately, and will never finish
|
||||
on Abacus (it's simply too large). The computation limit will allow Abacus
|
||||
to kill a computation if it takes too long. Support for user-definable
|
||||
precision is also planned.
|
||||
</div>
|
||||
<div class="double">
|
||||
<img src="https://i.imgur.com/JzenWPV.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="fullwidth white">
|
||||
<div class="double">
|
||||
<img src="https://i.imgur.com/jY17I3A.png">
|
||||
</div>
|
||||
<div class="double">
|
||||
<h2>Built-in Documentation</h2>
|
||||
Abacus plugins are given a mechanism to register documentation for
|
||||
the functions that they provide. The Abacus GUI displays these
|
||||
functions in a searchable list, allowing the user to read the parameters
|
||||
that have to be supplied to each function, as well as learn about
|
||||
its return value.<br><br>
|
||||
The search finds functions not only by their names, but also by relevant
|
||||
terms mentioned in the function's description, thus allowing related
|
||||
functions to be displayed together.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
5
docs/_layouts/page.html
Normal file
5
docs/_layouts/page.html
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
layout: base
|
||||
---
|
||||
<h1>{{ page.title }}</h1>
|
||||
{{ content }}
|
||||
27
docs/_pages/about.md
Normal file
27
docs/_pages/about.md
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
in_header: true
|
||||
layout: page
|
||||
title: About
|
||||
permalink: /about/
|
||||
---
|
||||
|
||||
## So... what IS Abacus?
|
||||
It's a calculator. Obviously. But what makes it better than
|
||||
what already exists? There's a few things. Abacus is:
|
||||
* Programmable, and not in TI Basic.
|
||||
* Precise. With the "precise" option, Abacus can keep up to 50 significant figures.
|
||||
* Capable. Ever wonder what 2<sup>700</sup> is? How about 8!!? Abacus can tell you!
|
||||
* Offline. While Wolfram Alpha can do powerful math, it needs internet connection!
|
||||
* Built for the desktop. Why use buttons on the screen when there's buttons on the keyboard?
|
||||
* Open source. Don't like something? Help is always welcome!
|
||||
|
||||
## Why was Abacus made?
|
||||
The initial project was proposed for the [Northwest Advanced Programming Workshop](http://nwapw.org/about/).
|
||||
You can read the project proposal on the main GitHub page, although the idea has
|
||||
changed quite a bit, mostly in shifting from "fast" to "precise".
|
||||
|
||||
## What is Abacus made with?
|
||||
Java and Kotlin. Java provides a good layer of abstraction and a great standard
|
||||
library, while Kotlin allows for the reduction of boilerplate code and null
|
||||
safety. Using JVM-based languages also allows Abacus to expose its entire
|
||||
API to plugins, and load them at runtime.
|
||||
34
docs/_pages/download.md
Normal file
34
docs/_pages/download.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
in_header: true
|
||||
layout: page
|
||||
title: Download
|
||||
permalink: /download/
|
||||
---
|
||||
|
||||
Currently, we do not provide standalone executables due to our unfamiliarity with
|
||||
including 3rd-party software. Abacus uses a number of open source libraries,
|
||||
and we do not want to breach the license terms for any of them. As soon as
|
||||
as we figure out the correct way to distribute Abacus, we will make a
|
||||
standalone distribution available. In the meantime, please use the below
|
||||
steps to run Abacus from source.
|
||||
|
||||
## Getting the Code
|
||||
Abacus is an open source project, and is distributed under the MIT license.
|
||||
If you would like to download the source code, simply clone it from
|
||||
[GitHub](https://github.com/DanilaFe/abacus).
|
||||
Alternatively, if you don't want the bleeding edge version, check out the
|
||||
[releases](https://github.com/DanilaFe/abacus/releases).
|
||||
|
||||
## Running from Source
|
||||
Once you have unpacked the source code, you can simply run it from
|
||||
the command line via the shell command:
|
||||
```
|
||||
./gradlew run
|
||||
```
|
||||
If you're on Windows, the command is similar:
|
||||
```
|
||||
gradlew run
|
||||
```
|
||||
This should download a distribution of Gradle, a build system that is
|
||||
used to compile Abacus. After some time, the Abacus window should appear.
|
||||
From there, you can use it normally.
|
||||
119
docs/assets/css/main.scss
Normal file
119
docs/assets/css/main.scss
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
---
|
||||
@import url('https://fonts.googleapis.com/css?family=Source+Code+Pro|Open+Sans|Raleway');
|
||||
|
||||
$background-color: #19d69e;
|
||||
$code-color: #efefef;
|
||||
$accent-color: #00AFE8;
|
||||
$clear-color: white;
|
||||
$title-font: "Open Sans";
|
||||
$text-font: "Raleway";
|
||||
$code-font: "Source Code Pro";
|
||||
$max-width: 850px;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $background-color;
|
||||
|
||||
&.button {
|
||||
display: inline-block;
|
||||
background-color: $background-color;
|
||||
color: $clear-color;
|
||||
padding: 10px;
|
||||
text-decoration: none;
|
||||
border-radius: 2px;
|
||||
|
||||
margin: 10px;
|
||||
transition: background-color .25s;
|
||||
|
||||
&:hover {
|
||||
background-color: $clear-color;
|
||||
color: $background-color;
|
||||
}
|
||||
|
||||
&.inverted {
|
||||
background-color: $clear-color;
|
||||
color: $background-color;
|
||||
|
||||
&:hover {
|
||||
background-color: $background-color;
|
||||
color: $clear-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font: {
|
||||
family: $title-font;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
nav {
|
||||
box-sizing: border-box;
|
||||
background-color: $clear-color;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $background-color;
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
|
||||
&.primary-link {
|
||||
font-size: 30px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $accent-color;
|
||||
}
|
||||
|
||||
transition: color .25s;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: $background-color;
|
||||
font: {
|
||||
family: $text-font;
|
||||
}
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.center {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: $max-width;
|
||||
|
||||
@media (min-width: $max-width) {
|
||||
margin: {
|
||||
left: auto;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 20px;
|
||||
padding: 30px;
|
||||
background-color: $clear-color;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: $code-color;
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
font-family: $code-font;
|
||||
|
||||
pre & {
|
||||
padding: 10px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
6
docs/index.md
Normal file
6
docs/index.md
Normal 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
|
||||
---
|
||||
8
fx/build.gradle
Normal file
8
fx/build.gradle
Normal file
@@ -0,0 +1,8 @@
|
||||
apply plugin: 'application'
|
||||
|
||||
dependencies {
|
||||
compile 'com.moandjiezana.toml:toml4j:0.7.1'
|
||||
compile project(':core')
|
||||
}
|
||||
|
||||
mainClassName = 'org.nwapw.abacus.fx.AbacusApplication'
|
||||
41
fx/src/main/java/org/nwapw/abacus/fx/AbacusApplication.java
Normal file
41
fx/src/main/java/org/nwapw/abacus/fx/AbacusApplication.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package org.nwapw.abacus.fx;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
/**
|
||||
* The main application class for JavaFX responsible for loading
|
||||
* and displaying the fxml file.
|
||||
*/
|
||||
public class AbacusApplication extends Application {
|
||||
|
||||
/**
|
||||
* The controller currently managing the 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"));
|
||||
Parent parent = loader.load();
|
||||
controller = loader.getController();
|
||||
Scene mainScene = new Scene(parent, 320, 480);
|
||||
primaryStage.setScene(mainScene);
|
||||
primaryStage.setTitle("Abacus");
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
super.stop();
|
||||
controller.performStop();
|
||||
}
|
||||
|
||||
}
|
||||
380
fx/src/main/java/org/nwapw/abacus/fx/AbacusController.java
Normal file
380
fx/src/main/java/org/nwapw/abacus/fx/AbacusController.java
Normal file
@@ -0,0 +1,380 @@
|
||||
package org.nwapw.abacus.fx;
|
||||
|
||||
import javafx.application.Platform;
|
||||
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;
|
||||
import javafx.scene.text.Text;
|
||||
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.function.DomainException;
|
||||
import org.nwapw.abacus.number.*;
|
||||
import org.nwapw.abacus.plugin.ClassFinder;
|
||||
import org.nwapw.abacus.plugin.PluginListener;
|
||||
import org.nwapw.abacus.plugin.PluginManager;
|
||||
import org.nwapw.abacus.plugin.StandardPlugin;
|
||||
import org.nwapw.abacus.tree.EvaluationResult;
|
||||
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;
|
||||
|
||||
|
||||
/**
|
||||
* The controller for the abacus FX UI, responsible
|
||||
* for all the user interaction.
|
||||
*/
|
||||
public class AbacusController implements PluginListener {
|
||||
|
||||
/**
|
||||
* The file used for saving and loading configuration.
|
||||
*/
|
||||
public static final File CONFIG_FILE = new File("config.toml");
|
||||
/**
|
||||
* The title for the apply alert dialog.
|
||||
*/
|
||||
private static final String APPLY_MSG_TITLE = "\"Apply\" Needed";
|
||||
/**
|
||||
* The text for the header of the apply alert dialog.
|
||||
*/
|
||||
private static final String APPLY_MSG_HEADER = "The settings have not been applied.";
|
||||
/**
|
||||
* The text for the dialog that is shown if settings haven't been applied.
|
||||
*/
|
||||
private static final String APPLY_MSG_TEXT = "You have made changes to the configuration, however, you haven't pressed \"Apply\". " +
|
||||
"The changes to the configuration will not be present in the calculator until \"Apply\" is pressed.";
|
||||
/**
|
||||
* Constant string that is displayed if the text could not be lexed or parsed.
|
||||
*/
|
||||
private static final String ERR_SYNTAX = "Syntax Error";
|
||||
/**
|
||||
* Constant string that is displayed if the tree could not be reduced.
|
||||
*/
|
||||
private static final String ERR_EVAL = "Evaluation Error";
|
||||
/**
|
||||
* Constant string that is displayed if the calculations are stopped before they are done.
|
||||
*/
|
||||
private static final String ERR_STOP = "Stopped";
|
||||
/**
|
||||
* Constant string that is displayed if the calculations are interrupted by an exception.
|
||||
*/
|
||||
private static final String ERR_EXCEPTION = "Exception Thrown";
|
||||
@FXML
|
||||
private TabPane coreTabPane;
|
||||
@FXML
|
||||
private Tab calculateTab;
|
||||
@FXML
|
||||
private Tab settingsTab;
|
||||
@FXML
|
||||
private Tab functionListTab;
|
||||
@FXML
|
||||
private TableView<HistoryModel> historyTable;
|
||||
@FXML
|
||||
private TableColumn<HistoryModel, String> inputColumn;
|
||||
@FXML
|
||||
private TableColumn<HistoryModel, String> parsedColumn;
|
||||
@FXML
|
||||
private TableColumn<HistoryModel, String> outputColumn;
|
||||
@FXML
|
||||
private Text outputText;
|
||||
@FXML
|
||||
private TextField inputField;
|
||||
@FXML
|
||||
private Button inputButton;
|
||||
@FXML
|
||||
private Button stopButton;
|
||||
@FXML
|
||||
private ComboBox<String> numberImplementationBox;
|
||||
@FXML
|
||||
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.
|
||||
*/
|
||||
private ObservableList<HistoryModel> historyData;
|
||||
|
||||
/**
|
||||
* The abacus instance used for calculations and all
|
||||
* other main processing code.
|
||||
*/
|
||||
private ObservableList<String> numberImplementationOptions;
|
||||
|
||||
/**
|
||||
* The list of plugin objects that can be toggled on and off,
|
||||
* 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;
|
||||
/**
|
||||
* The runnable used to perform the calculation.
|
||||
*/
|
||||
private final Runnable CALCULATION_RUNNABLE = new Runnable() {
|
||||
|
||||
private String attemptCalculation() {
|
||||
try {
|
||||
TreeNode constructedTree = abacus.parseString(inputField.getText());
|
||||
if (constructedTree == null) {
|
||||
return ERR_SYNTAX;
|
||||
}
|
||||
EvaluationResult result = abacus.evaluateTree(constructedTree);
|
||||
NumberInterface evaluatedNumber = result.getValue();
|
||||
if (evaluatedNumber == null) {
|
||||
return ERR_EVAL;
|
||||
}
|
||||
String resultingString = evaluatedNumber.toString();
|
||||
historyData.add(new HistoryModel(inputField.getText(), constructedTree.toString(), resultingString));
|
||||
abacus.applyToContext(result.getResultingContext());
|
||||
return resultingString;
|
||||
} catch (ComputationInterruptedException exception) {
|
||||
return ERR_STOP;
|
||||
} catch (DomainException exception) {
|
||||
return exception.getMessage();
|
||||
} catch (RuntimeException exception) {
|
||||
exception.printStackTrace();
|
||||
return ERR_EXCEPTION;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String calculation = attemptCalculation();
|
||||
Platform.runLater(() -> {
|
||||
outputText.setText(calculation);
|
||||
inputField.setText("");
|
||||
inputButton.setDisable(false);
|
||||
stopButton.setDisable(true);
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private Thread computationLimitThread;
|
||||
/**
|
||||
* 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 {
|
||||
ExtendedConfiguration abacusConfig = (ExtendedConfiguration) 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
|
||||
* have not yet been applied.
|
||||
*/
|
||||
private void alertIfApplyNeeded(boolean ignorePrevious) {
|
||||
if (changesMade && (!reloadAlertShown || ignorePrevious)) {
|
||||
reloadAlertShown = true;
|
||||
reloadAlert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
Callback<TableColumn<HistoryModel, String>, TableCell<HistoryModel, String>> cellFactory =
|
||||
param -> new CopyableCell<>();
|
||||
Callback<ListView<ToggleablePlugin>, ListCell<ToggleablePlugin>> pluginCellFactory =
|
||||
param -> new CheckBoxListCell<>(ToggleablePlugin::getEnabledProperty, new StringConverter<ToggleablePlugin>() {
|
||||
@Override
|
||||
public String toString(ToggleablePlugin object) {
|
||||
return object.getClassName().substring(object.getClassName().lastIndexOf('.') + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToggleablePlugin fromString(String 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();
|
||||
numberImplementationBox.setItems(numberImplementationOptions);
|
||||
numberImplementationBox.getSelectionModel().selectedIndexProperty().addListener(e -> changesMade = true);
|
||||
historyTable.getSelectionModel().setCellSelectionEnabled(true);
|
||||
enabledPlugins = FXCollections.observableArrayList();
|
||||
enabledPluginView.setItems(enabledPlugins);
|
||||
enabledPluginView.setCellFactory(pluginCellFactory);
|
||||
inputColumn.setCellFactory(cellFactory);
|
||||
inputColumn.setCellValueFactory(cell -> cell.getValue().getInputProperty());
|
||||
parsedColumn.setCellFactory(cellFactory);
|
||||
parsedColumn.setCellValueFactory(cell -> cell.getValue().getParsedProperty());
|
||||
outputColumn.setCellFactory(cellFactory);
|
||||
outputColumn.setCellValueFactory(cell -> cell.getValue().getOutputProperty());
|
||||
coreTabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (oldValue.equals(settingsTab)) alertIfApplyNeeded(true);
|
||||
});
|
||||
|
||||
abacus = new Abacus(new ExtendedConfiguration(CONFIG_FILE));
|
||||
PluginManager abacusPluginManager = abacus.getPluginManager();
|
||||
abacusPluginManager.addListener(this);
|
||||
performScan();
|
||||
|
||||
computationLimitField.setText(Double.toString(((ExtendedConfiguration) abacus.getConfiguration()).getComputationDelay()));
|
||||
computationLimitField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (!newValue.matches("(\\d+(\\.\\d*)?)?")) {
|
||||
computationLimitField.setText(oldValue);
|
||||
} else {
|
||||
changesMade = true;
|
||||
}
|
||||
});
|
||||
|
||||
changesMade = false;
|
||||
reloadAlertShown = false;
|
||||
|
||||
reloadAlert = new Alert(Alert.AlertType.WARNING);
|
||||
reloadAlert.setTitle(APPLY_MSG_TITLE);
|
||||
reloadAlert.setHeaderText(APPLY_MSG_HEADER);
|
||||
reloadAlert.setContentText(APPLY_MSG_TEXT);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void performCalculation() {
|
||||
inputButton.setDisable(true);
|
||||
stopButton.setDisable(false);
|
||||
calculationThread = new Thread(CALCULATION_RUNNABLE);
|
||||
calculationThread.start();
|
||||
computationLimitThread = new Thread(TIMER_RUNNABLE);
|
||||
computationLimitThread.start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void performStop() {
|
||||
if (calculationThread != null) {
|
||||
calculationThread.interrupt();
|
||||
calculationThread = null;
|
||||
}
|
||||
if (computationLimitThread != null) {
|
||||
computationLimitThread.interrupt();
|
||||
computationLimitThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void performSaveAndReload() {
|
||||
performSave();
|
||||
performReload();
|
||||
changesMade = false;
|
||||
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();
|
||||
}
|
||||
abacus.reload();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void performReload() {
|
||||
alertIfApplyNeeded(true);
|
||||
abacus.reload();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void performSave() {
|
||||
Configuration configuration = abacus.getConfiguration();
|
||||
configuration.setNumberImplementation(numberImplementationBox.getSelectionModel().getSelectedItem());
|
||||
Set<String> disabledPlugins = configuration.getDisabledPlugins();
|
||||
disabledPlugins.clear();
|
||||
for (ToggleablePlugin pluginEntry : enabledPlugins) {
|
||||
if (!pluginEntry.isEnabled()) disabledPlugins.add(pluginEntry.getClassName());
|
||||
}
|
||||
if (computationLimitField.getText().matches("\\d*(\\.\\d+)?") && computationLimitField.getText().length() != 0)
|
||||
((ExtendedConfiguration) configuration).setComputationDelay(Double.parseDouble(computationLimitField.getText()));
|
||||
((ExtendedConfiguration) configuration).saveTo(CONFIG_FILE);
|
||||
changesMade = false;
|
||||
reloadAlertShown = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad(PluginManager manager) {
|
||||
Configuration configuration = abacus.getConfiguration();
|
||||
Set<String> disabledPlugins = configuration.getDisabledPlugins();
|
||||
numberImplementationOptions.addAll(abacus.getPluginManager().getAllNumberImplementations());
|
||||
String actualImplementation = configuration.getNumberImplementation();
|
||||
String toSelect = (numberImplementationOptions.contains(actualImplementation)) ? actualImplementation : "<default>";
|
||||
numberImplementationBox.getSelectionModel().select(toSelect);
|
||||
for (Class<?> pluginClass : abacus.getPluginManager().getLoadedPluginClasses()) {
|
||||
String fullName = pluginClass.getName();
|
||||
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.addAll(manager.getAllTreeValueFunctions().stream().map(name -> pluginManager.documentationFor(name, DocumentationType.TREE_VALUE_FUNCTION))
|
||||
.collect(Collectors.toCollection(ArrayList::new)));
|
||||
functionList.sort(Comparator.comparing(Documentation::getCodeName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnload(PluginManager manager) {
|
||||
functionList.clear();
|
||||
enabledPlugins.clear();
|
||||
numberImplementationOptions.clear();
|
||||
}
|
||||
|
||||
}
|
||||
36
fx/src/main/java/org/nwapw/abacus/fx/CopyableCell.java
Normal file
36
fx/src/main/java/org/nwapw/abacus/fx/CopyableCell.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package org.nwapw.abacus.fx;
|
||||
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
|
||||
/**
|
||||
* A cell that copies its value to the clipboard
|
||||
* when double clicked.
|
||||
*
|
||||
* @param <S> The type of the table view generic type.
|
||||
* @param <T> The type of the value contained in the cell.
|
||||
*/
|
||||
public class CopyableCell<S, T> extends TableCell<S, T> {
|
||||
|
||||
/**
|
||||
* Creates a new copyable cell.
|
||||
*/
|
||||
public CopyableCell() {
|
||||
addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
|
||||
if (event.getClickCount() == 2) {
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||
.setContents(new StringSelection(getText()), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(T item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
setText((empty || item == null) ? null : item.toString());
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
59
fx/src/main/java/org/nwapw/abacus/fx/DocumentationCell.java
Normal file
59
fx/src/main/java/org/nwapw/abacus/fx/DocumentationCell.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.nwapw.abacus.fx
|
||||
|
||||
import com.moandjiezana.toml.Toml
|
||||
import com.moandjiezana.toml.TomlWriter
|
||||
import org.nwapw.abacus.config.Configuration
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Additional settings for user interface.
|
||||
*
|
||||
* ExtendedConfiguration is used to add other settings
|
||||
* that aren't built into Abacus core, but are necessary
|
||||
* for the fx module.
|
||||
*
|
||||
* @property computationDelay the delay before which the computation stops.
|
||||
* @param implementation the number implementation, same as [Configuration.numberImplementation]
|
||||
* @param disabledPlugins the list of plugins that should be disabled, same as [Configuration.disabledPlugins]
|
||||
*/
|
||||
class ExtendedConfiguration(var computationDelay: Double = 0.0,
|
||||
implementation: String = "<default>",
|
||||
disabledPlugins: Array<String> = emptyArray())
|
||||
: Configuration(implementation, disabledPlugins) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The default TOML.
|
||||
*/
|
||||
val DEFAULT_TOML_STRING = """
|
||||
computationDelay=0.0
|
||||
implementation="naive"
|
||||
disabledPlugins=[]
|
||||
"""
|
||||
/**
|
||||
* A reader with the default TOML data.
|
||||
*/
|
||||
val DEFAULT_TOML_READER = Toml().read(DEFAULT_TOML_STRING)
|
||||
/**
|
||||
* A writer used to writing the configuration to disk.
|
||||
*/
|
||||
val DEFAULT_TOML_WRITER = TomlWriter()
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new configuration from a file on disk.
|
||||
* @param tomlFile the file from disk to load.
|
||||
*/
|
||||
constructor(tomlFile: File) : this() {
|
||||
val toml = Toml(DEFAULT_TOML_READER)
|
||||
if(tomlFile.exists()) toml.read(tomlFile)
|
||||
copyFrom(toml.to(ExtendedConfiguration::class.java))
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies data from another configuration into this one.
|
||||
* @param config the configuration to copy from.
|
||||
*/
|
||||
fun copyFrom(config: ExtendedConfiguration) {
|
||||
computationDelay = config.computationDelay
|
||||
numberImplementation = config.numberImplementation
|
||||
disabledPlugins.clear()
|
||||
disabledPlugins.addAll(config.disabledPlugins)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves this configuration to a file.
|
||||
* @param file the file to save to.
|
||||
*/
|
||||
fun saveTo(file: File) {
|
||||
DEFAULT_TOML_WRITER.write(this, file)
|
||||
}
|
||||
|
||||
}
|
||||
32
fx/src/main/kotlin/org/nwapw/abacus/fx/HistoryModel.kt
Normal file
32
fx/src/main/kotlin/org/nwapw/abacus/fx/HistoryModel.kt
Normal 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)
|
||||
|
||||
}
|
||||
31
fx/src/main/kotlin/org/nwapw/abacus/fx/ToggleablePlugin.kt
Normal file
31
fx/src/main/kotlin/org/nwapw/abacus/fx/ToggleablePlugin.kt
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
76
fx/src/main/resources/abacus.fxml
Normal file
76
fx/src/main/resources/abacus.fxml
Normal file
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<BorderPane xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.nwapw.abacus.fx.AbacusController">
|
||||
<center>
|
||||
<TabPane fx:id="coreTabPane">
|
||||
<Tab fx:id="calculateTab" text="Calculator" closable="false">
|
||||
<BorderPane>
|
||||
<center>
|
||||
<TableView fx:id="historyTable">
|
||||
<columnResizePolicy>
|
||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
|
||||
</columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="inputColumn" text="Input" sortable="false"/>
|
||||
<TableColumn fx:id="parsedColumn" text="Parsed" sortable="false"/>
|
||||
<TableColumn fx:id="outputColumn" text="Output" sortable="false"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
</center>
|
||||
<bottom>
|
||||
<VBox>
|
||||
<ScrollPane prefHeight="50" vbarPolicy="NEVER">
|
||||
<padding>
|
||||
<Insets top="10" bottom="10" left="10" right="10"/>
|
||||
</padding>
|
||||
<Text fx:id="outputText"/>
|
||||
</ScrollPane>
|
||||
<TextField fx:id="inputField" onAction="#performCalculation"/>
|
||||
<Button fx:id="inputButton" text="Calculate" maxWidth="Infinity"
|
||||
onAction="#performCalculation"/>
|
||||
<Button fx:id="stopButton" text="Stop" maxWidth="Infinity"
|
||||
onAction="#performStop" disable="true"/>
|
||||
</VBox>
|
||||
</bottom>
|
||||
</BorderPane>
|
||||
</Tab>
|
||||
<Tab fx:id="settingsTab" text="Settings" closable="false">
|
||||
<GridPane hgap="10" vgap="10">
|
||||
<padding>
|
||||
<Insets left="10" right="10" top="10" bottom="10"/>
|
||||
</padding>
|
||||
<Label text="Number Implementation" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
|
||||
<ComboBox fx:id="numberImplementationBox" GridPane.columnIndex="1" GridPane.rowIndex="0"/>
|
||||
<ListView fx:id="enabledPluginView"
|
||||
GridPane.rowIndex="1" GridPane.columnIndex="0"
|
||||
GridPane.columnSpan="2" maxHeight="100"/>
|
||||
<Text GridPane.columnIndex="0" GridPane.rowIndex="2" text="Computation Limit"/>
|
||||
<TextField fx:id="computationLimitField" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
|
||||
<FlowPane GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="3" hgap="10"
|
||||
vgap="10">
|
||||
<Button text="Save" 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>
|
||||
|
||||
</BorderPane>
|
||||
BIN
image/logo.png
Normal file
BIN
image/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -1 +1,2 @@
|
||||
rootProject.name = 'abacus'
|
||||
include 'core', 'fx'
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
package org.nwapw.abacus;
|
||||
|
||||
import org.nwapw.abacus.config.ConfigurationObject;
|
||||
import org.nwapw.abacus.number.NumberInterface;
|
||||
import org.nwapw.abacus.parsing.LexerTokenizer;
|
||||
import org.nwapw.abacus.parsing.ShuntingYardParser;
|
||||
import org.nwapw.abacus.parsing.TreeBuilder;
|
||||
import org.nwapw.abacus.plugin.ClassFinder;
|
||||
import org.nwapw.abacus.plugin.NumberImplementation;
|
||||
import org.nwapw.abacus.plugin.PluginManager;
|
||||
import org.nwapw.abacus.plugin.StandardPlugin;
|
||||
import org.nwapw.abacus.tree.NumberReducer;
|
||||
import org.nwapw.abacus.tree.TreeNode;
|
||||
import org.nwapw.abacus.window.Window;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* The main calculator class. This is responsible
|
||||
* for piecing together all of the components, allowing
|
||||
* their interaction with each other.
|
||||
*/
|
||||
public class Abacus {
|
||||
|
||||
public static final NumberImplementation DEFAULT_IMPLEMENTATION = StandardPlugin.IMPLEMENTATION_NAIVE;
|
||||
/**
|
||||
* The file used for saving and loading configuration.
|
||||
*/
|
||||
public static final File CONFIG_FILE = new File("config.toml");
|
||||
|
||||
/**
|
||||
* The plugin manager responsible for
|
||||
* loading and unloading plugins,
|
||||
* and getting functions from them.
|
||||
*/
|
||||
private PluginManager pluginManager;
|
||||
/**
|
||||
* The reducer used to evaluate the tree.
|
||||
*/
|
||||
private NumberReducer numberReducer;
|
||||
/**
|
||||
* The configuration loaded from a file.
|
||||
*/
|
||||
private ConfigurationObject configuration;
|
||||
/**
|
||||
* The tree builder used to construct a tree
|
||||
* from a string.
|
||||
*/
|
||||
private TreeBuilder treeBuilder;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the Abacus calculator.
|
||||
*/
|
||||
public Abacus() {
|
||||
pluginManager = new PluginManager();
|
||||
numberReducer = new NumberReducer(this);
|
||||
configuration = new ConfigurationObject(CONFIG_FILE);
|
||||
configuration.save(CONFIG_FILE);
|
||||
LexerTokenizer lexerTokenizer = new LexerTokenizer();
|
||||
ShuntingYardParser shuntingYardParser = new ShuntingYardParser(this);
|
||||
treeBuilder = new TreeBuilder<>(lexerTokenizer, shuntingYardParser);
|
||||
|
||||
pluginManager.addListener(lexerTokenizer);
|
||||
pluginManager.addListener(shuntingYardParser);
|
||||
pluginManager.addInstantiated(new StandardPlugin(pluginManager));
|
||||
try {
|
||||
ClassFinder.loadJars("plugins")
|
||||
.forEach(plugin -> pluginManager.addClass(plugin));
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
pluginManager.load();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
} catch (ClassNotFoundException | InstantiationException | UnsupportedLookAndFeelException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
new Window(new Abacus()).setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current tree builder.
|
||||
*
|
||||
* @return the main tree builder in this abacus instance.
|
||||
*/
|
||||
public TreeBuilder getTreeBuilder() {
|
||||
return treeBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current plugin manager,
|
||||
*
|
||||
* @return the plugin manager in this abacus instance.
|
||||
*/
|
||||
public PluginManager getPluginManager() {
|
||||
return pluginManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reducer that is responsible for transforming
|
||||
* an expression into a number.
|
||||
*
|
||||
* @return the number reducer in this abacus instance.
|
||||
*/
|
||||
public NumberReducer getNumberReducer() {
|
||||
return numberReducer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration object associated with this instance.
|
||||
*
|
||||
* @return the configuration object.
|
||||
*/
|
||||
public ConfigurationObject getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string into a tree structure using the main
|
||||
* tree builder.
|
||||
*
|
||||
* @param input the input string to parse
|
||||
* @return the resulting tree, null if the tree builder or the produced tree are null.
|
||||
*/
|
||||
public TreeNode parseString(String input) {
|
||||
return treeBuilder.fromString(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the given tree using the main
|
||||
* number reducer.
|
||||
*
|
||||
* @param tree the tree to reduce, must not be null.
|
||||
* @return the resulting number, or null of the reduction failed.
|
||||
*/
|
||||
public NumberInterface evaluateTree(TreeNode tree) {
|
||||
return tree.reduce(numberReducer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a number from a string.
|
||||
*
|
||||
* @param numberString the string to create the number from.
|
||||
* @return the resulting number.
|
||||
*/
|
||||
public NumberInterface numberFromString(String numberString) {
|
||||
NumberImplementation toInstantiate =
|
||||
pluginManager.numberImplementationFor(configuration.getNumberImplementation());
|
||||
if (toInstantiate == null) toInstantiate = DEFAULT_IMPLEMENTATION;
|
||||
|
||||
return toInstantiate.instanceForString(numberString);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.nwapw.abacus.config;
|
||||
|
||||
/**
|
||||
* Serializable class that will be used to load TOML
|
||||
* configurations.
|
||||
*/
|
||||
public class Configuration {
|
||||
|
||||
/**
|
||||
* The type of number this calculator should use.
|
||||
*/
|
||||
public String numberType;
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package org.nwapw.abacus.config;
|
||||
|
||||
import com.moandjiezana.toml.Toml;
|
||||
import com.moandjiezana.toml.TomlWriter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A configuration object, which essentially
|
||||
* manages saving, loading, and getting values
|
||||
* from the configuration. While Configuration is
|
||||
* the data model, this is the interface with it.
|
||||
*/
|
||||
public class ConfigurationObject {
|
||||
|
||||
/**
|
||||
* The writer used to store the configuration.
|
||||
*/
|
||||
private static final TomlWriter TOML_WRITER = new TomlWriter();
|
||||
/**
|
||||
* The configuration instance being modeled.
|
||||
*/
|
||||
private Configuration configuration;
|
||||
|
||||
/**
|
||||
* Creates a new configuration object with the given config.
|
||||
*
|
||||
* @param config the config to use.
|
||||
*/
|
||||
public ConfigurationObject(Configuration config) {
|
||||
setup(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a configuration object by attempting to
|
||||
* load a config from the given path, using the
|
||||
* default configuration otherwise.
|
||||
*
|
||||
* @param path the path to attempt to load.
|
||||
*/
|
||||
public ConfigurationObject(File path) {
|
||||
Configuration config;
|
||||
if (!path.exists()) {
|
||||
config = getDefaultConfig();
|
||||
} else {
|
||||
Toml parse = new Toml();
|
||||
parse.read(path);
|
||||
config = parse.to(Configuration.class);
|
||||
}
|
||||
setup(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new configuration object with the
|
||||
* default configuration.
|
||||
*/
|
||||
public ConfigurationObject() {
|
||||
setup(getDefaultConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the ConfigurationObject.
|
||||
* different constructors do different things,
|
||||
* but they all lead here.
|
||||
*
|
||||
* @param configuration the configuration to set up with.
|
||||
*/
|
||||
private void setup(Configuration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default configuration.
|
||||
*
|
||||
* @return the newly created default configuration.
|
||||
*/
|
||||
private Configuration getDefaultConfig() {
|
||||
configuration = new Configuration();
|
||||
configuration.numberType = "naive";
|
||||
return configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the implementation the user has requested to
|
||||
* represent their numbers.
|
||||
*
|
||||
* @return the implementation name.
|
||||
*/
|
||||
public String getNumberImplementation() {
|
||||
return configuration.numberType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the ConfigurationObject to the given file.
|
||||
*
|
||||
* @param toFile the file to save ot.
|
||||
* @return true if the save succeed, false if otherwise.
|
||||
*/
|
||||
public boolean save(File toFile) {
|
||||
if (toFile.getParentFile() != null) toFile.getParentFile().mkdirs();
|
||||
try {
|
||||
TOML_WRITER.write(configuration, toFile);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package org.nwapw.abacus.function;
|
||||
|
||||
import org.nwapw.abacus.number.NumberInterface;
|
||||
|
||||
/**
|
||||
* A function that operates on one or more
|
||||
* inputs and returns a single number.
|
||||
*/
|
||||
public abstract class Function {
|
||||
|
||||
/**
|
||||
* Checks whether the given params will work for the given function.
|
||||
*
|
||||
* @param params the given params
|
||||
* @return true if the params can be used with this function.
|
||||
*/
|
||||
protected abstract boolean matchesParams(NumberInterface[] params);
|
||||
|
||||
/**
|
||||
* Internal apply implementation, which already receives appropriately promoted
|
||||
* parameters that have bee run through matchesParams
|
||||
*
|
||||
* @param params the promoted parameters.
|
||||
* @return the return value of the function.
|
||||
*/
|
||||
protected abstract NumberInterface applyInternal(NumberInterface[] params);
|
||||
|
||||
/**
|
||||
* Function to check, promote arguments and run the function.
|
||||
*
|
||||
* @param params the raw input parameters.
|
||||
* @return the return value of the function, or null if an error occurred.
|
||||
*/
|
||||
public NumberInterface apply(NumberInterface... params) {
|
||||
if (!matchesParams(params)) return null;
|
||||
return applyInternal(params);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,75 +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 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package org.nwapw.abacus.number;
|
||||
|
||||
/**
|
||||
* An interface used to represent a number.
|
||||
*/
|
||||
public interface NumberInterface {
|
||||
|
||||
/**
|
||||
* The maximum precision to which this number operates.
|
||||
*
|
||||
* @return the precision.
|
||||
*/
|
||||
int getMaxPrecision();
|
||||
|
||||
/**
|
||||
* Multiplies this number by another, returning
|
||||
* a new number instance.
|
||||
*
|
||||
* @param multiplier the multiplier
|
||||
* @return the result of the multiplication.
|
||||
*/
|
||||
NumberInterface multiply(NumberInterface multiplier);
|
||||
|
||||
/**
|
||||
* Divides this number by another, returning
|
||||
* a new number instance.
|
||||
*
|
||||
* @param divisor the divisor
|
||||
* @return the result of the division.
|
||||
*/
|
||||
NumberInterface divide(NumberInterface divisor);
|
||||
|
||||
/**
|
||||
* Adds this number to another, returning
|
||||
* a new number instance.
|
||||
*
|
||||
* @param summand the summand
|
||||
* @return the result of the summation.
|
||||
*/
|
||||
NumberInterface add(NumberInterface summand);
|
||||
|
||||
/**
|
||||
* Subtracts another number from this number,
|
||||
* a new number instance.
|
||||
*
|
||||
* @param subtrahend the subtrahend.
|
||||
* @return the result of the subtraction.
|
||||
*/
|
||||
NumberInterface subtract(NumberInterface subtrahend);
|
||||
|
||||
/**
|
||||
* Returns a new instance of this number with
|
||||
* the sign flipped.
|
||||
*
|
||||
* @return the new instance.
|
||||
*/
|
||||
NumberInterface negate();
|
||||
|
||||
/**
|
||||
* Raises this number to an integer power.
|
||||
*
|
||||
* @param exponent the exponent to which to take the number.
|
||||
* @return the resulting value.
|
||||
*/
|
||||
NumberInterface intPow(int exponent);
|
||||
|
||||
/**
|
||||
* Compares this number to another.
|
||||
*
|
||||
* @param number the number to compare to.
|
||||
* @return same as Integer.compare();
|
||||
*/
|
||||
int compareTo(NumberInterface number);
|
||||
|
||||
/**
|
||||
* Same as Math.signum().
|
||||
*
|
||||
* @return 1 if this number is positive, -1 if this number is negative, 0 if this number is 0.
|
||||
*/
|
||||
int signum();
|
||||
|
||||
/**
|
||||
* Returns the least integer greater than or equal to the number.
|
||||
* @return the least integer >= the number, if int can hold the value.
|
||||
*/
|
||||
NumberInterface ceiling();
|
||||
|
||||
/**
|
||||
* Return the greatest integer less than or equal to the number.
|
||||
* @return the greatest int >= the number, if int can hold the value.
|
||||
*/
|
||||
NumberInterface floor();
|
||||
|
||||
/**
|
||||
* Returns the fractional part of the number.
|
||||
* @return the fractional part of the number.
|
||||
*/
|
||||
NumberInterface fractionalPart();
|
||||
|
||||
/**
|
||||
* Returns the integer representation of this number, discarding any fractional part,
|
||||
* if int can hold the value.
|
||||
* @return
|
||||
*/
|
||||
int intValue();
|
||||
|
||||
/**
|
||||
* Promotes this class to another number class.
|
||||
*
|
||||
* @param toClass the class to promote to.
|
||||
* @return the resulting new instance.
|
||||
*/
|
||||
NumberInterface promoteTo(Class<? extends NumberInterface> toClass);
|
||||
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
package org.nwapw.abacus.plugin;
|
||||
|
||||
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.*;
|
||||
|
||||
/**
|
||||
* A class that controls instances of plugins, allowing for them
|
||||
* to interact with each other and the calculator.
|
||||
*/
|
||||
public class PluginManager {
|
||||
|
||||
/**
|
||||
* List of classes loaded by this manager.
|
||||
*/
|
||||
private Set<Class<?>> loadedPluginClasses;
|
||||
/**
|
||||
* A list of loaded plugins.
|
||||
*/
|
||||
private Set<Plugin> plugins;
|
||||
/**
|
||||
* List of functions that have been cached,
|
||||
* that is, found in a plugin and returned.
|
||||
*/
|
||||
private Map<String, Function> cachedFunctions;
|
||||
/**
|
||||
* List of operators that have been cached,
|
||||
* that is, found in a plugin and returned.
|
||||
*/
|
||||
private Map<String, Operator> cachedOperators;
|
||||
/**
|
||||
* The list of number implementations that have
|
||||
* been cached, that is, found in a plugin and returned.
|
||||
*/
|
||||
private Map<String, NumberImplementation> cachedNumberImplementations;
|
||||
/**
|
||||
* The list of number implementations that have been
|
||||
* found by their implementation class.
|
||||
*/
|
||||
private Map<Class<? extends NumberInterface>, NumberImplementation> cachedInterfaceImplementations;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private Set<PluginListener> listeners;
|
||||
|
||||
/**
|
||||
* Creates a new plugin manager.
|
||||
*/
|
||||
public PluginManager() {
|
||||
loadedPluginClasses = new HashSet<>();
|
||||
plugins = new HashSet<>();
|
||||
cachedFunctions = new HashMap<>();
|
||||
cachedOperators = new HashMap<>();
|
||||
cachedNumberImplementations = new HashMap<>();
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
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);
|
||||
|
||||
T loadedValue = null;
|
||||
for (Plugin plugin : plugins) {
|
||||
if (setFunction.apply(plugin).contains(name)) {
|
||||
loadedValue = getFunction.apply(plugin, name);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number implementation for the given implementation class.
|
||||
* @param name the class for which to find the implementation.
|
||||
* @return the implementation.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
cachedInterfaceImplementations.put(name, toReturn);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mathematical constant pi for the given implementation class.
|
||||
* @param forClass the class for which to find pi.
|
||||
* @return pi
|
||||
*/
|
||||
public NumberInterface piFor(Class<? extends NumberInterface> forClass){
|
||||
if(cachedPi.containsKey(forClass)) return cachedPi.get(forClass);
|
||||
NumberImplementation implementation = interfaceImplementationFor(forClass);
|
||||
NumberInterface generatedPi = null;
|
||||
if(implementation != null){
|
||||
generatedPi = implementation.instanceForPi();
|
||||
}
|
||||
cachedPi.put(forClass, generatedPi);
|
||||
return generatedPi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an instance of Plugin that already has been instantiated.
|
||||
*
|
||||
* @param plugin the plugin to add.
|
||||
*/
|
||||
public void addInstantiated(Plugin plugin) {
|
||||
if (loadedPluginClasses.contains(plugin.getClass())) return;
|
||||
plugins.add(plugin);
|
||||
loadedPluginClasses.add(plugin.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a class of plugin, and adds it to this
|
||||
* plugin manager.
|
||||
*
|
||||
* @param newClass the new class to instantiate.
|
||||
*/
|
||||
public void addClass(Class<?> newClass) {
|
||||
if (!Plugin.class.isAssignableFrom(newClass) || newClass == Plugin.class) return;
|
||||
try {
|
||||
addInstantiated((Plugin) newClass.getConstructor(PluginManager.class).newInstance(this));
|
||||
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all the plugins in the PluginManager.
|
||||
*/
|
||||
public void load() {
|
||||
for (Plugin plugin : plugins) plugin.enable();
|
||||
for (Plugin plugin : plugins) {
|
||||
allFunctions.addAll(plugin.providedFunctions());
|
||||
allOperators.addAll(plugin.providedOperators());
|
||||
allNumberImplementations.addAll(plugin.providedNumberImplementations());
|
||||
}
|
||||
listeners.forEach(e -> e.onLoad(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads all the plugins in the PluginManager.
|
||||
*/
|
||||
public void unload() {
|
||||
for (Plugin plugin : plugins) plugin.disable();
|
||||
allFunctions.clear();
|
||||
allOperators.clear();
|
||||
allNumberImplementations.clear();
|
||||
listeners.forEach(e -> e.onUnload(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads all the plugins in the PluginManager.
|
||||
*/
|
||||
public void reload() {
|
||||
unload();
|
||||
reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the functions loaded by the Plugin Manager.
|
||||
*
|
||||
* @return the set of all functions that were loaded.
|
||||
*/
|
||||
public Set<String> getAllFunctions() {
|
||||
return allFunctions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the operators loaded by the Plugin Manager.
|
||||
*
|
||||
* @return the set of all operators that were loaded.
|
||||
*/
|
||||
public Set<String> getAllOperators() {
|
||||
return allOperators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the number implementations loaded by the Plugin Manager.
|
||||
*
|
||||
* @return the set of all implementations that were loaded.
|
||||
*/
|
||||
public Set<String> getAllNumberImplementations(){
|
||||
return allNumberImplementations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a plugin change listener to this plugin manager.
|
||||
*
|
||||
* @param listener the listener to add.
|
||||
*/
|
||||
public void addListener(PluginListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the plugin change listener from this plugin manager.
|
||||
*
|
||||
* @param listener the listener to remove.
|
||||
*/
|
||||
public void removeListener(PluginListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user