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

Compare commits

..

130 Commits

Author SHA1 Message Date
672252ef41 Merge branch 'master' into new_image 2017-08-30 15:35:35 -07:00
824f391fc7 Add the logo to GitHub pages. 2017-08-30 15:31:25 -07:00
63a160659a Merge pull request #7 from DanilaFe/tree-operators
Implement tree operators and functions.
2017-08-30 15:14:41 -07:00
879d09e5b8 Add a logo. 2017-08-30 15:14:08 -07:00
ae0ec0c375 Switch add and multiply to two parameters. 2017-08-29 18:31:47 -07:00
337a38a07d Merge branch 'master' into tree-operators 2017-08-29 18:17:58 -07:00
fbfc68ebfe Merge pull request #6 from DanilaFe/fxml-fix
Move the FXML file for the fx project into the correct location.
2017-08-29 18:17:32 -07:00
01e7a03444 Merge branch 'master' into fxml-fix 2017-08-29 18:14:09 -07:00
0cb180284a Merge pull request #4 from DanilaFe/variable-parsing
Add variables into the parser.
2017-08-29 18:13:35 -07:00
192269ea9a Merge branch 'master' into variable-parsing 2017-08-29 18:11:30 -07:00
f134e5aa04 Merge pull request #5 from DanilaFe/dokka-setup
Add dokka plugin to generate documentation.
2017-08-29 18:09:36 -07:00
e3c37cf10a Bring tests up to date. 2017-08-28 12:59:16 -07:00
823c788148 Move the FXML file for the fx project into the correct location. 2017-08-27 15:49:01 -07:00
ece9f1ae04 Add dokka plugin to generate documentation. 2017-08-27 15:47:32 -07:00
fbc12ec41c Format newly written code. 2017-08-26 12:19:34 -07:00
385a64eace Make ReducerApplicable an independent interface. 2017-08-26 11:52:02 -07:00
c2feedee32 Register precedences of TreeValue operators. 2017-08-25 19:49:37 -07:00
b98b08b872 Make sure TreeValueOperator extends Operator. 2017-08-25 19:49:15 -07:00
f8eb051583 Fix token precedence for variable names / operators. 2017-08-25 19:48:56 -07:00
20b2e77ee1 Add reduction of TreeValue operators. 2017-08-25 19:48:43 -07:00
bfc1ed5819 Fix incorrect template argument. 2017-08-25 18:47:49 -07:00
9d52d55e68 Add TreeValue operator nodes, and parsing for them. 2017-08-25 18:42:41 -07:00
07d7343339 Abstract some Binary and Unary node logic. 2017-08-25 17:46:25 -07:00
73075c57b9 Add registering TreeValueOperators. 2017-08-25 17:31:47 -07:00
5b1a48c02e Convert Applicable interfaces into Kotlin. 2017-08-25 16:07:23 -07:00
ca2681cc21 Add a TreeValueOperator. 2017-08-25 15:49:35 -07:00
8a3c614602 Make applicable into an interface. 2017-08-25 15:49:26 -07:00
da1c78945e Move the code for applicables that require a Reducer. 2017-08-25 15:29:37 -07:00
225a926f86 Move NumberFunction into Kotlin. 2017-08-25 14:59:41 -07:00
f83f2a7aaa Rename Function to NumberFunction. 2017-08-25 14:56:36 -07:00
d04adf4da5 Add Applicable to Operator, therby removing the need for Functions in it 2017-08-25 14:55:05 -07:00
1f0addccea Add documentation loading for functions. 2017-08-25 01:51:14 -07:00
1a47e07e97 Add Tree Value Functions to NumberReducer. 2017-08-25 01:41:51 -07:00
26305c3bae Add the withReducer variants of the Applier functions. 2017-08-25 01:41:32 -07:00
6b9252f902 Add parsing of TreeValueFunctions. 2017-08-25 01:21:28 -07:00
bc26ad0b88 Abstract the call functionality, and add TreeValueFunctionNode. 2017-08-25 01:17:52 -07:00
c5cd0f81ad Remove data modifier from tree classes. 2017-08-25 01:07:59 -07:00
ac19c7b230 Change lexer tokenizer to recognize tree value functions. 2017-08-25 01:03:12 -07:00
40c80db914 Add tree value functions to plugins. 2017-08-25 00:59:39 -07:00
00462281fe Add a function that operates on trees. 2017-08-25 00:49:16 -07:00
01f80bbb53 Abstract some of the Function functionality further. 2017-08-25 00:43:36 -07:00
553c7354c1 Account for the new string-only node structure.
The output has to be the same as the user-provided input, as the
tree isn't converted to numbers until evaluation.
2017-08-18 16:31:54 -07:00
50ede6460c Remove Abacus dependency from ShuntingYardParser. 2017-08-18 15:57:48 -07:00
beb583a231 Move number string parsing from the parser into the reducer. 2017-08-18 14:26:33 -07:00
e0ff229df4 Temporarily substitute 0 for variables. 2017-08-18 14:21:48 -07:00
1c751353f1 Lex and parse variables. 2017-08-18 14:21:14 -07:00
0a15043b63 Implement a variable TreeNode. 2017-08-18 14:20:49 -07:00
21e059c1ca Add a new TokenType for variables. 2017-08-18 14:20:37 -07:00
16faceb3cc Add comment to DocumentationType. 2017-08-16 15:38:02 -07:00
251da90d57 Fix problems with scaling on mobile phones. 2017-08-15 01:24:48 -07:00
a7536b198f Add a proper About page. 2017-08-15 01:02:16 -07:00
66bda5db39 Add the download page. 2017-08-15 00:40:28 -07:00
2425adaea5 Remove the rounded borders. 2017-08-15 00:40:21 -07:00
f5d913d527 Change download to link to the Download page. 2017-08-15 00:40:05 -07:00
beae822d76 Move button styling into scss. 2017-08-15 00:31:47 -07:00
bcd3e3b68a Add a home layout. 2017-08-15 00:14:20 -07:00
8b42acfbb1 Change the code font. 2017-08-14 23:42:17 -07:00
952eeac704 Add some style to code. 2017-08-14 22:58:32 -07:00
b57f24854e Add the page layout for pages like About. 2017-08-14 22:58:27 -07:00
6758771c45 Add support for navbar links. 2017-08-14 22:46:39 -07:00
415a05288a Use the center class for centering the content. 2017-08-14 22:36:46 -07:00
334439e075 Add a header. 2017-08-14 22:36:36 -07:00
852776566d Add some basic CSS styling. 2017-08-14 22:36:26 -07:00
07020de1f9 Add the CSS file and link to it from head. 2017-08-14 21:40:37 -07:00
06c2397a09 Use the minima head.html include. 2017-08-14 21:28:28 -07:00
69fe28d5e4 Modify 404 to use the base layout. 2017-08-14 21:22:55 -07:00
367c282497 Start the base page layout. 2017-08-14 21:22:43 -07:00
2876eda2ca Add a new head include. 2017-08-14 21:22:34 -07:00
4ec8c7a535 Remove default theme altogether. 2017-08-14 21:16:35 -07:00
5167946fe2 Overwrite the default header and footer. Temporarily blank. 2017-08-14 21:13:33 -07:00
418e896108 Create the about page. 2017-08-14 21:12:41 -07:00
d3012b9624 Delete the about me page, and add a folder for pages. 2017-08-14 21:11:16 -07:00
bf7881faab Remove the default post. 2017-08-14 19:42:14 -07:00
b75e92838d Configure the default site. 2017-08-14 19:42:06 -07:00
620f087e38 Create basic Jekyll template. 2017-08-14 19:21:17 -07:00
205d5dbc77 Format code. 2017-08-14 19:03:52 -07:00
d861444d13 Delete old, unused code. 2017-08-13 16:49:25 -07:00
eac2a9ed6b Move the JavaFX files written in Kotlin to the FX module. 2017-08-13 01:51:53 -07:00
31c61fdf95 Reduce Kotlin version to 1.7 2017-08-13 01:08:19 -07:00
8f251d2d13 Split the project into separate modules. 2017-08-12 21:11:01 -07:00
99ffd51a43 Merge remote-tracking branch 'origin/inv-trig-doc' 2017-08-11 22:25:45 -07:00
00c51c62fd Merge branch 'inv-trig'
# Conflicts:
#	src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java
2017-08-11 22:13:20 -07:00
400ed6e70a Merge branch 'master' of github.com:DanilaFe/abacus 2017-08-11 22:10:42 -07:00
Arthur Drobot
5902ba8c5c Inverse trig documentation. 2017-08-11 21:07:34 -07:00
Arthur Drobot
efdc4af31b Add examples to documentation of trig functions. 2017-08-11 20:32:39 -07:00
2e0b1201a3 Merge branch 'inv-trig'
# Conflicts:
#	src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java
2017-08-11 12:29:12 -07:00
Arthur Drobot
d94edaa33f Modify sqrt comment. 2017-08-11 01:23:01 -07:00
Arthur Drobot
1e6cf08ec2 Write arctan and add arccot. 2017-08-11 01:19:13 -07:00
7c378b401a Remove useless comment. 2017-08-10 22:16:12 -07:00
1c448048f2 Fix typos created in removing the promoteTo calls. 2017-08-09 19:23:44 -07:00
fd21014c39 Phase out as many promoteTo calls as possible. 2017-08-09 19:04:32 -07:00
Arthur Drobot
fee9f091fd Revert "Write a RationalNumber class."
This reverts commit f3942b6760.
2017-08-09 18:43:52 -07:00
021e569491 Make the C in nCr capital. 2017-08-09 17:15:12 -07:00
Arthur Drobot
4226df72f5 Add arcsec. Adjust some comments. 2017-08-09 16:04:26 -07:00
Arthur Drobot
1f0e6a7ce4 Add arccsc. 2017-08-09 15:57:02 -07:00
Arthur Drobot
efe76a6fdc Add arccos. 2017-08-09 15:08:54 -07:00
Arthur Drobot
ca6d8d2ba2 Add arcsin. 2017-08-09 12:47:43 -07:00
e6f5af3727 Add optimization to NPR. 2017-08-09 12:47:22 -07:00
4e042bd0eb Implement two combinatorics operators. 2017-08-09 12:34:02 -07:00
d6f4838f05 Add a random function. 2017-08-09 11:51:12 -07:00
61475a24d9 Fix the weird scroll bars. 2017-08-09 11:22:01 -07:00
c498a5b643 Merge branch 'function-doc' 2017-08-09 11:10:00 -07:00
5e3daaed43 Rescan the plugins folder. 2017-08-09 11:02:21 -07:00
b99ad5a09a Add plugin removal from the plugin manager. 2017-08-09 10:30:04 -07:00
ff8701a7bf Deprecate the promotion functions. 2017-08-08 15:29:19 -07:00
Arthur Drobot
1d6957c4d9 Write a RationalNumber class. 2017-08-08 14:53:34 -07:00
33b175a3c6 Fix some documentation glitches. 2017-08-08 14:39:41 -07:00
c95a6df304 Change the sorting order. 2017-08-08 14:26:04 -07:00
3316f02e2b Add documentation. 2017-08-08 14:18:14 -07:00
6767a0e4aa Change the width property bound to the width. 2017-08-08 14:18:08 -07:00
400e4578a0 Add documentation browsing to the abacus controller. 2017-08-08 13:49:19 -07:00
9d92d0eebb Add registering documentation to plugins. 2017-08-08 13:49:04 -07:00
fdcf2b5c6d Fix documentation loading code. 2017-08-08 13:47:03 -07:00
8c3de54d0c Add a matches function. 2017-08-08 13:46:54 -07:00
5a57544067 Add a plugin registration function to Plugins. 2017-08-08 11:27:59 -07:00
61f40c72aa Add a registered documentation set. 2017-08-08 11:09:46 -07:00
ea5ff08c09 Add documentation type for a documentation entry. 2017-08-08 10:54:36 -07:00
5f80c0bf14 Add a documentation class. 2017-08-08 10:50:30 -07:00
7dcc80fcae Merge branch 'master' of github.com:DanilaFe/abacus 2017-08-08 10:19:45 -07:00
d10536155b Make the map private. 2017-08-08 10:13:35 -07:00
536cac7b23 Convert Reducer interface to Kotlin. 2017-08-08 10:11:30 -07:00
Arthur Drobot
b6e4c6d2ea Merge branch 'sig-fig' 2017-08-08 09:46:46 -07:00
Arthur Drobot
f8b3559cec Add some tests to pow. 2017-08-08 09:45:13 -07:00
Arthur Drobot
4cf4ba98a8 Adjust unit tests. Set the number of significant figures in the output to 50. 2017-08-08 09:27:10 -07:00
12710c625b Add missing comments. 2017-08-07 22:48:56 -07:00
e71b037195 Switch the ToggleablePlugin class to Kotlin to avoid boilerplate. 2017-08-07 22:44:16 -07:00
fe92929856 Switch HistoryModel to Kotlin to avoid boilerplate. 2017-08-07 22:39:35 -07:00
e61cfdca46 Create a documentation object class. 2017-08-07 22:30:32 -07:00
Arthur Drobot
68fbcd2d7c Modify exp to use the non-alternating series and take a reciprocal in the case of a negative argument. 2017-08-07 18:13:18 -07:00
Arthur Drobot
ed92b382f0 Rewrite precise number to limit significant figures rather than decimal places. Add getMaxError to NumberInterface; modify numbers and StandardPlugin appropriately. 2017-08-07 17:58:18 -07:00
91 changed files with 2321 additions and 1472 deletions

View File

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

4
core/build.gradle Normal file
View File

@@ -0,0 +1,4 @@
dependencies {
compile 'com.moandjiezana.toml:toml4j:0.7.1'
testCompile 'junit:junit:4.12'
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -128,5 +128,9 @@ public class NaiveNumber extends NumberInterface {
return Double.toString(Math.round(value * shiftBy) / shiftBy);
}
@Override
public NumberInterface getMaxError() {
return new NaiveNumber(Math.pow(10, -18));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,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
@@ -67,10 +62,21 @@ public abstract class Plugin {
* @param name the name to register by.
* @param toRegister the function implementation.
*/
protected final void registerFunction(String name, Function 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);
}
/**
* To be used in load(). Registers an operator abstract class
* with the plugin internally, which makes it accessible to
@@ -79,10 +85,22 @@ 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) {
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.
@@ -94,6 +112,16 @@ public abstract class Plugin {
manager.registerNumberImplementation(name, implementation);
}
/**
* To be used in load(). Registers a documentation instance
* used to explain some element of the plugin to the user.
*
* @param documentation the documentation instance.
*/
protected final void registerDocumentation(Documentation documentation) {
manager.registerDocumentation(documentation);
}
/**
* Searches the PluginManager for the given function name.
* This can be used by the plugins internally in order to call functions
@@ -102,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
@@ -114,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
@@ -130,6 +182,17 @@ public abstract class Plugin {
return manager.numberImplementationFor(name);
}
/**
* Searches the PluginManager for the given documentation name and type.
*
* @param name the name for which to search.
* @param type the type of documentation to search for.
* @return the found documentation, or null if none was found.
*/
protected final Documentation documentationFor(String name, DocumentationType type) {
return manager.documentationFor(name, type);
}
/**
* Searches the plugin manager for a Pi value for the given number implementation.
* This is done so that number implementations with various degrees of precision

View File

@@ -1,12 +1,14 @@
package org.nwapw.abacus.plugin;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NumberInterface;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A class that controls instances of plugins, allowing for them
@@ -25,15 +27,27 @@ public class PluginManager {
/**
* The map of functions registered by the plugins.
*/
private Map<String, Function> registeredFunctions;
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, Operator> registeredOperators;
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 implementations that have been
* found by their implementation class.
@@ -63,8 +77,11 @@ public class PluginManager {
loadedPluginClasses = new HashSet<>();
plugins = new HashSet<>();
registeredFunctions = new HashMap<>();
registeredTreeValueFunctions = new HashMap<>();
registeredOperators = new HashMap<>();
registeredTreeValueOperators = new HashMap<>();
registeredNumberImplementations = new HashMap<>();
registeredDocumentation = new HashSet<>();
cachedInterfaceImplementations = new HashMap<>();
cachedPi = new HashMap<>();
listeners = new HashSet<>();
@@ -72,58 +89,136 @@ public class PluginManager {
/**
* 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, Function function){
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, Operator operator){
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){
public void registerNumberImplementation(String name, NumberImplementation implementation) {
registeredNumberImplementations.put(name, implementation);
}
/**
* Registers the given documentation with the plugin manager,
* making it accessible to the plugin manager etc.
*
* @param documentation the documentation to register.
*/
public void registerDocumentation(Documentation documentation) {
registeredDocumentation.add(documentation);
}
/**
* Gets the function registered under the given name.
*
* @param name the name of the function.
* @return the function, or null if it was not found.
*/
public Function functionFor(String name){
public 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 Operator operatorFor(String name){
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){
public NumberImplementation numberImplementationFor(String name) {
return registeredNumberImplementations.get(name);
}
/**
* Gets the documentation for the given entity of the given type.
*
* @param name the name of the entity to search for.
* @param type the type that this entity is, to filter out similarly named documentation.
* @return the documentation object.
*/
public Documentation documentationFor(String name, DocumentationType type) {
Documentation toReturn = null;
for (Documentation entry : registeredDocumentation) {
if (entry.getCodeName().equals(name) && entry.getType() == type) {
toReturn = entry;
break;
}
}
if (toReturn == null) {
toReturn = new Documentation(name, "", "", "", type);
registerDocumentation(toReturn);
}
return toReturn;
}
/**
* Gets the number implementation for the given implementation class.
*
@@ -133,9 +228,9 @@ public class PluginManager {
public NumberImplementation interfaceImplementationFor(Class<? extends NumberInterface> name) {
if (cachedInterfaceImplementations.containsKey(name)) return cachedInterfaceImplementations.get(name);
NumberImplementation toReturn = null;
for(String key : registeredNumberImplementations.keySet()){
for (String key : registeredNumberImplementations.keySet()) {
NumberImplementation implementation = registeredNumberImplementations.get(key);
if(implementation.getImplementation() == name) {
if (implementation.getImplementation() == name) {
toReturn = implementation;
break;
}
@@ -187,6 +282,25 @@ public class PluginManager {
}
}
/**
* Removes the plugin with the given class from the manager.
*
* @param toRemove the plugin to remove.
*/
public void removeClass(Class<? extends Plugin> toRemove) {
if (!loadedPluginClasses.contains(toRemove)) return;
plugins.removeIf(plugin -> plugin.getClass() == toRemove);
loadedPluginClasses.remove(toRemove);
}
/**
* Removes all plugins from this plugin manager.
*/
public void removeAll() {
loadedPluginClasses.clear();
plugins.clear();
}
/**
* Loads all the plugins in the PluginManager.
*/
@@ -210,8 +324,11 @@ public class PluginManager {
plugin.disable();
}
registeredFunctions.clear();
registeredTreeValueFunctions.clear();
registeredOperators.clear();
registeredTreeValueOperators.clear();
registeredNumberImplementations.clear();
registeredDocumentation.clear();
cachedInterfaceImplementations.clear();
cachedPi.clear();
listeners.forEach(e -> e.onUnload(this));
@@ -234,6 +351,15 @@ public class PluginManager {
return registeredFunctions.keySet();
}
/**
* Gets all the tree vlaue functions loaded by the PluginManager.
*
* @return the set of all the tree value functions that were loaded.
*/
public Set<String> getAllTreeValueFunctions() {
return registeredTreeValueFunctions.keySet();
}
/**
* Gets all the operators loaded by the Plugin Manager.
*
@@ -243,6 +369,15 @@ public class PluginManager {
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.
*

View File

@@ -0,0 +1,829 @@
package org.nwapw.abacus.plugin;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.function.BiFunction;
/**
* The plugin providing standard functions such as addition and subtraction to
* the calculator.
*/
public class StandardPlugin extends Plugin {
/**
* The addition operator, +
*/
public static final NumberOperator OP_ADD = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 2;
}
@Override
public NumberInterface applyInternal(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(NumberInterface[] params) {
return params.length == 2;
}
@Override
public NumberInterface applyInternal(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(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(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(NumberInterface[] params) {
return params.length == 2;
}
@Override
public NumberInterface applyInternal(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 square root function.
*/
public static final NumberFunction FUNCTION_SQRT = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return OP_CARET.apply(params[0], ((new NaiveNumber(0.5)).promoteTo(params[0].getClass())));
}
};
/**
* The implementation for the infinite-precision BigDecimal.
*/
public static final NumberImplementation IMPLEMENTATION_PRECISE = new NumberImplementation(PreciseNumber.class, 0) {
@Override
public NumberInterface instanceForString(String string) {
return new PreciseNumber(string);
}
@Override
public NumberInterface instanceForPi() {
NumberInterface C = FUNCTION_SQRT.apply(new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
NumberInterface M = PreciseNumber.ONE;
NumberInterface L = new PreciseNumber("13591409");
NumberInterface X = M;
NumberInterface sum = L;
int termsNeeded = C.getMaxPrecision() / 13 + 1;
NumberInterface lSummand = new PreciseNumber("545140134");
NumberInterface xMultiplier = new PreciseNumber("262537412")
.multiply(new PreciseNumber("1000000000"))
.add(new PreciseNumber("640768000"))
.negate();
for (int i = 0; i < termsNeeded; i++) {
M = M
.multiply(new PreciseNumber((12 * i + 2) + ""))
.multiply(new PreciseNumber((12 * i + 6) + ""))
.multiply(new PreciseNumber((12 * i + 10) + ""))
.divide(new PreciseNumber(Math.pow(i + 1, 3) + ""));
L = L.add(lSummand);
X = X.multiply(xMultiplier);
sum = sum.add(M.multiply(L).divide(X));
}
return C.divide(sum);
}
};
/**
* Stores objects of NumberInterface with integer values for reuse.
*/
private final static HashMap<Class<? extends NumberInterface>, HashMap<Integer, NumberInterface>> integerValues = new HashMap<>();
/**
* The division operator, /
*/
public static final NumberOperator OP_DIVIDE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 2 && params[1].compareTo(fromInt(params[0].getClass(), 0)) != 0;
}
@Override
public NumberInterface applyInternal(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(NumberInterface[] params) {
return params.length == 1
&& params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0
&& params[0].signum() >= 0;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
if (params[0].signum() == 0) {
return fromInt(params[0].getClass(), 1);
}
NumberInterface one = fromInt(params[0].getClass(), 1);
NumberInterface factorial = params[0];
NumberInterface multiplier = params[0];
//It is necessary to later prevent calls of factorial on anything but non-negative integers.
while ((multiplier = multiplier.subtract(one)).signum() == 1) {
factorial = factorial.multiply(multiplier);
}
return factorial;
/*if(!storedList.containsKey(params[0].getClass())){
storedList.put(params[0].getClass(), new ArrayList<NumberInterface>());
storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass()));
storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass()));
}*/
}
};
/**
* The permutation operator.
*/
public static final NumberOperator OP_NPR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 2 && params[0].fractionalPart().signum() == 0
&& params[1].fractionalPart().signum() == 0;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
if (params[0].compareTo(params[1]) < 0 ||
params[0].signum() < 0 ||
(params[0].signum() == 0 && params[1].signum() != 0)) return fromInt(params[0].getClass(), 0);
NumberInterface total = fromInt(params[0].getClass(), 1);
NumberInterface multiplyBy = params[0];
NumberInterface remainingMultiplications = params[1];
NumberInterface halfway = params[0].divide(fromInt(params[0].getClass(), 2));
if (remainingMultiplications.compareTo(halfway) > 0) {
remainingMultiplications = params[0].subtract(remainingMultiplications);
}
while (remainingMultiplications.signum() > 0) {
total = total.multiply(multiplyBy);
remainingMultiplications = remainingMultiplications.subtract(fromInt(params[0].getClass(), 1));
multiplyBy = multiplyBy.subtract(fromInt(params[0].getClass(), 1));
}
return total;
}
};
/**
* The combination operator.
*/
public static final NumberOperator OP_NCR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 2 && params[0].fractionalPart().signum() == 0
&& params[1].fractionalPart().signum() == 0;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return OP_NPR.apply(params).divide(OP_FACTORIAL.apply(params[1]));
}
};
/**
* The absolute value function, abs(-3) = 3
*/
public static final NumberFunction FUNCTION_ABS = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return params[0].multiply(fromInt(params[0].getClass(), params[0].signum()));
}
};
/**
* The natural log function.
*/
public static final NumberFunction FUNCTION_LN = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1 && params[0].compareTo(fromInt(params[0].getClass(), 0)) > 0;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface param = params[0];
NumberInterface one = fromInt(param.getClass(), 1);
int powersOf2 = 0;
while (FUNCTION_ABS.apply(param.subtract(one)).compareTo(new NaiveNumber(0.1).promoteTo(param.getClass())) >= 0) {
if (param.subtract(one).signum() == 1) {
param = param.divide(fromInt(param.getClass(), 2));
powersOf2++;
if (param.subtract(one).signum() != 1) {
break;
//No infinite loop for you.
}
} else {
param = param.multiply(fromInt(param.getClass(), 2));
powersOf2--;
if (param.subtract(one).signum() != -1) {
break;
//No infinite loop for you.
}
}
}
return getLog2(param).multiply(fromInt(param.getClass(), powersOf2)).add(getLogPartialSum(param));
}
/**
* Returns the partial sum of the Taylor series for logx (around x=1).
* Automatically determines the number of terms needed based on the precision of x.
* @param x value at which the series is evaluated. 0 < x < 2. (x=2 is convergent but impractical.)
* @return the partial sum.
*/
private NumberInterface getLogPartialSum(NumberInterface x) {
NumberInterface maxError = x.getMaxError();
x = x.subtract(fromInt(x.getClass(), 1)); //Terms used are for log(x+1).
NumberInterface currentNumerator = x, currentTerm = x, sum = x;
int n = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) {
n++;
currentNumerator = currentNumerator.multiply(x).negate();
currentTerm = currentNumerator.divide(fromInt(x.getClass(), n));
sum = sum.add(currentTerm);
}
return sum;
}
/**
* Returns natural log of 2 to the required precision of the class of number.
* @param number a number of the same type as the return type. (Used for precision.)
* @return the value of log(2) with the appropriate precision.
*/
private NumberInterface getLog2(NumberInterface number) {
NumberInterface maxError = number.getMaxError();
//NumberInterface errorBound = fromInt(number.getClass(), 1);
//We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n)
//In the following, a=1/3^n, b=1/4^n, c = 1/n.
//a is also an error bound.
NumberInterface a = fromInt(number.getClass(), 1), b = a, c = a;
NumberInterface sum = fromInt(number.getClass(), 0);
NumberInterface one = fromInt(number.getClass(), 1);
int n = 0;
while (a.compareTo(maxError) >= 1) {
n++;
a = a.divide(fromInt(number.getClass(), 3));
b = b.divide(fromInt(number.getClass(), 4));
c = one.divide(fromInt(number.getClass(), n));
sum = sum.add(a.add(b).multiply(c));
}
return sum;
}
};
/**
* Gets a random number smaller or equal to the given number's integer value.
*/
public static final NumberFunction FUNCTION_RAND_INT = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return fromInt(params[0].getClass(), (int) Math.round(Math.random() * params[0].floor().intValue()));
}
};
private static final HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
/**
* The exponential function, exp(1) = e^1 = 2.71...
*/
public static final NumberFunction FUNCTION_EXP = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface maxError = params[0].getMaxError();
int n = 0;
if (params[0].signum() < 0) {
NumberInterface[] negatedParams = {params[0].negate()};
return fromInt(params[0].getClass(), 1).divide(applyInternal(negatedParams));
} else {
//We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!.
//right and left refer to lhs and rhs in the above inequality.
NumberInterface sum = fromInt(params[0].getClass(), 1);
NumberInterface nextNumerator = params[0];
NumberInterface left = params[0].multiply(fromInt(params[0].getClass(), 3).intPow(params[0].ceiling().intValue())), right = maxError;
do {
sum = sum.add(nextNumerator.divide(factorial(params[0].getClass(), n + 1)));
n++;
nextNumerator = nextNumerator.multiply(params[0]);
left = left.multiply(params[0]);
NumberInterface nextN = fromInt(params[0].getClass(), n + 1);
right = right.multiply(nextN);
//System.out.println(left + ", " + right);
}
while (left.compareTo(right) > 0);
//System.out.println(n+1);
return sum;
}
}
};
/**
* The caret / pow operator, ^
*/
public static final NumberOperator OP_CARET = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2) {
@Override
public boolean matchesParams(NumberInterface[] params) {
NumberInterface zero = fromInt(params[0].getClass(), 0);
return params.length == 2
&& !(params[0].compareTo(zero) == 0
&& params[1].compareTo(zero) == 0)
&& !(params[0].signum() == -1 && params[1].fractionalPart().compareTo(zero) != 0);
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface zero = fromInt(params[0].getClass(), 0);
if (params[0].compareTo(zero) == 0)
return zero;
else if (params[1].compareTo(zero) == 0)
return fromInt(params[0].getClass(), 1);
//Detect integer bases:
if (params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0
&& FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[0].getClass(), Integer.MAX_VALUE)) < 0
&& FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[1].getClass(), 1)) >= 0) {
NumberInterface[] newParams = {params[0], params[1].fractionalPart()};
return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(newParams));
}
return FUNCTION_EXP.apply(FUNCTION_LN.apply(FUNCTION_ABS.apply(params[0])).multiply(params[1]));
}
};
/**
* The sine function (the argument is interpreted in radians).
*/
public final NumberFunction functionSin = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface pi = piFor(params[0].getClass());
NumberInterface twoPi = pi.multiply(fromInt(pi.getClass(), 2));
NumberInterface theta = getSmallAngle(params[0], pi);
//System.out.println(theta);
if (theta.compareTo(pi.multiply(new NaiveNumber(1.5).promoteTo(twoPi.getClass()))) >= 0) {
theta = theta.subtract(twoPi);
} else if (theta.compareTo(pi.divide(fromInt(pi.getClass(), 2))) > 0) {
theta = pi.subtract(theta);
}
//System.out.println(theta);
return sinTaylor(theta);
}
};
/**
* The cosine function (the argument is in radians).
*/
public final NumberFunction functionCos = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(params[0]));
}
};
/**
* The tangent function (the argument is in radians).
*/
public final NumberFunction functionTan = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(params[0]).divide(functionCos.apply(params[0]));
}
};
/**
* The secant function (the argument is in radians).
*/
public final NumberFunction functionSec = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return fromInt(params[0].getClass(), 1).divide(functionCos.apply(params[0]));
}
};
/**
* The cosecant function (the argument is in radians).
*/
public final NumberFunction functionCsc = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return fromInt(params[0].getClass(), 1).divide(functionSin.apply(params[0]));
}
};
/**
* The cotangent function (the argument is in radians).
*/
public final NumberFunction functionCot = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return functionCos.apply(params[0]).divide(functionSin.apply(params[0]));
}
};
/**
* The arcsine function (return type in radians).
*/
public final NumberFunction functionArcsin = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1
&& FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) <= 0;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
if (FUNCTION_ABS.apply(params[0]).compareTo(new NaiveNumber(0.8).promoteTo(params[0].getClass())) >= 0) {
NumberInterface[] newParams = {FUNCTION_SQRT.apply(fromInt(params[0].getClass(), 1).subtract(params[0].multiply(params[0])))};
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(applyInternal(newParams)).multiply(fromInt(params[0].getClass(), params[0].signum()));
}
NumberInterface currentTerm = params[0], sum = currentTerm,
multiplier = currentTerm.multiply(currentTerm), summandBound = sum.getMaxError().multiply(fromInt(sum.getClass(), 1).subtract(multiplier)),
power = currentTerm, coefficient = fromInt(params[0].getClass(), 1);
int exponent = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(summandBound) > 0) {
exponent += 2;
power = power.multiply(multiplier);
coefficient = coefficient.multiply(fromInt(params[0].getClass(), exponent - 2))
.divide(fromInt(params[0].getClass(), exponent - 1));
currentTerm = power.multiply(coefficient).divide(fromInt(power.getClass(), exponent));
sum = sum.add(currentTerm);
}
return sum;
}
};
/**
* The arccosine function.
*/
public final NumberFunction functionArccos = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) <= 0;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(functionArcsin.apply(params));
}
};
/**
* The arccosecant function.
*/
public final NumberFunction functionArccsc = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) >= 0;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface[] reciprocalParamArr = {fromInt(params[0].getClass(), 1).divide(params[0])};
return functionArcsin.apply(reciprocalParamArr);
}
};
/**
* The arcsecant function.
*/
public final NumberFunction functionArcsec = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1 && FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), 1)) >= 0;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface[] reciprocalParamArr = {fromInt(params[0].getClass(), 1).divide(params[0])};
return functionArccos.apply(reciprocalParamArr);
}
};
/**
* The arctangent function.
*/
public final NumberFunction functionArctan = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
if (params[0].signum() == -1) {
NumberInterface[] negatedParams = {params[0].negate()};
return applyInternal(negatedParams).negate();
}
if (params[0].compareTo(fromInt(params[0].getClass(), 1)) > 0) {
NumberInterface[] reciprocalParams = {fromInt(params[0].getClass(), 1).divide(params[0])};
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(applyInternal(reciprocalParams));
}
if (params[0].compareTo(fromInt(params[0].getClass(), 1)) == 0) {
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 4));
}
if (params[0].compareTo(new NaiveNumber(0.9).promoteTo(params[0].getClass())) >= 0) {
NumberInterface[] newParams = {params[0].multiply(fromInt(params[0].getClass(), 2))
.divide(fromInt(params[0].getClass(), 1).subtract(params[0].multiply(params[0])))};
return applyInternal(newParams).divide(fromInt(params[0].getClass(), 2));
}
NumberInterface currentPower = params[0], currentTerm = currentPower, sum = currentTerm,
maxError = params[0].getMaxError(), multiplier = currentPower.multiply(currentPower).negate();
int n = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) {
n += 2;
currentPower = currentPower.multiply(multiplier);
currentTerm = currentPower.divide(fromInt(currentPower.getClass(), n));
sum = sum.add(currentTerm);
}
return sum;
}
};
/**
* The arccotangent function. Range: (0, pi).
*/
public final NumberFunction functionArccot = new NumberFunction() {
@Override
public boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(functionArctan.apply(params));
}
};
public StandardPlugin(PluginManager manager) {
super(manager);
}
/**
* Returns a partial sum of a series whose terms are given by the nthTermFunction, evaluated at x.
*
* @param x the value at which the series is evaluated.
* @param nthTermFunction the function that returns the nth term of the series, in the format term(x, n).
* @param n the number of terms in the partial sum.
* @return the value of the partial sum that has the same class as x.
*/
private static NumberInterface sumSeries(NumberInterface x, BiFunction<Integer, NumberInterface, NumberInterface> nthTermFunction, int n) {
NumberInterface sum = fromInt(x.getClass(), 0);
for (int i = 0; i <= n; i++) {
sum = sum.add(nthTermFunction.apply(i, x));
}
return sum;
}
/**
* A factorial function that uses memoization for each number class; it efficiently
* computes factorials of non-negative integers.
*
* @param numberClass type of number to return.
* @param n non-negative integer.
* @return a number of numClass with value n factorial.
*/
public static NumberInterface factorial(Class<? extends NumberInterface> numberClass, int n) {
if (!FACTORIAL_LISTS.containsKey(numberClass)) {
FACTORIAL_LISTS.put(numberClass, new ArrayList<>());
FACTORIAL_LISTS.get(numberClass).add(fromInt(numberClass, 1));
FACTORIAL_LISTS.get(numberClass).add(fromInt(numberClass, 1));
}
ArrayList<NumberInterface> list = FACTORIAL_LISTS.get(numberClass);
if (n >= list.size()) {
while (list.size() < n + 16) {
list.add(list.get(list.size() - 1).multiply(fromInt(numberClass, list.size())));
}
}
return list.get(n);
}
/**
* Returns the value of the Taylor series for sin (centered at 0) at x.
*
* @param x where the series is evaluated.
* @return the value of the series
*/
private static NumberInterface sinTaylor(NumberInterface x) {
NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x;
NumberInterface maxError = x.getMaxError();
int n = 1;
do {
n += 2;
power = power.multiply(multiplier);
currentTerm = power.divide(factorial(x.getClass(), n));
sum = sum.add(currentTerm);
} while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0);
return sum;
}
/**
* Returns an equivalent angle in the interval [0, 2pi)
*
* @param phi an angle (in radians).
* @return theta in [0, 2pi) that differs from phi by a multiple of 2pi.
*/
private static NumberInterface getSmallAngle(NumberInterface phi, NumberInterface pi) {
NumberInterface twoPi = pi.multiply(fromInt(pi.getClass(), 2));
NumberInterface theta = FUNCTION_ABS.apply(phi).subtract(twoPi
.multiply(FUNCTION_ABS.apply(phi).divide(twoPi).floor())); //Now theta is in [0, 2pi).
if (phi.signum() < 0) {
theta = twoPi.subtract(theta);
}
return theta;
}
/**
* Returns a number of class numType with value n.
*
* @param numType class of number to return.
* @param n value of returned number.
* @return numClass instance with value n.
*/
private static NumberInterface fromInt(Class<? extends NumberInterface> numType, int n) {
if (!integerValues.containsKey(numType)) {
integerValues.put(numType, new HashMap<>());
}
if (!integerValues.get(numType).containsKey(n)) {
integerValues.get(numType).put(n, new NaiveNumber(n).promoteTo(numType));
}
return integerValues.get(numType).get(n);
}
@Override
public void onEnable() {
registerNumberImplementation("naive", IMPLEMENTATION_NAIVE);
registerNumberImplementation("precise", IMPLEMENTATION_PRECISE);
registerOperator("+", OP_ADD);
registerOperator("-", OP_SUBTRACT);
registerOperator("`", OP_NEGATE);
registerOperator("*", OP_MULTIPLY);
registerOperator("/", OP_DIVIDE);
registerOperator("^", OP_CARET);
registerOperator("!", OP_FACTORIAL);
registerOperator("nPr", OP_NPR);
registerOperator("nCr", OP_NCR);
registerFunction("abs", FUNCTION_ABS);
registerFunction("exp", FUNCTION_EXP);
registerFunction("ln", FUNCTION_LN);
registerFunction("sqrt", FUNCTION_SQRT);
registerFunction("sin", functionSin);
registerFunction("cos", functionCos);
registerFunction("tan", functionTan);
registerFunction("sec", functionSec);
registerFunction("csc", functionCsc);
registerFunction("cot", functionCot);
registerFunction("arcsin", functionArcsin);
registerFunction("arccos", functionArccos);
registerFunction("arctan", functionArctan);
registerFunction("arcsec", functionArcsec);
registerFunction("arccsc", functionArccsc);
registerFunction("arccot", functionArccot);
registerFunction("random_int", FUNCTION_RAND_INT);
registerDocumentation(new Documentation("abs", "Absolute Value", "Finds the distance " +
"from zero of a number.", "Given a number, this function finds the distance form " +
"zero of a number, effectively turning negative numbers into positive ones.\n\n" +
"Example: abs(-2) -> 2", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("exp", "Exponentiate", "Brings e to the given power.",
"This function evaluates e to the power of the given value, and is the inverse " +
"of the natural logarithm.\n\n" +
"Example: exp(1) -> 2.718...", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("ln", "Natural Logarithm", "Gets the natural " +
"logarithm of the given value.", "The natural logarithm of a number is " +
"the power that e has to be brought to to be equal to the number.\n\n" +
"Example: ln(2.718) -> 1", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("sqrt", "Square Root", "Finds the square root " +
"of the number.", "A square root a of a number is defined as the non-negative a such that a times a is equal " +
"to that number.\n\n" +
"Example: sqrt(4) -> 2", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("sin", "Sine", "Computes the sine of the given angle, " +
"in radians.", "Example: sin(pi/6) -> 0.5", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("cos", "Cosine", "Computes the cosine of the given angle, " +
"in radians.", "Example: cos(pi/6) -> 0.866... (the exact result is sqrt(3)/2)", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("tan", "Tangent", "Computes the tangent of the given angle, " +
"in radians.", "Example: tan(pi/6) -> 0.577... (the exact result is 1/sqrt(3))", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("sec", "Secant", "Computes the secant of the given angle, " +
"in radians.", "Example: sec(pi/6) -> 1.154... (the exact result is 2/sqrt(3))", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("csc", "Cosecant", "Computes the cosecant of the given angle, " +
"in radians.", "Example: csc(pi/6) -> 2", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("cot", "Cotangent", "Computes the cotangent of the given angle, " +
"in radians.", "Example: cot(pi/6) -> 1.732... (the exact result is sqrt(3))", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("random_int", "Random Integer", "Generates a random integer [0, n].",
"Generates a pseudorandom number using the standard JVM random mechanism, keeping it less than or " +
"equal to the given number.\n\n" +
"Example: random_int(5) -> 4\n" +
"random_int(5) -> 3\n" +
"random_int(5) -> 3\n", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("arcsin", "Arcsine", "Computes the arcsine of x. (The result is in radians.)",
"Example: arcsin(0.5) -> 0.523... (the exact result is pi/6)", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("arccos", "Arccosine", "Computes the arccosine of x. (The result is in radians.)",
"Example: arccos(0.5) -> 1.047... (the exact result is pi/3)", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("arctan", "Arctangent", "Computes the arctangent of x. (The result is in radians.)",
"Example: arctan(1) -> 0.785... (the exact result is pi/4)", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("arcsec", "Arcsecant", "Computes the arcsecant of x. (The result is in radians.)",
"Example: arcsec(2) -> 1.047... (the exact result is pi/3)", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("arccsc", "Arccosecant", "Computes the arcscosecant of x. (The result is in radians.)",
"Example: arccsc(2) -> 0.523... (the exact result is pi/6)", DocumentationType.FUNCTION));
registerDocumentation(new Documentation("arccot", "Arccotangent", "Computes the arccotangent of x. (The result is in radians," +
" in the range (0, pi).)",
"Example: arccot(0) -> 1.570... (the exact result is pi/2)", DocumentationType.FUNCTION));
}
@Override
public void onDisable() {
}
}

View File

@@ -0,0 +1,76 @@
package org.nwapw.abacus.tree;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NumberInterface;
/**
* A reducer implementation that turns a tree into a single number.
* This is not always guaranteed to work.
*/
public class NumberReducer implements Reducer<NumberInterface> {
/**
* The plugin manager from which to draw the functions.
*/
private Abacus abacus;
/**
* Creates a new number reducer.
*
* @param abacus the calculator instance.
*/
public NumberReducer(Abacus abacus) {
this.abacus = abacus;
}
@Override
public NumberInterface reduceNode(TreeNode node, Object... children) {
if (node instanceof NumberNode) {
return abacus.numberFromString(((NumberNode) node).getNumber());
} else if(node instanceof VariableNode) {
return abacus.numberFromString("0");
} else if (node instanceof NumberBinaryNode) {
NumberInterface left = (NumberInterface) children[0];
NumberInterface right = (NumberInterface) children[1];
NumberOperator operator = abacus.getPluginManager().operatorFor(((BinaryNode) node).getOperation());
return operator.apply(left, right);
} else if (node instanceof NumberUnaryNode) {
NumberInterface child = (NumberInterface) children[0];
NumberOperator operator = abacus.getPluginManager().operatorFor(((UnaryNode) node).getOperation());
return operator.apply(child);
} else if (node instanceof FunctionNode) {
NumberInterface[] convertedChildren = new NumberInterface[children.length];
for (int i = 0; i < convertedChildren.length; i++) {
convertedChildren[i] = (NumberInterface) children[i];
}
NumberFunction function = abacus.getPluginManager().functionFor(((FunctionNode) node).getCallTo());
if (function == null) return null;
return function.apply(convertedChildren);
} else if (node instanceof TreeValueFunctionNode){
CallNode callNode = (CallNode) node;
TreeNode[] realChildren = new TreeNode[callNode.getChildren().size()];
for(int i = 0; i < realChildren.length; i++){
realChildren[i] = callNode.getChildren().get(i);
}
TreeValueFunction function =
abacus.getPluginManager().treeValueFunctionFor(callNode.getCallTo());
if(function == null) return null;
return function.applyWithReducer(this, realChildren);
} else if (node instanceof TreeValueBinaryNode) {
BinaryNode binaryNode = (BinaryNode) node;
TreeValueOperator operator = abacus.getPluginManager()
.treeValueOperatorFor(binaryNode.getOperation());
if(operator == null) return null;
return operator.applyWithReducer(this, binaryNode.getLeft(), binaryNode.getRight());
} else if(node instanceof TreeValueUnaryNode) {
UnaryNode unaryNode = (UnaryNode) node;
TreeValueOperator operator = abacus.getPluginManager()
.treeValueOperatorFor(unaryNode.getOperation());
if(operator == null) return null;
return operator.applyWithReducer(this, unaryNode.getApplyTo());
}
return null;
}
}

View File

@@ -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.

View File

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

View File

@@ -0,0 +1,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>

View File

@@ -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>

View File

@@ -3,12 +3,11 @@ package org.nwapw.abacus.function
/**
* A single operator that can be used by Abacus.
*
* This is a data class that holds the information about a single operator, such as a plus or minus.
* 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.
* @param function the function this operator applies to its arguments.
*/
data class Operator(val associativity: OperatorAssociativity, val type: OperatorType,
val precedence: Int, val function: Function)
open class Operator(val associativity: OperatorAssociativity, val type: OperatorType,
val precedence: Int)

View File

@@ -0,0 +1,13 @@
package org.nwapw.abacus.function
import org.nwapw.abacus.function.applicable.ReducerApplicable
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 : ReducerApplicable<TreeNode, NumberInterface, NumberInterface>

View File

@@ -0,0 +1,18 @@
package org.nwapw.abacus.function
import org.nwapw.abacus.function.applicable.ReducerApplicable
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),
ReducerApplicable<TreeNode, NumberInterface, NumberInterface>

View File

@@ -0,0 +1,40 @@
package org.nwapw.abacus.function.applicable
/**
* 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(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(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(vararg params: T): O? {
if (!matchesParams(params)) return null
return applyInternal(params)
}
}

View File

@@ -0,0 +1,43 @@
package org.nwapw.abacus.function.applicable
import org.nwapw.abacus.tree.Reducer
/**
* Applicable that requires a reducer.
*
* ReducerApplicable slightly more specific Applicable that requires a reducer
* to be passed to it along with the parameters.
* @param <T> the type of the input arguments.
* @param <O> the return type of the application.
* @param <R> the required type of the reducer.
*/
interface ReducerApplicable<in T : Any, out O : Any, in R : Any> {
/**
* Checks if this applicable can be applied to the
* given parameters.
* @param params the parameters to check.
*/
fun matchesParams(params: Array<out T>): Boolean
/**
* Applies this applicable to the given arguments, and reducer.
* @param reducer the reducer to use in the application.
* @param params the arguments to apply to.
* @return the result of the application.
*/
fun applyWithReducerInternal(reducer: Reducer<R>, params: Array<out T>): O?
/**
* Applies this applicable to the given arguments, and reducer,
* if the arguments and reducer are compatible with this applicable.
* @param reducer the reducer to use in the application.
* @param params the arguments to apply to.
* @return the result of the application, or null if the arguments are incompatible.
*/
fun applyWithReducer(reducer: Reducer<R>, vararg params: T): O? {
if (!matchesParams(params)) return null
return applyWithReducerInternal(reducer, params)
}
}

View File

@@ -11,13 +11,7 @@ package org.nwapw.abacus.tree
* @param left the left node.
* @param right the right node.
*/
data class BinaryNode(val operation: String, val left: TreeNode? = null, val right: TreeNode?) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
val leftReduce = left?.reduce(reducer) ?: return null
val rightReduce = right?.reduce(reducer) ?: return null
return reducer.reduceNode(this, leftReduce, rightReduce)
}
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") + ")"

View File

@@ -0,0 +1,29 @@
package org.nwapw.abacus.tree
/**
* Represents a more generic function call.
*
* This class does not specify how it should be reduced, allowing other classes
* to extend this functionality.
*
* @param callTo the name of the things being called.
*/
abstract class CallNode(val callTo: String) : TreeNode() {
/**
* The list of children this node has.
*/
val children: MutableList<TreeNode> = mutableListOf()
override fun toString(): String {
val buffer = StringBuffer()
buffer.append(callTo)
buffer.append("(")
for (i in 0 until children.size) {
buffer.append(children[i].toString())
buffer.append(if (i != children.size - 1) ", " else ")")
}
return buffer.toString()
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
package org.nwapw.abacus.tree
/**
* A tree node that 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)
}
}

View File

@@ -0,0 +1,16 @@
package org.nwapw.abacus.tree
/**
* A tree node that represents a tree value function call.
*
* This is in many ways similar to a simple FunctionNode, and the distinction
* is mostly to help the reducer. Besides that, this class also does not
* even attempt to reduce its children.
*/
class TreeValueFunctionNode(name: String) : CallNode(name) {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
return reducer.reduceNode(this)
}
}

View File

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

View File

@@ -9,12 +9,7 @@ package org.nwapw.abacus.tree
* @param operation the operation applied to the given node.
* @param applyTo the node to which the operation will be applied.
*/
data class UnaryNode(val operation: String, val applyTo: TreeNode? = null) : TreeNode() {
override fun <T : Any> reduce(reducer: Reducer<T>): T? {
val reducedChild = applyTo?.reduce(reducer) ?: return null
return reducer.reduceNode(this, reducedChild)
}
abstract class UnaryNode(val operation: String, val applyTo: TreeNode? = null) : TreeNode() {
override fun toString(): String {
return "(" + (applyTo?.toString() ?: "null") + ")" + operation

View File

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

View File

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

View File

@@ -5,10 +5,7 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.function.OperatorAssociativity;
import org.nwapw.abacus.function.OperatorType;
import org.nwapw.abacus.function.*;
import org.nwapw.abacus.lexing.pattern.Match;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.parsing.LexerTokenizer;
@@ -21,24 +18,46 @@ public class TokenizerTests {
private static Abacus abacus = new Abacus(new Configuration(0, "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(NumberInterface[] params) {
return params.length == 2;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
public NumberInterface applyInternal(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(NumberInterface[] params) {
return true;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return subtractFunction.apply(params);
}
});
registerOperator("-", new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX,
0) {
@Override
public boolean matchesParams(NumberInterface[] params) {
return true;
}
@Override
public NumberInterface applyInternal(NumberInterface[] params) {
return subtractFunction.apply(params);
}
});
registerFunction("subtract", subtractFunction);
}

7
docs/404.html Normal file
View File

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

27
docs/Gemfile Normal file
View File

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

58
docs/Gemfile.lock Normal file
View File

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

43
docs/_config.yml Normal file
View File

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

View File

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

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

View File

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

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

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

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

@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html>
{% include head.html %}
<style>
body {
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;
}
</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"/>
</body>
</html>

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

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

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

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

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

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

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

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

6
docs/index.md Normal file
View File

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

7
fx/build.gradle Normal file
View File

@@ -0,0 +1,7 @@
apply plugin: 'application'
dependencies {
compile project(':core')
}
mainClassName = 'org.nwapw.abacus.fx.AbacusApplication'

View File

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

View File

@@ -1,8 +1,6 @@
package org.nwapw.abacus.fx;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
@@ -14,6 +12,8 @@ import javafx.util.Callback;
import javafx.util.StringConverter;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.function.Documentation;
import org.nwapw.abacus.function.DocumentationType;
import org.nwapw.abacus.number.ComputationInterruptedException;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.plugin.ClassFinder;
@@ -24,7 +24,10 @@ import org.nwapw.abacus.tree.TreeNode;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -97,7 +100,7 @@ public class AbacusController implements PluginListener {
@FXML
private TextField computationLimitField;
@FXML
private ListView<String> functionListView;
private ListView<Documentation> functionListView;
@FXML
private TextField functionListSearchField;
@@ -120,46 +123,22 @@ public class AbacusController implements PluginListener {
/**
* The list of functions that are registered in the calculator.
*/
private ObservableList<String> functionList;
private ObservableList<Documentation> functionList;
/**
* The filtered list displayed to the user.
*/
private FilteredList<String> functionFilter;
private FilteredList<Documentation> functionFilter;
/**
* The abacus instance used for changing the plugin configuration.
*/
private Abacus abacus;
/**
* Boolean which represents whether changes were made to the configuration.
*/
private boolean changesMade;
/**
* Whether an alert about changes to the configuration was already shown.
*/
private boolean reloadAlertShown;
/**
* The alert shown when a press to "apply" is needed.
*/
private Alert reloadAlert;
/**
* The runnable that takes care of killing computations that take too long.
*/
private final Runnable TIMER_RUNNABLE = () -> {
try {
Configuration abacusConfig = abacus.getConfiguration();
if(abacusConfig.getComputationDelay() == 0) return;
Thread.sleep((long) (abacusConfig.getComputationDelay() * 1000));
performStop();
} catch (InterruptedException e) { }
};
/**
* The runnable used to perform the calculation.
*/
private final Runnable CALCULATION_RUNNABLE = new Runnable() {
private String attemptCalculation(){
private String attemptCalculation() {
try {
TreeNode constructedTree = abacus.parseString(inputField.getText());
if (constructedTree == null) {
@@ -174,7 +153,7 @@ public class AbacusController implements PluginListener {
return resultingString;
} catch (ComputationInterruptedException exception) {
return ERR_STOP;
} catch (RuntimeException exception){
} catch (RuntimeException exception) {
exception.printStackTrace();
return ERR_EXCEPTION;
}
@@ -191,6 +170,18 @@ public class AbacusController implements PluginListener {
});
}
};
/**
* Boolean which represents whether changes were made to the configuration.
*/
private boolean changesMade;
/**
* Whether an alert about changes to the configuration was already shown.
*/
private boolean reloadAlertShown;
/**
* The alert shown when a press to "apply" is needed.
*/
private Alert reloadAlert;
/**
* The thread that is waiting to pause the calculation.
*/
@@ -199,6 +190,18 @@ public class AbacusController implements PluginListener {
* The thread in which the computation runs.
*/
private Thread calculationThread;
/**
* The runnable that takes care of killing computations that take too long.
*/
private final Runnable TIMER_RUNNABLE = () -> {
try {
Configuration abacusConfig = abacus.getConfiguration();
if (abacusConfig.getComputationDelay() == 0) return;
Thread.sleep((long) (abacusConfig.getComputationDelay() * 1000));
performStop();
} catch (InterruptedException e) {
}
};
/**
* Alerts the user if the changes they made
@@ -216,7 +219,7 @@ public class AbacusController implements PluginListener {
Callback<TableColumn<HistoryModel, String>, TableCell<HistoryModel, String>> cellFactory =
param -> new CopyableCell<>();
Callback<ListView<ToggleablePlugin>, ListCell<ToggleablePlugin>> pluginCellFactory =
param -> new CheckBoxListCell<>(ToggleablePlugin::enabledProperty, new StringConverter<ToggleablePlugin>() {
param -> new CheckBoxListCell<>(ToggleablePlugin::getEnabledProperty, new StringConverter<ToggleablePlugin>() {
@Override
public String toString(ToggleablePlugin object) {
return object.getClassName().substring(object.getClassName().lastIndexOf('.') + 1);
@@ -224,15 +227,15 @@ public class AbacusController implements PluginListener {
@Override
public ToggleablePlugin fromString(String string) {
return new ToggleablePlugin(true, string);
return new ToggleablePlugin(string, true);
}
});
functionList = FXCollections.observableArrayList();
functionFilter = new FilteredList<>(functionList, (s) -> true);
functionListView.setItems(functionFilter);
functionListSearchField.textProperty().addListener((observable, oldValue, newValue) ->
functionFilter.setPredicate((newValue.length() == 0) ? ((s) -> true) : ((s) -> s.contains(newValue))));
functionFilter.setPredicate((newValue.length() == 0) ? ((s) -> true) : ((s) -> s.matches(newValue))));
functionListView.setCellFactory(param -> new DocumentationCell());
historyData = FXCollections.observableArrayList();
historyTable.setItems(historyData);
numberImplementationOptions = FXCollections.observableArrayList();
@@ -243,11 +246,11 @@ public class AbacusController implements PluginListener {
enabledPluginView.setItems(enabledPlugins);
enabledPluginView.setCellFactory(pluginCellFactory);
inputColumn.setCellFactory(cellFactory);
inputColumn.setCellValueFactory(cell -> cell.getValue().inputProperty());
inputColumn.setCellValueFactory(cell -> cell.getValue().getInputProperty());
parsedColumn.setCellFactory(cellFactory);
parsedColumn.setCellValueFactory(cell -> cell.getValue().parsedProperty());
parsedColumn.setCellValueFactory(cell -> cell.getValue().getParsedProperty());
outputColumn.setCellFactory(cellFactory);
outputColumn.setCellValueFactory(cell -> cell.getValue().outputProperty());
outputColumn.setCellValueFactory(cell -> cell.getValue().getOutputProperty());
coreTabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue.equals(settingsTab)) alertIfApplyNeeded(true);
});
@@ -255,17 +258,11 @@ public class AbacusController implements PluginListener {
abacus = new Abacus(new Configuration(CONFIG_FILE));
PluginManager abacusPluginManager = abacus.getPluginManager();
abacusPluginManager.addListener(this);
abacusPluginManager.addInstantiated(new StandardPlugin(abacus.getPluginManager()));
try {
ClassFinder.loadJars("plugins").forEach(abacusPluginManager::addClass);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
abacusPluginManager.reload();
performScan();
computationLimitField.setText(Double.toString(abacus.getConfiguration().getComputationDelay()));
computationLimitField.textProperty().addListener((observable, oldValue, newValue) -> {
if(!newValue.matches("(\\d+(\\.\\d*)?)?")) {
if (!newValue.matches("(\\d+(\\.\\d*)?)?")) {
computationLimitField.setText(oldValue);
} else {
changesMade = true;
@@ -292,12 +289,12 @@ public class AbacusController implements PluginListener {
}
@FXML
public void performStop(){
if(calculationThread != null) {
public void performStop() {
if (calculationThread != null) {
calculationThread.interrupt();
calculationThread = null;
}
if(computationLimitThread != null){
if (computationLimitThread != null) {
computationLimitThread.interrupt();
computationLimitThread = null;
}
@@ -311,6 +308,19 @@ public class AbacusController implements PluginListener {
reloadAlertShown = false;
}
@FXML
public void performScan() {
PluginManager abacusPluginManager = abacus.getPluginManager();
abacusPluginManager.removeAll();
abacusPluginManager.addInstantiated(new StandardPlugin(abacus.getPluginManager()));
try {
ClassFinder.loadJars("plugins").forEach(abacusPluginManager::addClass);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
abacusPluginManager.reload();
}
@FXML
public void performReload() {
alertIfApplyNeeded(true);
@@ -326,7 +336,7 @@ public class AbacusController implements PluginListener {
for (ToggleablePlugin pluginEntry : enabledPlugins) {
if (!pluginEntry.isEnabled()) disabledPlugins.add(pluginEntry.getClassName());
}
if(computationLimitField.getText().matches("\\d*(\\.\\d+)?") && computationLimitField.getText().length() != 0)
if (computationLimitField.getText().matches("\\d*(\\.\\d+)?") && computationLimitField.getText().length() != 0)
configuration.setComputationDelay(Double.parseDouble(computationLimitField.getText()));
configuration.saveTo(CONFIG_FILE);
changesMade = false;
@@ -343,12 +353,16 @@ public class AbacusController implements PluginListener {
numberImplementationBox.getSelectionModel().select(toSelect);
for (Class<?> pluginClass : abacus.getPluginManager().getLoadedPluginClasses()) {
String fullName = pluginClass.getName();
ToggleablePlugin plugin = new ToggleablePlugin(!disabledPlugins.contains(fullName), fullName);
plugin.enabledProperty().addListener(e -> changesMade = true);
ToggleablePlugin plugin = new ToggleablePlugin(fullName, !disabledPlugins.contains(fullName));
plugin.getEnabledProperty().addListener(e -> changesMade = true);
enabledPlugins.add(plugin);
}
functionList.addAll(manager.getAllFunctions());
functionList.sort(String::compareTo);
PluginManager pluginManager = abacus.getPluginManager();
functionList.addAll(manager.getAllFunctions().stream().map(name -> pluginManager.documentationFor(name, DocumentationType.FUNCTION))
.collect(Collectors.toCollection(ArrayList::new)));
functionList.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

View File

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

View File

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

View File

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

View File

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

BIN
image/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

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

View File

@@ -1,39 +0,0 @@
package org.nwapw.abacus.function;
import org.nwapw.abacus.number.NumberInterface;
/**
* A function that operates on one or more
* inputs and returns a single number.
*/
public abstract class Function {
/**
* Checks whether the given params will work for the given function.
*
* @param params the given params
* @return true if the params can be used with this function.
*/
protected abstract boolean matchesParams(NumberInterface[] params);
/**
* Internal apply implementation, which already receives appropriately promoted
* parameters that have bee run through matchesParams
*
* @param params the promoted parameters.
* @return the return value of the function.
*/
protected abstract NumberInterface applyInternal(NumberInterface[] params);
/**
* Function to check, promote arguments and run the function.
*
* @param params the raw input parameters.
* @return the return value of the function, or null if an error occurred.
*/
public NumberInterface apply(NumberInterface... params) {
if (!matchesParams(params)) return null;
return applyInternal(params);
}
}

View File

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

View File

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

View File

@@ -1,592 +0,0 @@
package org.nwapw.abacus.plugin;
import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.function.OperatorAssociativity;
import org.nwapw.abacus.function.OperatorType;
import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.function.BiFunction;
/**
* The plugin providing standard functions such as addition and subtraction to
* the calculator.
*/
public class StandardPlugin extends Plugin {
/**
* Stores objects of NumberInterface with integer values for reuse.
*/
private final static HashMap<Class<? extends NumberInterface>, HashMap<Integer, NumberInterface>> integerValues = new HashMap<>();
/**
* The addition operator, +
*/
public static final Operator OP_ADD = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length >= 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface sum = params[0];
for (int i = 1; i < params.length; i++) {
sum = sum.add(params[i]);
}
return sum;
}
});
/**
* The subtraction operator, -
*/
public static final Operator OP_SUBTRACT = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].subtract(params[1]);
}
});
/**
* The negation operator, -
*/
public static final Operator OP_NEGATE = new Operator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].negate();
}
});
/**
* The multiplication operator, *
*/
public static final Operator OP_MULTIPLY = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length >= 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface product = params[0];
for (int i = 1; i < params.length; i++) {
product = product.multiply(params[i]);
}
return product;
}
});
/**
* The division operator, /
*/
public static final Operator OP_DIVIDE = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2 && params[1].compareTo(NaiveNumber.ZERO.promoteTo(params[1].getClass())) != 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].divide(params[1]);
}
});
/**
* The factorial operator, !
*/
public static final Operator OP_FACTORIAL = new Operator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0, new Function() {
//private HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> storedList = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>();
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1
&& params[0].fractionalPart().compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0
&& params[0].signum() >= 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
if (params[0].signum() == 0) {
return fromInt(params[0].getClass(), 1);
}
NumberInterface factorial = params[0];
NumberInterface multiplier = params[0];
//It is necessary to later prevent calls of factorial on anything but non-negative integers.
while ((multiplier = multiplier.subtract(NaiveNumber.ONE.promoteTo(multiplier.getClass()))).signum() == 1) {
factorial = factorial.multiply(multiplier);
}
return factorial;
/*if(!storedList.containsKey(params[0].getClass())){
storedList.put(params[0].getClass(), new ArrayList<NumberInterface>());
storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass()));
storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass()));
}*/
}
});
/**
* The absolute value function, abs(-3) = 3
*/
public static final Function FUNCTION_ABS = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return params[0].multiply((new NaiveNumber(params[0].signum())).promoteTo(params[0].getClass()));
}
};
/**
* The natural log function.
*/
public static final Function FUNCTION_LN = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1 && params[0].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) > 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface param = params[0];
int powersOf2 = 0;
while (FUNCTION_ABS.apply(param.subtract(NaiveNumber.ONE.promoteTo(param.getClass()))).compareTo(new NaiveNumber(0.1).promoteTo(param.getClass())) >= 0) {
if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() == 1) {
param = param.divide(fromInt(param.getClass(), 2));
powersOf2++;
if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() != 1) {
break;
//No infinite loop for you.
}
} else {
param = param.multiply(fromInt(param.getClass(), 2));
powersOf2--;
if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() != -1) {
break;
//No infinite loop for you.
}
}
}
return getLog2(param).multiply((new NaiveNumber(powersOf2)).promoteTo(param.getClass())).add(getLogPartialSum(param));
}
/**
* Returns the partial sum of the Taylor series for logx (around x=1).
* Automatically determines the number of terms needed based on the precision of x.
* @param x value at which the series is evaluated. 0 < x < 2. (x=2 is convergent but impractical.)
* @return the partial sum.
*/
private NumberInterface getLogPartialSum(NumberInterface x) {
NumberInterface maxError = getMaxError(x);
x = x.subtract(NaiveNumber.ONE.promoteTo(x.getClass())); //Terms used are for log(x+1).
NumberInterface currentNumerator = x, currentTerm = x, sum = x;
int n = 1;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) {
n++;
currentNumerator = currentNumerator.multiply(x).negate();
currentTerm = currentNumerator.divide(new NaiveNumber(n).promoteTo(x.getClass()));
sum = sum.add(currentTerm);
}
return sum;
}
/**
* Returns natural log of 2 to the required precision of the class of number.
* @param number a number of the same type as the return type. (Used for precision.)
* @return the value of log(2) with the appropriate precision.
*/
private NumberInterface getLog2(NumberInterface number) {
NumberInterface maxError = getMaxError(number);
//NumberInterface errorBound = fromInt(number.getClass(), 1);
//We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n)
//In the following, a=1/3^n, b=1/4^n, c = 1/n.
//a is also an error bound.
NumberInterface a = fromInt(number.getClass(), 1), b = a, c = a;
NumberInterface sum = NaiveNumber.ZERO.promoteTo(number.getClass());
int n = 0;
while (a.compareTo(maxError) >= 1) {
n++;
a = a.divide(fromInt(number.getClass(), 3));
b = b.divide(fromInt(number.getClass(), 4));
c = NaiveNumber.ONE.promoteTo(number.getClass()).divide((new NaiveNumber(n)).promoteTo(number.getClass()));
sum = sum.add(a.add(b).multiply(c));
}
return sum;
}
};
/**
* The square root function.
*/
public static final Function FUNCTION_SQRT = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return OP_CARET.getFunction().apply(params[0], ((new NaiveNumber(0.5)).promoteTo(params[0].getClass())));
}
};
/**
* The implementation for double-based naive numbers.
*/
public static final NumberImplementation IMPLEMENTATION_NAIVE = new NumberImplementation(NaiveNumber.class, 0) {
@Override
public NumberInterface instanceForString(String string) {
return new NaiveNumber(string);
}
@Override
public NumberInterface instanceForPi() {
return new NaiveNumber(Math.PI);
}
};
/**
* The implementation for the infinite-precision BigDecimal.
*/
public static final NumberImplementation IMPLEMENTATION_PRECISE = new NumberImplementation(PreciseNumber.class, 0) {
@Override
public NumberInterface instanceForString(String string) {
return new PreciseNumber(string);
}
@Override
public NumberInterface instanceForPi() {
NumberInterface C = FUNCTION_SQRT.apply(new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
NumberInterface M = PreciseNumber.ONE;
NumberInterface L = new PreciseNumber("13591409");
NumberInterface X = M;
NumberInterface sum = L;
int termsNeeded = C.getMaxPrecision() / 13 + 1;
NumberInterface lSummand = new PreciseNumber("545140134");
NumberInterface xMultiplier = new PreciseNumber("262537412")
.multiply(new PreciseNumber("1000000000"))
.add(new PreciseNumber("640768000"))
.negate();
for (int i = 0; i < termsNeeded; i++) {
M = M
.multiply(new NaiveNumber(12 * i + 2).promoteTo(PreciseNumber.class))
.multiply(new NaiveNumber(12 * i + 6).promoteTo(PreciseNumber.class))
.multiply(new NaiveNumber(12 * i + 10).promoteTo(PreciseNumber.class))
.divide(new NaiveNumber(Math.pow(i + 1, 3)).promoteTo(PreciseNumber.class));
L = L.add(lSummand);
X = X.multiply(xMultiplier);
sum = sum.add(M.multiply(L).divide(X));
}
return C.divide(sum);
}
};
private static final HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
/**
* The exponential function, exp(1) = e^1 = 2.71...
*/
public static final Function FUNCTION_EXP = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface maxError = getMaxError(params[0]);
int n = 0;
if (params[0].signum() <= 0) {
NumberInterface currentTerm = NaiveNumber.ONE.promoteTo(params[0].getClass()), sum = currentTerm;
while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) {
n++;
currentTerm = currentTerm.multiply(params[0]).divide((new NaiveNumber(n)).promoteTo(params[0].getClass()));
sum = sum.add(currentTerm);
}
return sum;
} else {
//We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!.
//right and left refer to lhs and rhs in the above inequality.
NumberInterface sum = NaiveNumber.ONE.promoteTo(params[0].getClass());
NumberInterface nextNumerator = params[0];
NumberInterface left = params[0].multiply(fromInt(params[0].getClass(), 3).intPow(params[0].ceiling().intValue())), right = maxError;
do {
sum = sum.add(nextNumerator.divide(factorial(params[0].getClass(), n + 1)));
n++;
nextNumerator = nextNumerator.multiply(params[0]);
left = left.multiply(params[0]);
NumberInterface nextN = (new NaiveNumber(n + 1)).promoteTo(params[0].getClass());
right = right.multiply(nextN);
//System.out.println(left + ", " + right);
}
while (left.compareTo(right) > 0);
//System.out.println(n+1);
return sum;
}
}
};
/**
* The caret / pow operator, ^
*/
public static final Operator OP_CARET = new Operator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2
&& !(params[0].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0
&& params[1].compareTo(NaiveNumber.ZERO.promoteTo(params[1].getClass())) == 0)
&& !(params[0].signum() == -1 && params[1].fractionalPart().compareTo(NaiveNumber.ZERO.promoteTo(params[1].getClass())) != 0);
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
if (params[0].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0)
return NaiveNumber.ZERO.promoteTo(params[0].getClass());
else if (params[1].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0)
return NaiveNumber.ONE.promoteTo(params[1].getClass());
//Detect integer bases:
if(params[0].fractionalPart().compareTo(fromInt(params[0].getClass(), 0)) == 0
&& FUNCTION_ABS.apply(params[0]).compareTo(fromInt(params[0].getClass(), Integer.MAX_VALUE)) < 0
&& FUNCTION_ABS.apply(params[1]).compareTo(fromInt(params[1].getClass(), 1)) >= 0){
NumberInterface[] newParams = {params[0], params[1].fractionalPart()};
return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(newParams));
}
return FUNCTION_EXP.apply(FUNCTION_LN.apply(FUNCTION_ABS.apply(params[0])).multiply(params[1]));
}
});
/**
* The sine function (the argument is interpreted in radians).
*/
public final Function functionSin = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface pi = piFor(params[0].getClass());
NumberInterface twoPi = pi.multiply(fromInt(pi.getClass(), 2));
NumberInterface theta = getSmallAngle(params[0], pi);
//System.out.println(theta);
if (theta.compareTo(pi.multiply(new NaiveNumber(1.5).promoteTo(twoPi.getClass()))) >= 0) {
theta = theta.subtract(twoPi);
} else if (theta.compareTo(pi.divide(fromInt(pi.getClass(), 2))) > 0) {
theta = pi.subtract(theta);
}
//System.out.println(theta);
return sinTaylor(theta);
}
};
/**
* The cosine function (the argument is in radians).
*/
public final Function functionCos = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(piFor(params[0].getClass()).divide(fromInt(params[0].getClass(), 2))
.subtract(params[0]));
}
};
/**
* The tangent function (the argument is in radians).
*/
public final Function functionTan = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(params[0]).divide(functionCos.apply(params[0]));
}
};
/**
* The secant function (the argument is in radians).
*/
public final Function functionSec = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return NaiveNumber.ONE.promoteTo(params[0].getClass()).divide(functionCos.apply(params[0]));
}
};
/**
* The cosecant function (the argument is in radians).
*/
public final Function functionCsc = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return NaiveNumber.ONE.promoteTo(params[0].getClass()).divide(functionSin.apply(params[0]));
}
};
/**
* The cotangent function (the argument is in radians).
*/
public final Function functionCot = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionCos.apply(params[0]).divide(functionSin.apply(params[0]));
}
};
public StandardPlugin(PluginManager manager) {
super(manager);
}
/**
* Returns a partial sum of a series whose terms are given by the nthTermFunction, evaluated at x.
*
* @param x the value at which the series is evaluated.
* @param nthTermFunction the function that returns the nth term of the series, in the format term(x, n).
* @param n the number of terms in the partial sum.
* @return the value of the partial sum that has the same class as x.
*/
private static NumberInterface sumSeries(NumberInterface x, BiFunction<Integer, NumberInterface, NumberInterface> nthTermFunction, int n) {
NumberInterface sum = NaiveNumber.ZERO.promoteTo(x.getClass());
for (int i = 0; i <= n; i++) {
sum = sum.add(nthTermFunction.apply(i, x));
}
return sum;
}
/**
* Returns the maximum error based on the precision of the class of number.
*
* @param number Any instance of the NumberInterface in question (should return an appropriate precision).
* @return the maximum error.
*/
private static NumberInterface getMaxError(NumberInterface number) {
return fromInt(number.getClass(), 10).intPow(-number.getMaxPrecision());
}
/**
* A factorial function that uses memoization for each number class; it efficiently
* computes factorials of non-negative integers.
*
* @param numberClass type of number to return.
* @param n non-negative integer.
* @return a number of numClass with value n factorial.
*/
public static NumberInterface factorial(Class<? extends NumberInterface> numberClass, int n) {
if (!FACTORIAL_LISTS.containsKey(numberClass)) {
FACTORIAL_LISTS.put(numberClass, new ArrayList<>());
FACTORIAL_LISTS.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass));
FACTORIAL_LISTS.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass));
}
ArrayList<NumberInterface> list = FACTORIAL_LISTS.get(numberClass);
if (n >= list.size()) {
while (list.size() < n + 16) {
list.add(list.get(list.size() - 1).multiply(new NaiveNumber(list.size()).promoteTo(numberClass)));
}
}
return list.get(n);
}
/**
* Returns the value of the Taylor series for sin (centered at 0) at x.
*
* @param x where the series is evaluated.
* @return the value of the series
*/
private static NumberInterface sinTaylor(NumberInterface x) {
NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm = x, sum = x;
NumberInterface maxError = getMaxError(x);
int n = 1;
do {
n += 2;
power = power.multiply(multiplier);
currentTerm = power.divide(factorial(x.getClass(), n));
sum = sum.add(currentTerm);
} while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0);
return sum;
}
/**
* Returns an equivalent angle in the interval [0, 2pi)
*
* @param phi an angle (in radians).
* @return theta in [0, 2pi) that differs from phi by a multiple of 2pi.
*/
private static NumberInterface getSmallAngle(NumberInterface phi, NumberInterface pi) {
NumberInterface twoPi = pi.multiply(new NaiveNumber("2").promoteTo(phi.getClass()));
NumberInterface theta = FUNCTION_ABS.apply(phi).subtract(twoPi
.multiply(FUNCTION_ABS.apply(phi).divide(twoPi).floor())); //Now theta is in [0, 2pi).
if (phi.signum() < 0) {
theta = twoPi.subtract(theta);
}
return theta;
}
/**
* Returns a number of class numType with value n.
* @param numType class of number to return.
* @param n value of returned number.
* @return numClass instance with value n.
*/
private static NumberInterface fromInt(Class<? extends NumberInterface> numType, int n){
if(!integerValues.containsKey(numType)){
integerValues.put(numType, new HashMap<>());
}
if(!integerValues.get(numType).containsKey(n)){
integerValues.get(numType).put(n, new NaiveNumber(n).promoteTo(numType));
}
return integerValues.get(numType).get(n);
}
@Override
public void onEnable() {
registerNumberImplementation("naive", IMPLEMENTATION_NAIVE);
registerNumberImplementation("precise", IMPLEMENTATION_PRECISE);
registerOperator("+", OP_ADD);
registerOperator("-", OP_SUBTRACT);
registerOperator("`", OP_NEGATE);
registerOperator("*", OP_MULTIPLY);
registerOperator("/", OP_DIVIDE);
registerOperator("^", OP_CARET);
registerOperator("!", OP_FACTORIAL);
registerFunction("abs", FUNCTION_ABS);
registerFunction("exp", FUNCTION_EXP);
registerFunction("ln", FUNCTION_LN);
registerFunction("sqrt", FUNCTION_SQRT);
registerFunction("sin", functionSin);
registerFunction("cos", functionCos);
registerFunction("tan", functionTan);
registerFunction("sec", functionSec);
registerFunction("csc", functionCsc);
registerFunction("cot", functionCot);
}
@Override
public void onDisable() {
}
}

View File

@@ -1,54 +0,0 @@
package org.nwapw.abacus.tree;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.number.NumberInterface;
/**
* A reducer implementation that turns a tree into a single number.
* This is not always guaranteed to work.
*/
public class NumberReducer implements Reducer<NumberInterface> {
/**
* The plugin manager from which to draw the functions.
*/
private Abacus abacus;
/**
* Creates a new number reducer.
*
* @param abacus the calculator instance.
*/
public NumberReducer(Abacus abacus) {
this.abacus = abacus;
}
@Override
public NumberInterface reduceNode(TreeNode node, Object... children) {
if (node instanceof NumberNode) {
return ((NumberNode) node).getNumber();
} else if (node instanceof BinaryNode) {
NumberInterface left = (NumberInterface) children[0];
NumberInterface right = (NumberInterface) children[1];
Function function = abacus.getPluginManager().operatorFor(((BinaryNode) node).getOperation()).getFunction();
if (function == null) return null;
return function.apply(left, right);
} else if (node instanceof UnaryNode) {
NumberInterface child = (NumberInterface) children[0];
Function functionn = abacus.getPluginManager().operatorFor(((UnaryNode) node).getOperation()).getFunction();
if (functionn == null) return null;
return functionn.apply(child);
} else if (node instanceof FunctionNode) {
NumberInterface[] convertedChildren = new NumberInterface[children.length];
for (int i = 0; i < convertedChildren.length; i++) {
convertedChildren[i] = (NumberInterface) children[i];
}
Function function = abacus.getPluginManager().functionFor(((FunctionNode) node).getFunction());
if (function == null) return null;
return function.apply(convertedChildren);
}
return null;
}
}

View File

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

View File

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

View File

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

View File

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