镜像来自
https://github.com/DanilaFe/abacus
synced 2026-01-25 16:15:19 +00:00
Compare commits
268 次程式碼提交
lex
...
from-int-f
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
7d5efa1fe6 | ||
|
|
6a15c266c4 | ||
| 9f61fc5dbe | |||
| bae6ee5526 | |||
| 4f94700aef | |||
| b7152da58d | |||
| d17a8a9fa7 | |||
| 71c9f0d141 | |||
| fb02984e60 | |||
| a9ac4681f0 | |||
| 62d7053441 | |||
| f3cbb600ac | |||
| abc0e2d59f | |||
| f7d1be086b | |||
| 21a925d6d2 | |||
| 0d21898f20 | |||
| 3e39087fde | |||
| a984f2960d | |||
| a6832e09f4 | |||
| 0bcb3b25d9 | |||
| 2f5f967be4 | |||
| 72a2a8f1c1 | |||
| 58fc94e9d0 | |||
| 9cedb100ad | |||
| 99be2d80f1 | |||
| 2523b9b04b | |||
| cd60c9d52f | |||
| 23a3eb88f1 | |||
| 508e98413d | |||
| d06f611a2e | |||
| c541eaab97 | |||
| 0058ec9c71 | |||
|
|
f8bf60f383 | ||
|
|
4369eba107 | ||
| 385a0c960d | |||
|
|
d7ae1a80f1 | ||
|
|
e4a45c0ec4 | ||
| 22cf99d23d | |||
| 39b36f84e0 | |||
| b036b6c242 | |||
| eb3410f854 | |||
| f967053e3d | |||
| 75824a2a77 | |||
| da602876e7 | |||
|
|
8df468e04a | ||
|
|
e029ab1fea | ||
| af56d31723 | |||
|
|
eff7be0204 | ||
| 34ae4b42c6 | |||
| b680215f57 | |||
| e6cc08043e | |||
| 3e10ea223f | |||
| 44c52b412c | |||
| eb51d5d3e4 | |||
| 8ae28f2dab | |||
| 0bade4a7df | |||
|
|
9d5f9d901c | ||
| 0f02867a4e | |||
|
|
dad546c5b5 | ||
| f0e1b85dcf | |||
| 37261c2f58 | |||
| 691118c206 | |||
|
|
95845a1585 | ||
| 819fff6391 | |||
|
|
8cf0c94947 | ||
| 20f6e0b0b2 | |||
| 4056013d1f | |||
| c7b5d4c4fc | |||
| be28e26607 | |||
| 2f1ed5f0d1 | |||
| 2615273d28 | |||
| 6e1d2ce629 | |||
| 44b8efd9bc | |||
| 2502c90837 | |||
| e49f28a850 | |||
| 88e4a87d81 | |||
| cda09518c3 | |||
| 56510d97de | |||
|
|
86533d53c9 | ||
|
|
9b71f9aaf4 | ||
|
|
cf953da40a | ||
|
|
27ad10c0f1 | ||
|
|
601c4fea55 | ||
| c2ae0b4138 | |||
| 16938b4e06 | |||
| d964fbfb6f | |||
|
|
52fbfd5134 | ||
| 9713f24ed2 | |||
| 5de9453bec | |||
|
|
b31151384d | ||
| d205651332 | |||
| 6f99f07150 | |||
| 2cf41c1029 | |||
| 76677ef494 | |||
| 0cd40b028a | |||
| 7cb04a1222 | |||
| 0a97eeb442 | |||
|
|
1ee8c7d231 | ||
|
|
f97d16c640 | ||
|
|
a0bba03c2c | ||
| 05d0755526 | |||
| 0b97a935bf | |||
| 211e963db0 | |||
| d8145acc8f | |||
| 63b8162a9b | |||
| c655c63233 | |||
| 27ff1a47b5 | |||
| 2941252f7d | |||
| 44ed0199d4 | |||
| 5b582a7dbe | |||
| a02086e791 | |||
|
|
8666e96420 | ||
|
|
fd40e6b297 | ||
|
|
79ccd61af3 | ||
|
|
699ba9e193 | ||
|
|
e43f223086 | ||
| 2dbc91f79e | |||
|
|
782636a982 | ||
| 97d63489cc | |||
| a0a4f1fbfe | |||
| 763683b6b4 | |||
| 3ce74303ed | |||
| 122874b97a | |||
| 0125980c5a | |||
| cb7d0f309b | |||
| cb98601ae5 | |||
| 67b95edd44 | |||
| 960f891393 | |||
| b599bef775 | |||
| 21d88fe256 | |||
| 3d61ead0f6 | |||
| 28004ed98d | |||
| 317cc552e6 | |||
| 43c11f8454 | |||
| 3131d96d07 | |||
| 542f4b26ab | |||
| d449e58888 | |||
| 085569900b | |||
| 7b2ee1c87a | |||
| 274826cc09 | |||
| bfee4ec322 | |||
| bd1f7b8786 | |||
| 90c6625108 | |||
| a99b6b647f | |||
| d12d53032b | |||
| ff31dd6e47 | |||
| 9454620489 | |||
| 1160768ee5 | |||
| 1ce9fc6b1c | |||
| acf3d85584 | |||
| 6c80d8fe93 | |||
| c230675855 | |||
| bd44307f2b | |||
| a949a27da4 | |||
| 5f2f2c8589 | |||
| 7b74b734a3 | |||
| 352c578d15 | |||
| 5c301e4afa | |||
| 8c5306051e | |||
| c3bb3d7d3f | |||
| 556a72f946 | |||
| f303093a3f | |||
| 8dae4a880e | |||
| 243dc81deb | |||
| 0c07695991 | |||
| ff689f9bd5 | |||
|
|
c184b55738 | ||
| dc410917b3 | |||
| 9850f896bb | |||
| 0b3648d4f3 | |||
|
|
69e3b55643 | ||
| 2ba6e22fcb | |||
| 5228773b5e | |||
| 42393ca6a6 | |||
| b20ddc2013 | |||
| a881640bf6 | |||
| 0263086e10 | |||
| c9fad36d16 | |||
| 2cc4bd14ce | |||
| f119f19c04 | |||
| 65772c8d57 | |||
| bbbb2e855e | |||
| 8a29019852 | |||
| 0d7a416446 | |||
| 167e13cfe1 | |||
| b0ae3f90fc | |||
| a7c2084254 | |||
|
|
bf6f48bf82 | ||
| f7da896fc0 | |||
| 6813643b15 | |||
| e6cb755ec9 | |||
| 2ca23fd427 | |||
| efbd6a4c20 | |||
| a211884499 | |||
| f2c280766d | |||
|
|
088a45cf4c | ||
|
|
557bc66e53 | ||
| e6559015b3 | |||
| f931b9f322 | |||
|
|
9666ef9019 | ||
|
|
ba30227b28 | ||
| 78e2d50f89 | |||
| 07dd9d0a1a | |||
| ee1de6dc17 | |||
| 077a34c618 | |||
| 79e85832ce | |||
|
|
ea5a7a9558 | ||
|
|
3e52a9d645 | ||
|
|
7a0fa31cad | ||
|
|
aec37b6720 | ||
| 189f8c6e15 | |||
| e8595510b8 | |||
| b09c9c3cb2 | |||
| b0a7c90aa1 | |||
| cf95ed7dc0 | |||
| bc72b4da8a | |||
| 15d7dbd30e | |||
| c8146954c3 | |||
| d18e27bdb4 | |||
| c4eb70999b | |||
| 4a8164631f | |||
| d7caf1cdc7 | |||
| 8754871556 | |||
| b31c1f9624 | |||
| 2b7a68e179 | |||
| e06feaa581 | |||
| 626a2cb514 | |||
|
|
0002f14e61 | ||
|
|
f35f83a92f | ||
|
|
8d9dac1a75 | ||
|
|
aae7e678dd | ||
| 8ada6c32ff | |||
| a446034a92 | |||
| 78033e93b0 | |||
|
|
6038663b3a | ||
| 2074c11095 | |||
| 95bae3befb | |||
| e41c25847b | |||
| 50baf80433 | |||
| f7d4d01bc8 | |||
| ec030607bf | |||
| e816b86a3e | |||
|
|
356084ef61 | ||
| 798ee6f7c3 | |||
| ac153521d4 | |||
| 08999350f4 | |||
|
|
1b9dc5514e | ||
| c19ae3b071 | |||
| ade4eb1035 | |||
| 31b6adecd9 | |||
| 08a462b8f3 | |||
| 989ac80bf4 | |||
| 3cf4f958b0 | |||
| 7e7525cf37 | |||
| b93346ec37 | |||
| cc5d487386 | |||
|
|
27cf6ce64b | ||
|
|
5f60110385 | ||
| 38255b1219 | |||
|
|
1112dafadf | ||
|
|
54340ada63 | ||
|
|
21cd9fd052 | ||
|
|
afcddafd81 | ||
|
|
dbf7d587ed | ||
|
|
07acfefd0b | ||
|
|
e99afa0507 | ||
|
|
67f8c648db | ||
|
|
254276b2af |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -20,3 +20,12 @@
|
|||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
hs_err_pid*
|
hs_err_pid*
|
||||||
|
|
||||||
|
# Custom Stuff
|
||||||
|
# Gradle
|
||||||
|
.gradle/*
|
||||||
|
build/*
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
.idea/*
|
||||||
|
abacus.iml
|
||||||
|
|||||||
1
.travis.yml
Normal file
1
.travis.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
language: java
|
||||||
24
README.md
24
README.md
@@ -1,2 +1,26 @@
|
|||||||
# abacus
|
# abacus
|
||||||
|
[](https://travis-ci.org/DanilaFe/abacus)
|
||||||
|
|
||||||
Summer project for NWAPW.
|
Summer project for NWAPW.
|
||||||
|
Created by Arthur Drobot, Danila Fedorin and Riley Jones.
|
||||||
|
|
||||||
|
## Project Description
|
||||||
|
Abacus is a calculator built with extensibility and usability in mind. It provides a plugin interface, via Java, as Lua proves too difficult to link up to the Java core. The description of the internals of the project can be found on the wiki page.
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
Abacus is being built for the Northwest Advanced Programming Workshop, a 3 week program in which students work in teams to complete a single project, following principles of agile development. Because of its short timeframe, Abacus is not even close to completed state. Below is a list of the current features and problems.
|
||||||
|
- [x] Basic number class
|
||||||
|
- [x] Implementation of basic functions
|
||||||
|
- [x] Implementation of `exp`, `ln`, `sqrt` using the basic functions and Taylor Series
|
||||||
|
- [x] Plugin loading from JAR files
|
||||||
|
- [x] Regular expression pattern construction and matching
|
||||||
|
- [x] Infix and postfix operators
|
||||||
|
- [ ] __Correct__ handling of postfix operators (`12+!3` parses to `12!+3`, which is wrong)
|
||||||
|
- [ ] User-defined precision
|
||||||
|
|
||||||
|
## Project Proposal
|
||||||
|
>There is currently no calculator that is up to par with a sophisticated programmer's needs. The standard system ones are awful, not respecting the order of operations and having only a few basic functions programmed into them. The web ones are tied to the Internet and don't function offline. Physical ones like the TI-84 come close in terms of functionality, but they make the user have to switch between the computer and the device.
|
||||||
|
>
|
||||||
|
>My proposal is a more ergonomic calculator for advanced users. Of course, for a calculator, being able to do the actual math is a requirement. However, in this project I also would like to include other features that would make it much more pleasant to use. The first of these features is a wide collection of built in functions, designed with usefulness and consistency in mind. The second is scripting capabilities - most simply using Lua and its provided library. By allowing the users to script in a standardized language that isn't TI-BASIC, the calculator could simplify a variety of tasks and not have to clutter up the default provided functions with overly specific things. Lastly, it's important for the calculator to have a good design that doesn't get in the way of its use, on the two major desktop platforms (macOS and Windows).
|
||||||
|
>
|
||||||
|
>With these features I believe that this is a calculator that I would use (and frequently find myself wanting to use). It also seems to have a diverse array of tasks, such as UI design, implementing the math functions to be fast and optimized (fast inverse square root, anyone?), parsing code, and working with Lua integration.
|
||||||
|
|||||||
@@ -8,3 +8,5 @@
|
|||||||
* Rembulan(5.3) - https://github.com/mjanicek/rembulan
|
* Rembulan(5.3) - https://github.com/mjanicek/rembulan
|
||||||
* LuaJava - https://github.com/jasonsantos/luajava
|
* LuaJava - https://github.com/jasonsantos/luajava
|
||||||
* jnlua - https://code.google.com/archive/p/jnlua/
|
* jnlua - https://code.google.com/archive/p/jnlua/
|
||||||
|
## Gist
|
||||||
|
* https://gist.github.com/rileyJones/1c18338821b88e92a477bfa270344db3
|
||||||
|
|||||||
15
build.gradle
Normal file
15
build.gradle
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'application'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile 'com.moandjiezana.toml:toml4j:0.7.1'
|
||||||
|
testCompile 'junit:junit:4.12'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the main class for the application
|
||||||
|
mainClassName = 'org.nwapw.abacus.Abacus'
|
||||||
|
|
||||||
二進制
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
二進制
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#Fri Jul 28 17:18:51 PDT 2017
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-bin.zip
|
||||||
172
gradlew
vendored
Executable file
172
gradlew
vendored
Executable file
@@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn ( ) {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die ( ) {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save ( ) {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
84
gradlew.bat
vendored
Normal file
84
gradlew.bat
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
1
settings.gradle
Normal file
1
settings.gradle
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rootProject.name = 'abacus'
|
||||||
140
src/main/java/org/nwapw/abacus/Abacus.java
Normal file
140
src/main/java/org/nwapw/abacus/Abacus.java
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
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;
|
||||||
|
import org.nwapw.abacus.parsing.TreeBuilder;
|
||||||
|
import org.nwapw.abacus.plugin.NumberImplementation;
|
||||||
|
import org.nwapw.abacus.plugin.PluginManager;
|
||||||
|
import org.nwapw.abacus.plugin.StandardPlugin;
|
||||||
|
import org.nwapw.abacus.tree.NumberReducer;
|
||||||
|
import org.nwapw.abacus.tree.TreeNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main calculator class. This is responsible
|
||||||
|
* for piecing together all of the components, allowing
|
||||||
|
* their interaction with each other.
|
||||||
|
*/
|
||||||
|
public class Abacus {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default number implementation to be used if no other one is available / selected.
|
||||||
|
*/
|
||||||
|
public static final NumberImplementation DEFAULT_IMPLEMENTATION = StandardPlugin.IMPLEMENTATION_NAIVE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The plugin manager responsible for
|
||||||
|
* loading and unloading plugins,
|
||||||
|
* and getting functions from them.
|
||||||
|
*/
|
||||||
|
private PluginManager pluginManager;
|
||||||
|
/**
|
||||||
|
* The reducer used to evaluate the tree.
|
||||||
|
*/
|
||||||
|
private NumberReducer numberReducer;
|
||||||
|
/**
|
||||||
|
* The configuration loaded from a file.
|
||||||
|
*/
|
||||||
|
private Configuration configuration;
|
||||||
|
/**
|
||||||
|
* The tree builder used to construct a tree
|
||||||
|
* from a string.
|
||||||
|
*/
|
||||||
|
private TreeBuilder treeBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of the Abacus calculator.
|
||||||
|
*
|
||||||
|
* @param configuration the configuration object for this Abacus instance.
|
||||||
|
*/
|
||||||
|
public Abacus(Configuration configuration) {
|
||||||
|
pluginManager = new PluginManager(this);
|
||||||
|
numberReducer = new NumberReducer(this);
|
||||||
|
this.configuration = new Configuration(configuration);
|
||||||
|
LexerTokenizer lexerTokenizer = new LexerTokenizer();
|
||||||
|
ShuntingYardParser shuntingYardParser = new ShuntingYardParser(this);
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @return the main tree builder in this abacus instance.
|
||||||
|
*/
|
||||||
|
public TreeBuilder getTreeBuilder() {
|
||||||
|
return treeBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current plugin manager,
|
||||||
|
*
|
||||||
|
* @return the plugin manager in this abacus instance.
|
||||||
|
*/
|
||||||
|
public PluginManager getPluginManager() {
|
||||||
|
return pluginManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the reducer that is responsible for transforming
|
||||||
|
* an expression into a number.
|
||||||
|
*
|
||||||
|
* @return the number reducer in this abacus instance.
|
||||||
|
*/
|
||||||
|
public NumberReducer getNumberReducer() {
|
||||||
|
return numberReducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the configuration object associated with this instance.
|
||||||
|
*
|
||||||
|
* @return the configuration object.
|
||||||
|
*/
|
||||||
|
public Configuration getConfiguration() {
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a string into a tree structure using the main
|
||||||
|
* tree builder.
|
||||||
|
*
|
||||||
|
* @param input the input string to parse
|
||||||
|
* @return the resulting tree, null if the tree builder or the produced tree are null.
|
||||||
|
*/
|
||||||
|
public TreeNode parseString(String input) {
|
||||||
|
return treeBuilder.fromString(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates the given tree using the main
|
||||||
|
* number reducer.
|
||||||
|
*
|
||||||
|
* @param tree the tree to reduce, must not be null.
|
||||||
|
* @return the resulting number, or null of the reduction failed.
|
||||||
|
*/
|
||||||
|
public NumberInterface evaluateTree(TreeNode tree) {
|
||||||
|
return tree.reduce(numberReducer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a number from a string.
|
||||||
|
*
|
||||||
|
* @param numberString the string to create the number from.
|
||||||
|
* @return the resulting number.
|
||||||
|
*/
|
||||||
|
public NumberInterface numberFromString(String numberString) {
|
||||||
|
NumberImplementation toInstantiate =
|
||||||
|
pluginManager.numberImplementationFor(configuration.getNumberImplementation());
|
||||||
|
if (toInstantiate == null) toInstantiate = DEFAULT_IMPLEMENTATION;
|
||||||
|
|
||||||
|
return toInstantiate.instanceForString(numberString);
|
||||||
|
}
|
||||||
|
}
|
||||||
133
src/main/java/org/nwapw/abacus/config/Configuration.java
Normal file
133
src/main/java/org/nwapw/abacus/config/Configuration.java
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package org.nwapw.abacus.config;
|
||||||
|
|
||||||
|
import com.moandjiezana.toml.Toml;
|
||||||
|
import com.moandjiezana.toml.TomlWriter;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration object that stores
|
||||||
|
* options that the user can change.
|
||||||
|
*/
|
||||||
|
public class Configuration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The defaults TOML string.
|
||||||
|
*/
|
||||||
|
private static final String DEFAULT_CONFIG =
|
||||||
|
"numberImplementation = \"naive\"\n" +
|
||||||
|
"disabledPlugins = []";
|
||||||
|
/**
|
||||||
|
* The defaults TOML object, parsed from the string.
|
||||||
|
*/
|
||||||
|
private static final Toml DEFAULT_TOML = new Toml().read(DEFAULT_CONFIG);
|
||||||
|
/**
|
||||||
|
* The TOML writer used to write this configuration to a file.
|
||||||
|
*/
|
||||||
|
private static final TomlWriter TOML_WRITER = new TomlWriter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The implementation of the number that should be used.
|
||||||
|
*/
|
||||||
|
private String numberImplementation = "<default>";
|
||||||
|
/**
|
||||||
|
* The list of disabled plugins in this Configuration.
|
||||||
|
*/
|
||||||
|
private Set<String> disabledPlugins = new HashSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new configuration form the given configuration.
|
||||||
|
*
|
||||||
|
* @param copyFrom the configuration to copy.
|
||||||
|
*/
|
||||||
|
public Configuration(Configuration copyFrom){
|
||||||
|
copyFrom(copyFrom);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new configuration with the given values.
|
||||||
|
*
|
||||||
|
* @param numberImplementation the number implementation, like "naive" or "precise"
|
||||||
|
* @param disabledPlugins the list of disabled plugins.
|
||||||
|
*/
|
||||||
|
public Configuration(String numberImplementation, String[] disabledPlugins) {
|
||||||
|
this.numberImplementation = numberImplementation;
|
||||||
|
this.disabledPlugins.addAll(Arrays.asList(disabledPlugins));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a configuration from a given file, keeping non-specified fields default.
|
||||||
|
*
|
||||||
|
* @param fromFile the file to load from.
|
||||||
|
*/
|
||||||
|
public Configuration(File fromFile) {
|
||||||
|
if (!fromFile.exists()) return;
|
||||||
|
copyFrom(new Toml(DEFAULT_TOML).read(fromFile).to(Configuration.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the values from the given configuration into this one.
|
||||||
|
*
|
||||||
|
* @param otherConfiguration the configuration to copy from.
|
||||||
|
*/
|
||||||
|
public void copyFrom(Configuration otherConfiguration) {
|
||||||
|
this.numberImplementation = otherConfiguration.numberImplementation;
|
||||||
|
this.disabledPlugins.addAll(otherConfiguration.disabledPlugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves this configuration to the given file, creating
|
||||||
|
* any directories that do not exist.
|
||||||
|
*
|
||||||
|
* @param file the file to save to.
|
||||||
|
*/
|
||||||
|
public void saveTo(File file) {
|
||||||
|
if (file.getParentFile() != null) file.getParentFile().mkdirs();
|
||||||
|
try {
|
||||||
|
TOML_WRITER.write(this, file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of this configuration as a string.
|
||||||
|
*
|
||||||
|
* @return the string that represents this configuration.
|
||||||
|
*/
|
||||||
|
public String asTomlString(){
|
||||||
|
return TOML_WRITER.write(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number implementation from this configuration.
|
||||||
|
*
|
||||||
|
* @return the number implementation.
|
||||||
|
*/
|
||||||
|
public String getNumberImplementation() {
|
||||||
|
return numberImplementation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the number implementation for the configuration
|
||||||
|
*
|
||||||
|
* @param numberImplementation the number implementation.
|
||||||
|
*/
|
||||||
|
public void setNumberImplementation(String numberImplementation) {
|
||||||
|
this.numberImplementation = numberImplementation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of disabled plugins.
|
||||||
|
*
|
||||||
|
* @return the list of disabled plugins.
|
||||||
|
*/
|
||||||
|
public Set<String> getDisabledPlugins() {
|
||||||
|
return disabledPlugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
39
src/main/java/org/nwapw/abacus/function/Function.java
Executable file
39
src/main/java/org/nwapw/abacus/function/Function.java
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
76
src/main/java/org/nwapw/abacus/function/Operator.java
Normal file
76
src/main/java/org/nwapw/abacus/function/Operator.java
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package org.nwapw.abacus.function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that represents a single infix operator.
|
||||||
|
*/
|
||||||
|
public class Operator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The associativity of the operator.
|
||||||
|
*/
|
||||||
|
private OperatorAssociativity associativity;
|
||||||
|
/**
|
||||||
|
* The type of this operator.
|
||||||
|
*/
|
||||||
|
private OperatorType type;
|
||||||
|
/**
|
||||||
|
* The precedence of the operator.
|
||||||
|
*/
|
||||||
|
private int precedence;
|
||||||
|
/**
|
||||||
|
* The function that is called by this operator.
|
||||||
|
*/
|
||||||
|
private Function function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new operator with the given parameters.
|
||||||
|
*
|
||||||
|
* @param associativity the associativity of the operator.
|
||||||
|
* @param operatorType the type of this operator, like binary infix or unary postfix.
|
||||||
|
* @param precedence the precedence of the operator.
|
||||||
|
* @param function the function that the operator calls.
|
||||||
|
*/
|
||||||
|
public Operator(OperatorAssociativity associativity, OperatorType operatorType, int precedence, Function function) {
|
||||||
|
this.associativity = associativity;
|
||||||
|
this.type = operatorType;
|
||||||
|
this.precedence = precedence;
|
||||||
|
this.function = function;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the operator's associativity.
|
||||||
|
*
|
||||||
|
* @return the associativity.
|
||||||
|
*/
|
||||||
|
public OperatorAssociativity getAssociativity() {
|
||||||
|
return associativity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the operator's type.
|
||||||
|
*
|
||||||
|
* @return the type.
|
||||||
|
*/
|
||||||
|
public OperatorType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the operator's precedence.
|
||||||
|
*
|
||||||
|
* @return the precedence.
|
||||||
|
*/
|
||||||
|
public int getPrecedence() {
|
||||||
|
return precedence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the operator's function.
|
||||||
|
*
|
||||||
|
* @return the function.
|
||||||
|
*/
|
||||||
|
public Function getFunction() {
|
||||||
|
return function;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.nwapw.abacus.function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum to represent the associativity of an operator.
|
||||||
|
*/
|
||||||
|
public enum OperatorAssociativity {
|
||||||
|
LEFT, RIGHT
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.nwapw.abacus.function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of an operator, describing how it should behave.
|
||||||
|
*/
|
||||||
|
public enum OperatorType {
|
||||||
|
BINARY_INFIX, UNARY_POSTFIX, UNARY_PREFIX
|
||||||
|
}
|
||||||
24
src/main/java/org/nwapw/abacus/fx/AbacusApplication.java
Normal file
24
src/main/java/org/nwapw/abacus/fx/AbacusApplication.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package org.nwapw.abacus.fx;
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main application class for JavaFX responsible for loading
|
||||||
|
* and displaying the fxml file.
|
||||||
|
*/
|
||||||
|
public class AbacusApplication extends Application {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Stage primaryStage) throws Exception {
|
||||||
|
Parent parent = FXMLLoader.load(getClass().getResource("/abacus.fxml"));
|
||||||
|
Scene mainScene = new Scene(parent, 320, 480);
|
||||||
|
primaryStage.setScene(mainScene);
|
||||||
|
primaryStage.setTitle("Abacus");
|
||||||
|
primaryStage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
299
src/main/java/org/nwapw/abacus/fx/AbacusController.java
Normal file
299
src/main/java/org/nwapw/abacus/fx/AbacusController.java
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
package org.nwapw.abacus.fx;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.control.cell.CheckBoxListCell;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
import org.nwapw.abacus.Abacus;
|
||||||
|
import org.nwapw.abacus.config.Configuration;
|
||||||
|
import org.nwapw.abacus.number.ComputationInterruptedException;
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
import org.nwapw.abacus.plugin.ClassFinder;
|
||||||
|
import org.nwapw.abacus.plugin.PluginListener;
|
||||||
|
import org.nwapw.abacus.plugin.PluginManager;
|
||||||
|
import org.nwapw.abacus.plugin.StandardPlugin;
|
||||||
|
import org.nwapw.abacus.tree.TreeNode;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The controller for the abacus FX UI, responsible
|
||||||
|
* for all the user interaction.
|
||||||
|
*/
|
||||||
|
public class AbacusController implements PluginListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file used for saving and loading configuration.
|
||||||
|
*/
|
||||||
|
public static final File CONFIG_FILE = new File("config.toml");
|
||||||
|
/**
|
||||||
|
* The title for the apply alert dialog.
|
||||||
|
*/
|
||||||
|
private static final String APPLY_MSG_TITLE = "\"Apply\" Needed";
|
||||||
|
/**
|
||||||
|
* The text for the header of the apply alert dialog.
|
||||||
|
*/
|
||||||
|
private static final String APPLY_MSG_HEADER = "The settings have not been applied.";
|
||||||
|
/**
|
||||||
|
* The text for the dialog that is shown if settings haven't been applied.
|
||||||
|
*/
|
||||||
|
private static final String APPLY_MSG_TEXT = "You have made changes to the configuration, however, you haven't pressed \"Apply\". " +
|
||||||
|
"The changes to the configuration will not be present in the calculator until \"Apply\" is pressed.";
|
||||||
|
/**
|
||||||
|
* Constant string that is displayed if the text could not be lexed or parsed.
|
||||||
|
*/
|
||||||
|
private static final String ERR_SYNTAX = "Syntax Error";
|
||||||
|
/**
|
||||||
|
* Constant string that is displayed if the tree could not be reduced.
|
||||||
|
*/
|
||||||
|
private static final String ERR_EVAL = "Evaluation Error";
|
||||||
|
/**
|
||||||
|
* Constant string that is displayed if the calculations are stopped before they are done.
|
||||||
|
*/
|
||||||
|
private static final String ERR_STOP = "Stopped";
|
||||||
|
/**
|
||||||
|
* Constant string that is displayed if the calculations are interrupted by an exception.
|
||||||
|
*/
|
||||||
|
private static final String ERR_EXCEPTION = "Exception Thrown";
|
||||||
|
@FXML
|
||||||
|
private TabPane coreTabPane;
|
||||||
|
@FXML
|
||||||
|
private Tab calculateTab;
|
||||||
|
@FXML
|
||||||
|
private Tab settingsTab;
|
||||||
|
@FXML
|
||||||
|
private TableView<HistoryModel> historyTable;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<HistoryModel, String> inputColumn;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<HistoryModel, String> parsedColumn;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<HistoryModel, String> outputColumn;
|
||||||
|
@FXML
|
||||||
|
private Text outputText;
|
||||||
|
@FXML
|
||||||
|
private TextField inputField;
|
||||||
|
@FXML
|
||||||
|
private Button inputButton;
|
||||||
|
@FXML
|
||||||
|
private Button stopButton;
|
||||||
|
@FXML
|
||||||
|
private ComboBox<String> numberImplementationBox;
|
||||||
|
@FXML
|
||||||
|
private ListView<ToggleablePlugin> enabledPluginView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of history entries, created by the users.
|
||||||
|
*/
|
||||||
|
private ObservableList<HistoryModel> historyData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The abacus instance used for calculations and all
|
||||||
|
* other main processing code.
|
||||||
|
*/
|
||||||
|
private ObservableList<String> numberImplementationOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of plugin objects that can be toggled on and off,
|
||||||
|
* and, when reloaded, get added to the plugin manager's black list.
|
||||||
|
*/
|
||||||
|
private ObservableList<ToggleablePlugin> enabledPlugins;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 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 used to perform the calculation.
|
||||||
|
*/
|
||||||
|
private final Runnable CALCULATION_RUNNABLE = new Runnable() {
|
||||||
|
|
||||||
|
private String attemptCalculation(){
|
||||||
|
try {
|
||||||
|
TreeNode constructedTree = abacus.parseString(inputField.getText());
|
||||||
|
if (constructedTree == null) {
|
||||||
|
return ERR_SYNTAX;
|
||||||
|
}
|
||||||
|
NumberInterface evaluatedNumber = abacus.evaluateTree(constructedTree);
|
||||||
|
if (evaluatedNumber == null) {
|
||||||
|
return ERR_EVAL;
|
||||||
|
}
|
||||||
|
String resultingString = evaluatedNumber.toString();
|
||||||
|
historyData.add(new HistoryModel(inputField.getText(), constructedTree.toString(), resultingString));
|
||||||
|
return resultingString;
|
||||||
|
} catch (ComputationInterruptedException exception) {
|
||||||
|
return ERR_STOP;
|
||||||
|
} catch (RuntimeException exception){
|
||||||
|
exception.printStackTrace();
|
||||||
|
return ERR_EXCEPTION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
String calculation = attemptCalculation();
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
outputText.setText(calculation);
|
||||||
|
inputField.setText("");
|
||||||
|
inputButton.setDisable(false);
|
||||||
|
stopButton.setDisable(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The thread in which the computation runs.
|
||||||
|
*/
|
||||||
|
private Thread calculationThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alerts the user if the changes they made
|
||||||
|
* have not yet been applied.
|
||||||
|
*/
|
||||||
|
private void alertIfApplyNeeded(boolean ignorePrevious) {
|
||||||
|
if (changesMade && (!reloadAlertShown || ignorePrevious)) {
|
||||||
|
reloadAlertShown = true;
|
||||||
|
reloadAlert.showAndWait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
Callback<TableColumn<HistoryModel, String>, TableCell<HistoryModel, String>> cellFactory =
|
||||||
|
param -> new CopyableCell<>();
|
||||||
|
Callback<ListView<ToggleablePlugin>, ListCell<ToggleablePlugin>> pluginCellFactory =
|
||||||
|
param -> new CheckBoxListCell<>(ToggleablePlugin::enabledProperty, new StringConverter<ToggleablePlugin>() {
|
||||||
|
@Override
|
||||||
|
public String toString(ToggleablePlugin object) {
|
||||||
|
return object.getClassName().substring(object.getClassName().lastIndexOf('.') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ToggleablePlugin fromString(String string) {
|
||||||
|
return new ToggleablePlugin(true, string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
historyData = FXCollections.observableArrayList();
|
||||||
|
historyTable.setItems(historyData);
|
||||||
|
numberImplementationOptions = FXCollections.observableArrayList();
|
||||||
|
numberImplementationBox.setItems(numberImplementationOptions);
|
||||||
|
numberImplementationBox.getSelectionModel().selectedIndexProperty().addListener(e -> changesMade = true);
|
||||||
|
historyTable.getSelectionModel().setCellSelectionEnabled(true);
|
||||||
|
enabledPlugins = FXCollections.observableArrayList();
|
||||||
|
enabledPluginView.setItems(enabledPlugins);
|
||||||
|
enabledPluginView.setCellFactory(pluginCellFactory);
|
||||||
|
inputColumn.setCellFactory(cellFactory);
|
||||||
|
inputColumn.setCellValueFactory(cell -> cell.getValue().inputProperty());
|
||||||
|
parsedColumn.setCellFactory(cellFactory);
|
||||||
|
parsedColumn.setCellValueFactory(cell -> cell.getValue().parsedProperty());
|
||||||
|
outputColumn.setCellFactory(cellFactory);
|
||||||
|
outputColumn.setCellValueFactory(cell -> cell.getValue().outputProperty());
|
||||||
|
coreTabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (oldValue.equals(settingsTab)) alertIfApplyNeeded(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
changesMade = false;
|
||||||
|
reloadAlertShown = false;
|
||||||
|
|
||||||
|
reloadAlert = new Alert(Alert.AlertType.WARNING);
|
||||||
|
reloadAlert.setTitle(APPLY_MSG_TITLE);
|
||||||
|
reloadAlert.setHeaderText(APPLY_MSG_HEADER);
|
||||||
|
reloadAlert.setContentText(APPLY_MSG_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void performCalculation() {
|
||||||
|
inputButton.setDisable(true);
|
||||||
|
stopButton.setDisable(false);
|
||||||
|
calculationThread = new Thread(CALCULATION_RUNNABLE);
|
||||||
|
calculationThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void performStop(){
|
||||||
|
if(calculationThread != null)
|
||||||
|
calculationThread.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void performSaveAndReload() {
|
||||||
|
performSave();
|
||||||
|
performReload();
|
||||||
|
changesMade = false;
|
||||||
|
reloadAlertShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void performReload() {
|
||||||
|
alertIfApplyNeeded(true);
|
||||||
|
abacus.getPluginManager().reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void performSave() {
|
||||||
|
Configuration configuration = abacus.getConfiguration();
|
||||||
|
configuration.setNumberImplementation(numberImplementationBox.getSelectionModel().getSelectedItem());
|
||||||
|
Set<String> disabledPlugins = configuration.getDisabledPlugins();
|
||||||
|
disabledPlugins.clear();
|
||||||
|
for (ToggleablePlugin pluginEntry : enabledPlugins) {
|
||||||
|
if (!pluginEntry.isEnabled()) disabledPlugins.add(pluginEntry.getClassName());
|
||||||
|
}
|
||||||
|
configuration.saveTo(CONFIG_FILE);
|
||||||
|
changesMade = false;
|
||||||
|
reloadAlertShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad(PluginManager manager) {
|
||||||
|
Configuration configuration = abacus.getConfiguration();
|
||||||
|
Set<String> disabledPlugins = configuration.getDisabledPlugins();
|
||||||
|
numberImplementationOptions.addAll(abacus.getPluginManager().getAllNumberImplementations());
|
||||||
|
String actualImplementation = configuration.getNumberImplementation();
|
||||||
|
String toSelect = (numberImplementationOptions.contains(actualImplementation)) ? actualImplementation : "<default>";
|
||||||
|
numberImplementationBox.getSelectionModel().select(toSelect);
|
||||||
|
for (Class<?> pluginClass : abacus.getPluginManager().getLoadedPluginClasses()) {
|
||||||
|
String fullName = pluginClass.getName();
|
||||||
|
ToggleablePlugin plugin = new ToggleablePlugin(!disabledPlugins.contains(fullName), fullName);
|
||||||
|
plugin.enabledProperty().addListener(e -> changesMade = true);
|
||||||
|
enabledPlugins.add(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnload(PluginManager manager) {
|
||||||
|
enabledPlugins.clear();
|
||||||
|
numberImplementationOptions.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/main/java/org/nwapw/abacus/fx/CopyableCell.java
Normal file
36
src/main/java/org/nwapw/abacus/fx/CopyableCell.java
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package org.nwapw.abacus.fx;
|
||||||
|
|
||||||
|
import javafx.scene.control.TableCell;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.datatransfer.StringSelection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cell that copies its value to the clipboard
|
||||||
|
* when double clicked.
|
||||||
|
*
|
||||||
|
* @param <S> The type of the table view generic type.
|
||||||
|
* @param <T> The type of the value contained in the cell.
|
||||||
|
*/
|
||||||
|
public class CopyableCell<S, T> extends TableCell<S, T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new copyable cell.
|
||||||
|
*/
|
||||||
|
public CopyableCell() {
|
||||||
|
addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
|
||||||
|
if (event.getClickCount() == 2) {
|
||||||
|
Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||||
|
.setContents(new StringSelection(getText()), null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateItem(T item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
setText((empty || item == null) ? null : item.toString());
|
||||||
|
setGraphic(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
97
src/main/java/org/nwapw/abacus/fx/HistoryModel.java
Normal file
97
src/main/java/org/nwapw/abacus/fx/HistoryModel.java
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
60
src/main/java/org/nwapw/abacus/fx/ToggleablePlugin.java
Normal file
60
src/main/java/org/nwapw/abacus/fx/ToggleablePlugin.java
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
151
src/main/java/org/nwapw/abacus/lexing/Lexer.java
Normal file
151
src/main/java/org/nwapw/abacus/lexing/Lexer.java
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package org.nwapw.abacus.lexing;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.lexing.pattern.EndNode;
|
||||||
|
import org.nwapw.abacus.lexing.pattern.Match;
|
||||||
|
import org.nwapw.abacus.lexing.pattern.Pattern;
|
||||||
|
import org.nwapw.abacus.lexing.pattern.PatternNode;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lexer that can generate tokens of a given type given a list of regular expressions
|
||||||
|
* to operate on.
|
||||||
|
*
|
||||||
|
* @param <T> the type used to identify which match belongs to which pattern.
|
||||||
|
*/
|
||||||
|
public class Lexer<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The registered patterns.
|
||||||
|
*/
|
||||||
|
private Map<PatternEntry<T>, Pattern<T>> patterns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new lexer with no registered patterns.
|
||||||
|
*/
|
||||||
|
public Lexer() {
|
||||||
|
patterns = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a single pattern.
|
||||||
|
*
|
||||||
|
* @param pattern the pattern regex
|
||||||
|
* @param id the ID by which to identify the pattern.
|
||||||
|
*/
|
||||||
|
public void register(String pattern, T id) {
|
||||||
|
Pattern<T> compiledPattern = new Pattern<>(pattern, id);
|
||||||
|
if (compiledPattern.getHead() != null) patterns.put(new PatternEntry<>(pattern, id), compiledPattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters a pattern.
|
||||||
|
*
|
||||||
|
* @param pattern the pattern to unregister
|
||||||
|
* @param id the ID by which to identify the pattern.
|
||||||
|
*/
|
||||||
|
public void unregister(String pattern, T id) {
|
||||||
|
patterns.remove(new PatternEntry<>(pattern, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads one token from the given string.
|
||||||
|
*
|
||||||
|
* @param from the string to read from
|
||||||
|
* @param startAt the index to start at
|
||||||
|
* @param compare the comparator used to sort tokens by their ID.
|
||||||
|
* @return the best match.
|
||||||
|
*/
|
||||||
|
public Match<T> lexOne(String from, int startAt, Comparator<T> compare) {
|
||||||
|
ArrayList<Match<T>> matches = new ArrayList<>();
|
||||||
|
HashSet<PatternNode<T>> currentSet = new HashSet<>();
|
||||||
|
HashSet<PatternNode<T>> futureSet = new HashSet<>();
|
||||||
|
int index = startAt;
|
||||||
|
for (Pattern<T> pattern : patterns.values()) {
|
||||||
|
pattern.getHead().addInto(currentSet);
|
||||||
|
}
|
||||||
|
while (!currentSet.isEmpty()) {
|
||||||
|
for (PatternNode<T> node : currentSet) {
|
||||||
|
if (index < from.length() && node.matches(from.charAt(index))) {
|
||||||
|
node.addOutputsInto(futureSet);
|
||||||
|
} else if (node instanceof EndNode) {
|
||||||
|
matches.add(new Match<>(from.substring(startAt, index), ((EndNode<T>) node).getPatternId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<PatternNode<T>> tmp = currentSet;
|
||||||
|
currentSet = futureSet;
|
||||||
|
futureSet = tmp;
|
||||||
|
futureSet.clear();
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
matches.sort((a, b) -> compare.compare(a.getType(), b.getType()));
|
||||||
|
if (compare != null) {
|
||||||
|
matches.sort(Comparator.comparingInt(a -> a.getContent().length()));
|
||||||
|
}
|
||||||
|
return matches.isEmpty() ? null : matches.get(matches.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads all tokens from a string.
|
||||||
|
*
|
||||||
|
* @param from the string to start from.
|
||||||
|
* @param startAt the index to start at.
|
||||||
|
* @param compare the comparator used to sort matches by their IDs.
|
||||||
|
* @return the resulting list of matches, in order, or null on error.
|
||||||
|
*/
|
||||||
|
public List<Match<T>> lexAll(String from, int startAt, Comparator<T> compare) {
|
||||||
|
int index = startAt;
|
||||||
|
ArrayList<Match<T>> matches = new ArrayList<>();
|
||||||
|
Match<T> lastMatch = null;
|
||||||
|
while (index < from.length() && (lastMatch = lexOne(from, index, compare)) != null) {
|
||||||
|
int length = lastMatch.getContent().length();
|
||||||
|
if (length == 0) return null;
|
||||||
|
matches.add(lastMatch);
|
||||||
|
index += length;
|
||||||
|
}
|
||||||
|
if (lastMatch == null) return null;
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An entry that represents a pattern that has been registered with the lexer.
|
||||||
|
*
|
||||||
|
* @param <T> the type used to identify the pattern.
|
||||||
|
*/
|
||||||
|
private static class PatternEntry<T> {
|
||||||
|
/**
|
||||||
|
* The name of the entry.
|
||||||
|
*/
|
||||||
|
public String name;
|
||||||
|
/**
|
||||||
|
* The id of the entry.
|
||||||
|
*/
|
||||||
|
public T id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new pattern entry with the given name and id.
|
||||||
|
*
|
||||||
|
* @param name the name of the pattern entry.
|
||||||
|
* @param id the id of the pattern entry.
|
||||||
|
*/
|
||||||
|
public PatternEntry(String name, T id) {
|
||||||
|
this.name = name;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(name, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return obj instanceof PatternEntry &&
|
||||||
|
((PatternEntry) obj).name.equals(name) &&
|
||||||
|
((PatternEntry) obj).id.equals(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
package org.nwapw.abacus.lexing.pattern;
|
package org.nwapw.abacus.lexing.pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pattern node that matches any character.
|
||||||
|
*
|
||||||
|
* @param <T> the type that's used to tell which pattern this node belongs to.
|
||||||
|
*/
|
||||||
public class AnyNode<T> extends PatternNode<T> {
|
public class AnyNode<T> extends PatternNode<T> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
33
src/main/java/org/nwapw/abacus/lexing/pattern/EndNode.java
Normal file
33
src/main/java/org/nwapw/abacus/lexing/pattern/EndNode.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package org.nwapw.abacus.lexing.pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node that represents a successful match.
|
||||||
|
*
|
||||||
|
* @param <T> the type that's used to tell which pattern this node belongs to.
|
||||||
|
*/
|
||||||
|
public class EndNode<T> extends PatternNode<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the pattenr that has been matched.
|
||||||
|
*/
|
||||||
|
private T patternId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new end node with the given ID.
|
||||||
|
*
|
||||||
|
* @param patternId the pattern ID.
|
||||||
|
*/
|
||||||
|
public EndNode(T patternId) {
|
||||||
|
this.patternId = patternId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the pattern ID.
|
||||||
|
*
|
||||||
|
* @return the pattern ID.
|
||||||
|
*/
|
||||||
|
public T getPatternId() {
|
||||||
|
return patternId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
20
src/main/java/org/nwapw/abacus/lexing/pattern/LinkNode.java
Normal file
20
src/main/java/org/nwapw/abacus/lexing/pattern/LinkNode.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package org.nwapw.abacus.lexing.pattern;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node that is used as structural glue in pattern compilation.
|
||||||
|
*
|
||||||
|
* @param <T> the type that's used to tell which pattern this node belongs to.
|
||||||
|
*/
|
||||||
|
public class LinkNode<T> extends PatternNode<T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addInto(Collection<PatternNode<T>> into) {
|
||||||
|
if (!into.contains(this)) {
|
||||||
|
into.add(this);
|
||||||
|
addOutputsInto(into);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
47
src/main/java/org/nwapw/abacus/lexing/pattern/Match.java
Normal file
47
src/main/java/org/nwapw/abacus/lexing/pattern/Match.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package org.nwapw.abacus.lexing.pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A match that has been generated by the lexer.
|
||||||
|
*
|
||||||
|
* @param <T> the type used to represent the ID of the pattern this match belongs to.
|
||||||
|
*/
|
||||||
|
public class Match<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The content of this match.
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
/**
|
||||||
|
* The pattern type this match matched.
|
||||||
|
*/
|
||||||
|
private T type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new match with the given parameters.
|
||||||
|
*
|
||||||
|
* @param content the content of this match.
|
||||||
|
* @param type the type of the match.
|
||||||
|
*/
|
||||||
|
public Match(String content, T type) {
|
||||||
|
this.content = content;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the content of this match.
|
||||||
|
*
|
||||||
|
* @return the content.
|
||||||
|
*/
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the pattern type of the node.
|
||||||
|
*
|
||||||
|
* @return the ID of the pattern that this match matched.
|
||||||
|
*/
|
||||||
|
public T getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
268
src/main/java/org/nwapw/abacus/lexing/pattern/Pattern.java
Normal file
268
src/main/java/org/nwapw/abacus/lexing/pattern/Pattern.java
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
package org.nwapw.abacus.lexing.pattern;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Stack;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pattern that can be compiled from a string and used in lexing.
|
||||||
|
*
|
||||||
|
* @param <T> the type that is used to identify and sort this pattern.
|
||||||
|
*/
|
||||||
|
public class Pattern<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of this pattern.
|
||||||
|
*/
|
||||||
|
private T id;
|
||||||
|
/**
|
||||||
|
* The head of this pattern.
|
||||||
|
*/
|
||||||
|
private PatternNode<T> head;
|
||||||
|
/**
|
||||||
|
* The source string of this pattern.
|
||||||
|
*/
|
||||||
|
private String source;
|
||||||
|
/**
|
||||||
|
* The index at which the compilation has stopped.
|
||||||
|
*/
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of regex operator to functions that modify a PatternChain
|
||||||
|
* with the appropriate operation.
|
||||||
|
*/
|
||||||
|
private Map<Character, Function<PatternChain<T>, PatternChain<T>>> operations =
|
||||||
|
new HashMap<Character, Function<PatternChain<T>, PatternChain<T>>>() {{
|
||||||
|
put('+', Pattern.this::transformPlus);
|
||||||
|
put('*', Pattern.this::transformStar);
|
||||||
|
put('?', Pattern.this::transformQuestion);
|
||||||
|
}};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates / compiles a new pattern with the given id from the given string.
|
||||||
|
*
|
||||||
|
* @param from the string to compile a pattern from.
|
||||||
|
* @param id the ID to use.
|
||||||
|
*/
|
||||||
|
public Pattern(String from, T id) {
|
||||||
|
this.id = id;
|
||||||
|
index = 0;
|
||||||
|
source = from;
|
||||||
|
|
||||||
|
PatternChain<T> chain = parseSegment(false);
|
||||||
|
if (chain == null) {
|
||||||
|
head = null;
|
||||||
|
} else {
|
||||||
|
chain.append(new EndNode<>(id));
|
||||||
|
head = chain.head;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all characters that are considered "special" from
|
||||||
|
* the given string.
|
||||||
|
*
|
||||||
|
* @param from the string to sanitize.
|
||||||
|
* @return the resulting string.
|
||||||
|
*/
|
||||||
|
public static String sanitize(String from) {
|
||||||
|
Pattern<Integer> pattern = new Pattern<>("", 0);
|
||||||
|
from = from.replace(".", "\\.");
|
||||||
|
from = from.replace("|", "\\|");
|
||||||
|
from = from.replace("(", "\\(");
|
||||||
|
from = from.replace(")", "\\)");
|
||||||
|
for (Character key : pattern.operations.keySet()) {
|
||||||
|
from = from.replace("" + key, "\\" + key);
|
||||||
|
}
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A regex operator function that turns the chain
|
||||||
|
* into a one-or-more chain.
|
||||||
|
*
|
||||||
|
* @param chain the chain to transform.
|
||||||
|
* @return the modified chain.
|
||||||
|
*/
|
||||||
|
private PatternChain<T> transformPlus(PatternChain<T> chain) {
|
||||||
|
chain.tail.outputStates.add(chain.head);
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A regex operator function that turns the chain
|
||||||
|
* into a zero-or-more chain.
|
||||||
|
*
|
||||||
|
* @param chain the chain to transform.
|
||||||
|
* @return the modified chain.
|
||||||
|
*/
|
||||||
|
private PatternChain<T> transformStar(PatternChain<T> chain) {
|
||||||
|
LinkNode<T> newTail = new LinkNode<>();
|
||||||
|
LinkNode<T> newHead = new LinkNode<>();
|
||||||
|
newHead.outputStates.add(chain.head);
|
||||||
|
newHead.outputStates.add(newTail);
|
||||||
|
chain.tail.outputStates.add(newTail);
|
||||||
|
newTail.outputStates.add(newHead);
|
||||||
|
chain.head = newHead;
|
||||||
|
chain.tail = newTail;
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A regex operator function that turns the chain
|
||||||
|
* into a zero-or-one chain.
|
||||||
|
*
|
||||||
|
* @param chain the chain to transform.
|
||||||
|
* @return the modified chain.
|
||||||
|
*/
|
||||||
|
private PatternChain<T> transformQuestion(PatternChain<T> chain) {
|
||||||
|
LinkNode<T> newTail = new LinkNode<>();
|
||||||
|
LinkNode<T> newHead = new LinkNode<>();
|
||||||
|
newHead.outputStates.add(chain.head);
|
||||||
|
newHead.outputStates.add(newTail);
|
||||||
|
chain.tail.outputStates.add(newTail);
|
||||||
|
chain.head = newHead;
|
||||||
|
chain.tail = newTail;
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines a collection of chains into one OR chain.
|
||||||
|
*
|
||||||
|
* @param collection the collection of chains to combine.
|
||||||
|
* @return the resulting OR chain.
|
||||||
|
*/
|
||||||
|
private PatternChain<T> combineChains(Collection<PatternChain<T>> collection) {
|
||||||
|
LinkNode<T> head = new LinkNode<>();
|
||||||
|
LinkNode<T> tail = new LinkNode<>();
|
||||||
|
PatternChain<T> newChain = new PatternChain<>(head, tail);
|
||||||
|
for (PatternChain<T> chain : collection) {
|
||||||
|
head.outputStates.add(chain.head);
|
||||||
|
chain.tail.outputStates.add(tail);
|
||||||
|
}
|
||||||
|
return newChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a single value from the input into a chain.
|
||||||
|
*
|
||||||
|
* @return the resulting chain, or null on error.
|
||||||
|
*/
|
||||||
|
private PatternChain<T> parseValue() {
|
||||||
|
if (index >= source.length()) return null;
|
||||||
|
if (source.charAt(index) == '\\') {
|
||||||
|
if (++index >= source.length()) return null;
|
||||||
|
}
|
||||||
|
return new PatternChain<>(new ValueNode<>(source.charAt(index++)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a [] range from the input into a chain.
|
||||||
|
*
|
||||||
|
* @return the resulting chain, or null on error.
|
||||||
|
*/
|
||||||
|
private PatternChain<T> parseOr() {
|
||||||
|
Stack<PatternChain<T>> orStack = new Stack<>();
|
||||||
|
index++;
|
||||||
|
while (index < source.length() && source.charAt(index) != ']') {
|
||||||
|
if (source.charAt(index) == '-') {
|
||||||
|
index++;
|
||||||
|
if (orStack.empty() || orStack.peek().tail.range() == '\0') return null;
|
||||||
|
PatternChain<T> bottomRange = orStack.pop();
|
||||||
|
PatternChain<T> topRange = parseValue();
|
||||||
|
if (topRange == null || topRange.tail.range() == '\0') return null;
|
||||||
|
|
||||||
|
orStack.push(new PatternChain<>(new RangeNode<>(bottomRange.tail.range(), topRange.tail.range())));
|
||||||
|
} else {
|
||||||
|
PatternChain<T> newChain = parseValue();
|
||||||
|
if (newChain == null) return null;
|
||||||
|
orStack.push(newChain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index++ >= source.length()) return null;
|
||||||
|
return (orStack.size() == 1) ? orStack.pop() : combineChains(orStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a repeatable segment from the input into a chain
|
||||||
|
*
|
||||||
|
* @param isSubsegment whether the segment is a sub-expression "()", and therefore
|
||||||
|
* whether to expect a closing brace.
|
||||||
|
* @return the resulting chain, or null on error.
|
||||||
|
*/
|
||||||
|
private PatternChain<T> parseSegment(boolean isSubsegment) {
|
||||||
|
if (index >= source.length() || ((source.charAt(index) != '(') && isSubsegment)) return null;
|
||||||
|
if (isSubsegment) index++;
|
||||||
|
|
||||||
|
Stack<PatternChain<T>> orChain = new Stack<>();
|
||||||
|
PatternChain<T> fullChain = new PatternChain<>();
|
||||||
|
PatternChain<T> currentChain = null;
|
||||||
|
while (index < source.length() && source.charAt(index) != ')') {
|
||||||
|
char currentChar = source.charAt(index);
|
||||||
|
if (operations.containsKey(currentChar)) {
|
||||||
|
if (currentChain == null) return null;
|
||||||
|
|
||||||
|
currentChain = operations.get(currentChar).apply(currentChain);
|
||||||
|
fullChain.append(currentChain);
|
||||||
|
currentChain = null;
|
||||||
|
index++;
|
||||||
|
} else if (currentChar == '|') {
|
||||||
|
if (currentChain == null) return null;
|
||||||
|
|
||||||
|
fullChain.append(currentChain);
|
||||||
|
orChain.push(fullChain);
|
||||||
|
currentChain = null;
|
||||||
|
fullChain = new PatternChain<>();
|
||||||
|
if (++index >= source.length()) return null;
|
||||||
|
} else if (currentChar == '(') {
|
||||||
|
if (currentChain != null) {
|
||||||
|
fullChain.append(currentChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentChain = parseSegment(true);
|
||||||
|
if (currentChain == null) return null;
|
||||||
|
} else if (currentChar == '[') {
|
||||||
|
if (currentChain != null) {
|
||||||
|
fullChain.append(currentChain);
|
||||||
|
}
|
||||||
|
currentChain = parseOr();
|
||||||
|
if (currentChain == null) return null;
|
||||||
|
} else if (currentChar == '.') {
|
||||||
|
if (currentChain != null) {
|
||||||
|
fullChain.append(currentChain);
|
||||||
|
}
|
||||||
|
currentChain = new PatternChain<>(new AnyNode<>());
|
||||||
|
index++;
|
||||||
|
} else {
|
||||||
|
if (currentChain != null) {
|
||||||
|
fullChain.append(currentChain);
|
||||||
|
}
|
||||||
|
currentChain = parseValue();
|
||||||
|
if (currentChain == null) return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(!isSubsegment || (index < source.length() && source.charAt(index) == ')'))) return null;
|
||||||
|
if (isSubsegment) index++;
|
||||||
|
|
||||||
|
if (currentChain != null) fullChain.append(currentChain);
|
||||||
|
if (!orChain.empty()) {
|
||||||
|
orChain.push(fullChain);
|
||||||
|
fullChain = combineChains(orChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the head PatternNode, for use in matching
|
||||||
|
*
|
||||||
|
* @return the pattern node.
|
||||||
|
*/
|
||||||
|
public PatternNode<T> getHead() {
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package org.nwapw.abacus.lexing.pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A chain of nodes that can be treated as a single unit.
|
||||||
|
* Used during pattern compilation.
|
||||||
|
*
|
||||||
|
* @param <T> the type used to identify which pattern has been matched.
|
||||||
|
*/
|
||||||
|
public class PatternChain<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The head node of the chain.
|
||||||
|
*/
|
||||||
|
public PatternNode<T> head;
|
||||||
|
/**
|
||||||
|
* The tail node of the chain.
|
||||||
|
*/
|
||||||
|
public PatternNode<T> tail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new chain with the given start and end.
|
||||||
|
*
|
||||||
|
* @param head the start of the chain.
|
||||||
|
* @param tail the end of the chain.
|
||||||
|
*/
|
||||||
|
public PatternChain(PatternNode<T> head, PatternNode<T> tail) {
|
||||||
|
this.head = head;
|
||||||
|
this.tail = tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a chain that starts and ends with the same node.
|
||||||
|
*
|
||||||
|
* @param node the node to use.
|
||||||
|
*/
|
||||||
|
public PatternChain(PatternNode<T> node) {
|
||||||
|
this(node, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty chain.
|
||||||
|
*/
|
||||||
|
public PatternChain() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the other chain to this one. This modifies
|
||||||
|
* the nodes, as well.
|
||||||
|
* If this chain is empty, it is set to the other.
|
||||||
|
*
|
||||||
|
* @param other the other chain to append.
|
||||||
|
*/
|
||||||
|
public void append(PatternChain<T> other) {
|
||||||
|
if (other.head == null || tail == null) {
|
||||||
|
this.head = other.head;
|
||||||
|
this.tail = other.tail;
|
||||||
|
} else {
|
||||||
|
tail.outputStates.add(other.head);
|
||||||
|
tail = other.tail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a single node to this chain. This modifies
|
||||||
|
* the nodes, as well.
|
||||||
|
* If this chain is empty, it is set to the node.
|
||||||
|
*
|
||||||
|
* @param node the node to append to this chain.
|
||||||
|
*/
|
||||||
|
public void append(PatternNode<T> node) {
|
||||||
|
if (tail == null) {
|
||||||
|
head = tail = node;
|
||||||
|
} else {
|
||||||
|
tail.outputStates.add(node);
|
||||||
|
tail = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package org.nwapw.abacus.lexing.pattern;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A base class for a pattern node. Provides all functions
|
||||||
|
* necessary for matching, and is constructed by a Pattern instance
|
||||||
|
* from a string.
|
||||||
|
*
|
||||||
|
* @param <T> the type that's used to tell which pattern this node belongs to.
|
||||||
|
*/
|
||||||
|
public class PatternNode<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of states to which the lexer should continue
|
||||||
|
* should this node be correctly matched.
|
||||||
|
*/
|
||||||
|
protected Set<PatternNode<T>> outputStates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new pattern node.
|
||||||
|
*/
|
||||||
|
public PatternNode() {
|
||||||
|
outputStates = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the current input character can
|
||||||
|
* be matched by this node.
|
||||||
|
*
|
||||||
|
* @param other the character being matched.
|
||||||
|
* @return true if the character can be matched, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean matches(char other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this node can be used as part of a range, returns that value.
|
||||||
|
*
|
||||||
|
* @return a NULL terminator if this character cannot be converted
|
||||||
|
* into a range bound, or the appropriate range bound if it can.
|
||||||
|
*/
|
||||||
|
public char range() {
|
||||||
|
return '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds this node in a collection of other nodes.
|
||||||
|
*
|
||||||
|
* @param into the collection to add into.
|
||||||
|
*/
|
||||||
|
public void addInto(Collection<PatternNode<T>> into) {
|
||||||
|
into.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the node's children into a collection of other nodes.
|
||||||
|
*
|
||||||
|
* @param into the collection to add into.
|
||||||
|
*/
|
||||||
|
public void addOutputsInto(Collection<PatternNode<T>> into) {
|
||||||
|
outputStates.forEach(e -> e.addInto(into));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
35
src/main/java/org/nwapw/abacus/lexing/pattern/RangeNode.java
Normal file
35
src/main/java/org/nwapw/abacus/lexing/pattern/RangeNode.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package org.nwapw.abacus.lexing.pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node that matches a range of characters.
|
||||||
|
*
|
||||||
|
* @param <T> the type that's used to tell which pattern this node belongs to.
|
||||||
|
*/
|
||||||
|
public class RangeNode<T> extends PatternNode<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bottom bound of the range, inclusive.
|
||||||
|
*/
|
||||||
|
private char from;
|
||||||
|
/**
|
||||||
|
* The top bound of the range, inclusive.
|
||||||
|
*/
|
||||||
|
private char to;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new range node from the given range.
|
||||||
|
*
|
||||||
|
* @param from the bottom bound of the range.
|
||||||
|
* @param to the top bound of hte range.
|
||||||
|
*/
|
||||||
|
public RangeNode(char from, char to) {
|
||||||
|
this.from = from;
|
||||||
|
this.to = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(char other) {
|
||||||
|
return other >= from && other <= to;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
33
src/main/java/org/nwapw/abacus/lexing/pattern/ValueNode.java
Normal file
33
src/main/java/org/nwapw/abacus/lexing/pattern/ValueNode.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package org.nwapw.abacus.lexing.pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node that matches a single value.
|
||||||
|
*
|
||||||
|
* @param <T> the type that's used to tell which pattern this node belongs to.
|
||||||
|
*/
|
||||||
|
public class ValueNode<T> extends PatternNode<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value this node matches.
|
||||||
|
*/
|
||||||
|
private char value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new node that matches the given character.
|
||||||
|
*
|
||||||
|
* @param value the character value of the node.
|
||||||
|
*/
|
||||||
|
public ValueNode(char value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(char other) {
|
||||||
|
return other == value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char range() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.nwapw.abacus.number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when the computation is interrupted by
|
||||||
|
* the user.
|
||||||
|
*/
|
||||||
|
public class ComputationInterruptedException extends RuntimeException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new exception of this type.
|
||||||
|
*/
|
||||||
|
public ComputationInterruptedException(){
|
||||||
|
super("Computation interrupted by user.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
132
src/main/java/org/nwapw/abacus/number/NaiveNumber.java
Executable file
132
src/main/java/org/nwapw/abacus/number/NaiveNumber.java
Executable file
@@ -0,0 +1,132 @@
|
|||||||
|
package org.nwapw.abacus.number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of NumberInterface using a double.
|
||||||
|
*/
|
||||||
|
public class NaiveNumber extends NumberInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number zero.
|
||||||
|
*/
|
||||||
|
public static final NaiveNumber ZERO = new NaiveNumber(0);
|
||||||
|
/**
|
||||||
|
* The number one.
|
||||||
|
*/
|
||||||
|
public static final NaiveNumber ONE = new NaiveNumber(1);
|
||||||
|
/**
|
||||||
|
* The value of this number.
|
||||||
|
*/
|
||||||
|
private double value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new NaiveNumber with the given string.
|
||||||
|
*
|
||||||
|
* @param value the value, which will be parsed as a double.
|
||||||
|
*/
|
||||||
|
public NaiveNumber(String value) {
|
||||||
|
this(Double.parseDouble(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new NaiveNumber with the given value.
|
||||||
|
*
|
||||||
|
* @param value the value to use.
|
||||||
|
*/
|
||||||
|
public NaiveNumber(double value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxPrecision() {
|
||||||
|
return 18;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface multiplyInternal(NumberInterface multiplier) {
|
||||||
|
return new NaiveNumber(value * ((NaiveNumber) multiplier).value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface divideInternal(NumberInterface divisor) {
|
||||||
|
return new NaiveNumber(value / ((NaiveNumber) divisor).value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface addInternal(NumberInterface summand) {
|
||||||
|
return new NaiveNumber(value + ((NaiveNumber) summand).value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface subtractInternal(NumberInterface subtrahend) {
|
||||||
|
return new NaiveNumber(value - ((NaiveNumber) subtrahend).value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface negateInternal() {
|
||||||
|
return new NaiveNumber(-value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface intPowInternal(int exponent) {
|
||||||
|
if (exponent == 0) {
|
||||||
|
return NaiveNumber.ONE;
|
||||||
|
}
|
||||||
|
boolean takeReciprocal = exponent < 0;
|
||||||
|
exponent = Math.abs(exponent);
|
||||||
|
NumberInterface power = this;
|
||||||
|
for (int currentExponent = 1; currentExponent < exponent; currentExponent++) {
|
||||||
|
power = power.multiply(this);
|
||||||
|
}
|
||||||
|
if (takeReciprocal) {
|
||||||
|
power = NaiveNumber.ONE.divide(power);
|
||||||
|
}
|
||||||
|
return power;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(NumberInterface number) {
|
||||||
|
NaiveNumber num = (NaiveNumber) number;
|
||||||
|
return Double.compare(value, num.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int signum() {
|
||||||
|
return this.compareTo(ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface ceilingInternal() {
|
||||||
|
return new NaiveNumber(Math.ceil(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface floorInternal() {
|
||||||
|
return new NaiveNumber(Math.floor(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface fractionalPartInternal() {
|
||||||
|
return new NaiveNumber(value - Math.floor(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int intValue() {
|
||||||
|
return (int) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface promoteToInternal(Class<? extends NumberInterface> toClass) {
|
||||||
|
if (toClass == this.getClass()) return this;
|
||||||
|
else if (toClass == PreciseNumber.class) {
|
||||||
|
return new PreciseNumber(Double.toString(value));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
double shiftBy = Math.pow(10, 10);
|
||||||
|
return Double.toString(Math.round(value * shiftBy) / shiftBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
258
src/main/java/org/nwapw/abacus/number/NumberInterface.java
Executable file
258
src/main/java/org/nwapw/abacus/number/NumberInterface.java
Executable file
@@ -0,0 +1,258 @@
|
|||||||
|
package org.nwapw.abacus.number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface used to represent a number.
|
||||||
|
*/
|
||||||
|
public abstract class NumberInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the thread was interrupted and
|
||||||
|
* throw an exception to end the computation.
|
||||||
|
*/
|
||||||
|
private static void checkInterrupted(){
|
||||||
|
if(Thread.currentThread().isInterrupted())
|
||||||
|
throw new ComputationInterruptedException();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* The maximum precision to which this number operates.
|
||||||
|
*
|
||||||
|
* @return the precision.
|
||||||
|
*/
|
||||||
|
public abstract int getMaxPrecision();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiplies this number by another, returning
|
||||||
|
* a new number instance.
|
||||||
|
*
|
||||||
|
* @param multiplier the multiplier
|
||||||
|
* @return the result of the multiplication.
|
||||||
|
*/
|
||||||
|
protected abstract NumberInterface multiplyInternal(NumberInterface multiplier);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiplies this number by another, returning
|
||||||
|
* a new number instance. Also, checks if the
|
||||||
|
* thread has been interrupted, and if so, throws
|
||||||
|
* an exception.
|
||||||
|
*
|
||||||
|
* @param multiplier the multiplier
|
||||||
|
* @return the result of the multiplication.
|
||||||
|
*/
|
||||||
|
public final NumberInterface multiply(NumberInterface multiplier){
|
||||||
|
checkInterrupted();
|
||||||
|
return multiplyInternal(multiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Divides this number by another, returning
|
||||||
|
* a new number instance.
|
||||||
|
*
|
||||||
|
* @param divisor the divisor
|
||||||
|
* @return the result of the division.
|
||||||
|
*/
|
||||||
|
protected abstract NumberInterface divideInternal(NumberInterface divisor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Divides this number by another, returning
|
||||||
|
* a new number instance. Also, checks if the
|
||||||
|
* thread has been interrupted, and if so, throws
|
||||||
|
* an exception.
|
||||||
|
*
|
||||||
|
* @param divisor the divisor
|
||||||
|
* @return the result of the division.
|
||||||
|
*/
|
||||||
|
public final NumberInterface divide(NumberInterface divisor){
|
||||||
|
checkInterrupted();
|
||||||
|
return divideInternal(divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds this number to another, returning
|
||||||
|
* a new number instance.
|
||||||
|
*
|
||||||
|
* @param summand the summand
|
||||||
|
* @return the result of the summation.
|
||||||
|
*/
|
||||||
|
protected abstract NumberInterface addInternal(NumberInterface summand);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds this number to another, returning
|
||||||
|
* a new number instance. Also, checks if the
|
||||||
|
* thread has been interrupted, and if so, throws
|
||||||
|
* an exception.
|
||||||
|
*
|
||||||
|
* @param summand the summand
|
||||||
|
* @return the result of the summation.
|
||||||
|
*/
|
||||||
|
public final NumberInterface add(NumberInterface summand){
|
||||||
|
checkInterrupted();
|
||||||
|
return addInternal(summand);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtracts another number from this number,
|
||||||
|
* a new number instance.
|
||||||
|
*
|
||||||
|
* @param subtrahend the subtrahend.
|
||||||
|
* @return the result of the subtraction.
|
||||||
|
*/
|
||||||
|
protected abstract NumberInterface subtractInternal(NumberInterface subtrahend);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtracts another number from this number,
|
||||||
|
* a new number instance. Also, checks if the
|
||||||
|
* thread has been interrupted, and if so, throws
|
||||||
|
* an exception.
|
||||||
|
*
|
||||||
|
* @param subtrahend the subtrahend.
|
||||||
|
* @return the result of the subtraction.
|
||||||
|
*/
|
||||||
|
public final NumberInterface subtract(NumberInterface subtrahend){
|
||||||
|
checkInterrupted();
|
||||||
|
return subtractInternal(subtrahend);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new instance of this number with
|
||||||
|
* the sign flipped.
|
||||||
|
*
|
||||||
|
* @return the new instance.
|
||||||
|
*/
|
||||||
|
protected abstract NumberInterface negateInternal();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new instance of this number with
|
||||||
|
* the sign flipped. Also, checks if the
|
||||||
|
* thread has been interrupted, and if so, throws
|
||||||
|
* an exception.
|
||||||
|
*
|
||||||
|
* @return the new instance.
|
||||||
|
*/
|
||||||
|
public final NumberInterface negate(){
|
||||||
|
checkInterrupted();
|
||||||
|
return negateInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raises this number to an integer power.
|
||||||
|
*
|
||||||
|
* @param exponent the exponent to which to take the number.
|
||||||
|
* @return the resulting value.
|
||||||
|
*/
|
||||||
|
protected abstract NumberInterface intPowInternal(int exponent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raises this number to an integer power. Also, checks if the
|
||||||
|
* thread has been interrupted, and if so, throws
|
||||||
|
* an exception.
|
||||||
|
*
|
||||||
|
* @param exponent the exponent to which to take the number.
|
||||||
|
* @return the resulting value.
|
||||||
|
*/
|
||||||
|
public final NumberInterface intPow(int exponent){
|
||||||
|
checkInterrupted();
|
||||||
|
return intPowInternal(exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares this number to another.
|
||||||
|
*
|
||||||
|
* @param number the number to compare to.
|
||||||
|
* @return same as Integer.compare();
|
||||||
|
*/
|
||||||
|
public abstract int compareTo(NumberInterface number);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as Math.signum().
|
||||||
|
*
|
||||||
|
* @return 1 if this number is positive, -1 if this number is negative, 0 if this number is 0.
|
||||||
|
*/
|
||||||
|
public abstract int signum();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the least integer greater than or equal to the number.
|
||||||
|
*
|
||||||
|
* @return the least integer >= the number, if int can hold the value.
|
||||||
|
*/
|
||||||
|
protected abstract NumberInterface ceilingInternal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the least integer greater than or equal to the number.
|
||||||
|
* Also, checks if the thread has been interrupted, and if so, throws
|
||||||
|
* an exception.
|
||||||
|
*
|
||||||
|
* @return the least integer bigger or equal to the number, if int can hold the value.
|
||||||
|
*/
|
||||||
|
public final NumberInterface ceiling(){
|
||||||
|
checkInterrupted();
|
||||||
|
return ceilingInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the greatest integer less than or equal to the number.
|
||||||
|
*
|
||||||
|
* @return the greatest integer smaller or equal the number, if int can hold the value.
|
||||||
|
*/
|
||||||
|
protected abstract NumberInterface floorInternal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the greatest integer less than or equal to the number.
|
||||||
|
* Also, checks if the thread has been interrupted, and if so, throws
|
||||||
|
* an exception.
|
||||||
|
*
|
||||||
|
* @return the greatest int >= the number, if int can hold the value.
|
||||||
|
*/
|
||||||
|
public final NumberInterface floor(){
|
||||||
|
checkInterrupted();
|
||||||
|
return floorInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fractional part of the number.
|
||||||
|
*
|
||||||
|
* @return the fractional part of the number.
|
||||||
|
*/
|
||||||
|
protected abstract NumberInterface fractionalPartInternal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fractional part of the number.
|
||||||
|
* Also, checks if the thread has been interrupted,
|
||||||
|
* and if so, throws an exception.
|
||||||
|
* @return the fractional part of the number.
|
||||||
|
*/
|
||||||
|
public final NumberInterface fractionalPart(){
|
||||||
|
checkInterrupted();
|
||||||
|
return fractionalPartInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the integer representation of this number, discarding any fractional part,
|
||||||
|
* if int can hold the value.
|
||||||
|
*
|
||||||
|
* @return the integer value of this number.
|
||||||
|
*/
|
||||||
|
public abstract int intValue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promotes this class to another number class.
|
||||||
|
*
|
||||||
|
* @param toClass the class to promote to.
|
||||||
|
* @return the resulting new instance.
|
||||||
|
*/
|
||||||
|
protected abstract NumberInterface promoteToInternal(Class<? extends NumberInterface> toClass);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promotes this class to another number class. Also, checks if the
|
||||||
|
* thread has been interrupted, and if so, throws
|
||||||
|
* an exception.
|
||||||
|
*
|
||||||
|
* @param toClass the class to promote to.
|
||||||
|
* @return the resulting new instance.
|
||||||
|
*/
|
||||||
|
public final NumberInterface promoteTo(Class<? extends NumberInterface> toClass) {
|
||||||
|
checkInterrupted();
|
||||||
|
return promoteToInternal(toClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
154
src/main/java/org/nwapw/abacus/number/PreciseNumber.java
Executable file
154
src/main/java/org/nwapw/abacus/number/PreciseNumber.java
Executable file
@@ -0,0 +1,154 @@
|
|||||||
|
package org.nwapw.abacus.number;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A number that uses a BigDecimal to store its value,
|
||||||
|
* leading to infinite possible precision.
|
||||||
|
*/
|
||||||
|
public class PreciseNumber extends NumberInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number one.
|
||||||
|
*/
|
||||||
|
public static final PreciseNumber ONE = new PreciseNumber(BigDecimal.ONE);
|
||||||
|
/**
|
||||||
|
* The number zero.
|
||||||
|
*/
|
||||||
|
public static final PreciseNumber ZERO = new PreciseNumber(BigDecimal.ZERO);
|
||||||
|
/**
|
||||||
|
* The number ten.
|
||||||
|
*/
|
||||||
|
public static final PreciseNumber TEN = new PreciseNumber(BigDecimal.TEN);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value of the PreciseNumber.
|
||||||
|
*/
|
||||||
|
BigDecimal value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a precise number from the given string.
|
||||||
|
*
|
||||||
|
* @param string a string representation of the number meeting the same conditions
|
||||||
|
* as the BidDecimal(String) constructor.
|
||||||
|
*/
|
||||||
|
public PreciseNumber(String string) {
|
||||||
|
value = new BigDecimal(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a precise number from the given BigDecimal.
|
||||||
|
*
|
||||||
|
* @param value a BigDecimal object representing the value of the number.
|
||||||
|
*/
|
||||||
|
public PreciseNumber(BigDecimal value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxPrecision() {
|
||||||
|
return 65;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface multiplyInternal(NumberInterface multiplier) {
|
||||||
|
return new PreciseNumber(this.value.multiply(((PreciseNumber) multiplier).value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface divideInternal(NumberInterface divisor) {
|
||||||
|
return new PreciseNumber(value.divide(((PreciseNumber) divisor).value, this.getMaxPrecision(), RoundingMode.HALF_UP));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface addInternal(NumberInterface summand) {
|
||||||
|
return new PreciseNumber(value.add(((PreciseNumber) summand).value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface subtractInternal(NumberInterface subtrahend) {
|
||||||
|
return new PreciseNumber(value.subtract(((PreciseNumber) subtrahend).value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface intPowInternal(int exponent) {
|
||||||
|
if (exponent == 0) {
|
||||||
|
return PreciseNumber.ONE;
|
||||||
|
}
|
||||||
|
boolean takeReciprocal = exponent < 0;
|
||||||
|
exponent = Math.abs(exponent);
|
||||||
|
NumberInterface power = this;
|
||||||
|
for (int currentExponent = 1; currentExponent < exponent; currentExponent++) {
|
||||||
|
power = power.multiply(this);
|
||||||
|
}
|
||||||
|
if (takeReciprocal) {
|
||||||
|
power = PreciseNumber.ONE.divide(power);
|
||||||
|
}
|
||||||
|
return power;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(NumberInterface number) {
|
||||||
|
return value.compareTo(((PreciseNumber) number).value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int signum() {
|
||||||
|
return value.signum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface ceilingInternal() {
|
||||||
|
String str = value.toPlainString();
|
||||||
|
int decimalIndex = str.indexOf('.');
|
||||||
|
if (decimalIndex != -1) {
|
||||||
|
return this.floor().add(ONE);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface floorInternal() {
|
||||||
|
String str = value.toPlainString();
|
||||||
|
int decimalIndex = str.indexOf('.');
|
||||||
|
if (decimalIndex != -1) {
|
||||||
|
return new PreciseNumber(str.substring(0, decimalIndex));
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface fractionalPartInternal() {
|
||||||
|
String str = value.toPlainString();
|
||||||
|
int decimalIndex = str.indexOf('.');
|
||||||
|
if (decimalIndex != -1) {
|
||||||
|
return new PreciseNumber(str.substring(decimalIndex + 1));
|
||||||
|
}
|
||||||
|
return ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int intValue() {
|
||||||
|
return value.intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface negateInternal() {
|
||||||
|
return new PreciseNumber(value.negate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface promoteToInternal(Class<? extends NumberInterface> toClass) {
|
||||||
|
if (toClass == this.getClass()) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
BigDecimal rounded = value.setScale(getMaxPrecision() - 15, RoundingMode.HALF_UP);
|
||||||
|
return rounded.stripTrailingZeros().toPlainString();
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/main/java/org/nwapw/abacus/parsing/LexerTokenizer.java
Normal file
67
src/main/java/org/nwapw/abacus/parsing/LexerTokenizer.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package org.nwapw.abacus.parsing;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.lexing.Lexer;
|
||||||
|
import org.nwapw.abacus.lexing.pattern.Match;
|
||||||
|
import org.nwapw.abacus.lexing.pattern.Pattern;
|
||||||
|
import org.nwapw.abacus.plugin.PluginListener;
|
||||||
|
import org.nwapw.abacus.plugin.PluginManager;
|
||||||
|
import org.nwapw.abacus.tree.TokenType;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tokenzier that uses the lexer class and registered function and operator
|
||||||
|
* names to turn input into tokens in O(n) time.
|
||||||
|
*/
|
||||||
|
public class LexerTokenizer implements Tokenizer<Match<TokenType>>, PluginListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparator used to sort the tokens produced by the lexer.
|
||||||
|
*/
|
||||||
|
protected static final Comparator<TokenType> TOKEN_SORTER = Comparator.comparingInt(e -> e.priority);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lexer instance used to turn strings into matches.
|
||||||
|
*/
|
||||||
|
private Lexer<TokenType> lexer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new lexer tokenizer.
|
||||||
|
*/
|
||||||
|
public LexerTokenizer() {
|
||||||
|
lexer = new Lexer<TokenType>() {{
|
||||||
|
register(" ", TokenType.WHITESPACE);
|
||||||
|
register(",", TokenType.COMMA);
|
||||||
|
register("[0-9]*(\\.[0-9]+)?", TokenType.NUM);
|
||||||
|
register("\\(", TokenType.OPEN_PARENTH);
|
||||||
|
register("\\)", TokenType.CLOSE_PARENTH);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Match<TokenType>> tokenizeString(String string) {
|
||||||
|
return lexer.lexAll(string, 0, TOKEN_SORTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad(PluginManager manager) {
|
||||||
|
for (String operator : manager.getAllOperators()) {
|
||||||
|
lexer.register(Pattern.sanitize(operator), TokenType.OP);
|
||||||
|
}
|
||||||
|
for (String function : manager.getAllFunctions()) {
|
||||||
|
lexer.register(Pattern.sanitize(function), TokenType.FUNCTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnload(PluginManager manager) {
|
||||||
|
for (String operator : manager.getAllOperators()) {
|
||||||
|
lexer.unregister(Pattern.sanitize(operator), TokenType.OP);
|
||||||
|
}
|
||||||
|
for (String function : manager.getAllFunctions()) {
|
||||||
|
lexer.unregister(Pattern.sanitize(function), TokenType.FUNCTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
22
src/main/java/org/nwapw/abacus/parsing/Parser.java
Normal file
22
src/main/java/org/nwapw/abacus/parsing/Parser.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package org.nwapw.abacus.parsing;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.TreeNode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An itnerface that provides the ability to convert a list of tokens
|
||||||
|
* into a parse tree.
|
||||||
|
*
|
||||||
|
* @param <T> the type of tokens accepted by this parser.
|
||||||
|
*/
|
||||||
|
public interface Parser<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a tree out of the given tokens.
|
||||||
|
*
|
||||||
|
* @param tokens the tokens to construct a tree from.
|
||||||
|
* @return the constructed tree, or null on error.
|
||||||
|
*/
|
||||||
|
public TreeNode constructTree(List<T> tokens);
|
||||||
|
}
|
||||||
187
src/main/java/org/nwapw/abacus/parsing/ShuntingYardParser.java
Normal file
187
src/main/java/org/nwapw/abacus/parsing/ShuntingYardParser.java
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
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;
|
||||||
|
import org.nwapw.abacus.lexing.pattern.Match;
|
||||||
|
import org.nwapw.abacus.plugin.PluginListener;
|
||||||
|
import org.nwapw.abacus.plugin.PluginManager;
|
||||||
|
import org.nwapw.abacus.tree.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parser that uses shunting yard to rearranged matches into postfix
|
||||||
|
* and then convert them into a parse tree.
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
private Map<String, Integer> precedenceMap;
|
||||||
|
/**
|
||||||
|
* Map of operator associativity, loaded from the plugin operators.
|
||||||
|
*/
|
||||||
|
private Map<String, OperatorAssociativity> associativityMap;
|
||||||
|
/**
|
||||||
|
* Map of operator types, loaded from plugin operators.
|
||||||
|
*/
|
||||||
|
private Map<String, OperatorType> typeMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Shunting Yard parser with the given Abacus instance.
|
||||||
|
*
|
||||||
|
* @param abacus the abacus instance.
|
||||||
|
*/
|
||||||
|
public ShuntingYardParser(Abacus abacus) {
|
||||||
|
this.abacus = abacus;
|
||||||
|
precedenceMap = new HashMap<>();
|
||||||
|
associativityMap = new HashMap<>();
|
||||||
|
typeMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rearranges tokens into a postfix list, using Shunting Yard.
|
||||||
|
*
|
||||||
|
* @param from the tokens to be rearranged.
|
||||||
|
* @return the resulting list of rearranged tokens.
|
||||||
|
*/
|
||||||
|
public List<Match<TokenType>> intoPostfix(List<Match<TokenType>> from) {
|
||||||
|
ArrayList<Match<TokenType>> output = new ArrayList<>();
|
||||||
|
Stack<Match<TokenType>> tokenStack = new Stack<>();
|
||||||
|
TokenType previousType;
|
||||||
|
TokenType matchType = null;
|
||||||
|
while (!from.isEmpty()) {
|
||||||
|
Match<TokenType> match = from.remove(0);
|
||||||
|
previousType = matchType;
|
||||||
|
matchType = match.getType();
|
||||||
|
if (matchType == TokenType.NUM) {
|
||||||
|
output.add(match);
|
||||||
|
} else if (matchType == TokenType.FUNCTION) {
|
||||||
|
output.add(new Match<>("", TokenType.INTERNAL_FUNCTION_END));
|
||||||
|
tokenStack.push(match);
|
||||||
|
} else if (matchType == TokenType.OP) {
|
||||||
|
String tokenString = match.getContent();
|
||||||
|
OperatorType type = typeMap.get(tokenString);
|
||||||
|
int precedence = precedenceMap.get(tokenString);
|
||||||
|
OperatorAssociativity associativity = associativityMap.get(tokenString);
|
||||||
|
|
||||||
|
if (type == OperatorType.UNARY_POSTFIX) {
|
||||||
|
output.add(match);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenString.equals("-") && (previousType == null || previousType == TokenType.OP ||
|
||||||
|
previousType == TokenType.OPEN_PARENTH)) {
|
||||||
|
from.add(0, new Match<>("`", TokenType.OP));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!tokenStack.empty() && type == OperatorType.BINARY_INFIX) {
|
||||||
|
Match<TokenType> otherMatch = tokenStack.peek();
|
||||||
|
TokenType otherMatchType = otherMatch.getType();
|
||||||
|
if (!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break;
|
||||||
|
|
||||||
|
if (otherMatchType == TokenType.OP) {
|
||||||
|
int otherPrecedence = precedenceMap.get(otherMatch.getContent());
|
||||||
|
if (otherPrecedence < precedence ||
|
||||||
|
(associativity == OperatorAssociativity.RIGHT && otherPrecedence == precedence)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.add(tokenStack.pop());
|
||||||
|
}
|
||||||
|
tokenStack.push(match);
|
||||||
|
} else if (matchType == TokenType.OPEN_PARENTH) {
|
||||||
|
tokenStack.push(match);
|
||||||
|
} else if (matchType == TokenType.CLOSE_PARENTH || matchType == TokenType.COMMA) {
|
||||||
|
while (!tokenStack.empty() && tokenStack.peek().getType() != TokenType.OPEN_PARENTH) {
|
||||||
|
output.add(tokenStack.pop());
|
||||||
|
}
|
||||||
|
if (tokenStack.empty()) return null;
|
||||||
|
if (matchType == TokenType.CLOSE_PARENTH) {
|
||||||
|
tokenStack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!tokenStack.empty()) {
|
||||||
|
Match<TokenType> match = tokenStack.peek();
|
||||||
|
TokenType newMatchType = match.getType();
|
||||||
|
if (!(newMatchType == TokenType.OP || newMatchType == TokenType.FUNCTION)) return null;
|
||||||
|
output.add(tokenStack.pop());
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a tree recursively from a list of tokens.
|
||||||
|
*
|
||||||
|
* @param matches the list of tokens from the source string.
|
||||||
|
* @return the construct tree expression.
|
||||||
|
*/
|
||||||
|
public TreeNode constructRecursive(List<Match<TokenType>> matches) {
|
||||||
|
if (matches.size() == 0) return null;
|
||||||
|
Match<TokenType> match = matches.remove(0);
|
||||||
|
TokenType matchType = match.getType();
|
||||||
|
if (matchType == TokenType.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);
|
||||||
|
} else {
|
||||||
|
TreeNode applyTo = constructRecursive(matches);
|
||||||
|
if (applyTo == null) return null;
|
||||||
|
else return new UnaryNode(operator, applyTo);
|
||||||
|
}
|
||||||
|
} else if (matchType == TokenType.NUM) {
|
||||||
|
return new NumberNode(abacus.numberFromString(match.getContent()));
|
||||||
|
} else if (matchType == TokenType.FUNCTION) {
|
||||||
|
String functionName = match.getContent();
|
||||||
|
FunctionNode node = new FunctionNode(functionName);
|
||||||
|
while (!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END) {
|
||||||
|
TreeNode argument = constructRecursive(matches);
|
||||||
|
if (argument == null) return null;
|
||||||
|
node.prependChild(argument);
|
||||||
|
}
|
||||||
|
if (matches.isEmpty()) return null;
|
||||||
|
matches.remove(0);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TreeNode constructTree(List<Match<TokenType>> tokens) {
|
||||||
|
tokens = intoPostfix(new ArrayList<>(tokens));
|
||||||
|
if(tokens == null) return null;
|
||||||
|
Collections.reverse(tokens);
|
||||||
|
TreeNode constructedTree = constructRecursive(tokens);
|
||||||
|
return tokens.size() == 0 ? constructedTree : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad(PluginManager manager) {
|
||||||
|
for (String operator : manager.getAllOperators()) {
|
||||||
|
Operator operatorInstance = manager.operatorFor(operator);
|
||||||
|
precedenceMap.put(operator, operatorInstance.getPrecedence());
|
||||||
|
associativityMap.put(operator, operatorInstance.getAssociativity());
|
||||||
|
typeMap.put(operator, operatorInstance.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnload(PluginManager manager) {
|
||||||
|
precedenceMap.clear();
|
||||||
|
associativityMap.clear();
|
||||||
|
typeMap.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/java/org/nwapw/abacus/parsing/Tokenizer.java
Normal file
20
src/main/java/org/nwapw/abacus/parsing/Tokenizer.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package org.nwapw.abacus.parsing;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that provides the ability to convert a string into a list of tokens.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the tokens produced.
|
||||||
|
*/
|
||||||
|
public interface Tokenizer<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string into tokens.
|
||||||
|
*
|
||||||
|
* @param string the string to convert.
|
||||||
|
* @return the list of tokens, or null on error.
|
||||||
|
*/
|
||||||
|
public List<T> tokenizeString(String string);
|
||||||
|
|
||||||
|
}
|
||||||
50
src/main/java/org/nwapw/abacus/parsing/TreeBuilder.java
Normal file
50
src/main/java/org/nwapw/abacus/parsing/TreeBuilder.java
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package org.nwapw.abacus.parsing;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.TreeNode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TreeBuilder class used to piece together a Tokenizer and
|
||||||
|
* Parser of the same kind. This is used to essentially avoid
|
||||||
|
* working with any parameters at all, and the generics
|
||||||
|
* in this class are used only to ensure the tokenizer and parser
|
||||||
|
* are of the same type.
|
||||||
|
*
|
||||||
|
* @param <T> the type of tokens created by the tokenizer and used by the parser.
|
||||||
|
*/
|
||||||
|
public class TreeBuilder<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tokenizer used to convert a string into tokens.
|
||||||
|
*/
|
||||||
|
private Tokenizer<T> tokenizer;
|
||||||
|
/**
|
||||||
|
* The parser used to parse a list of tokens into a tree.
|
||||||
|
*/
|
||||||
|
private Parser<T> parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Tree Builder with the given tokenizer and parser
|
||||||
|
*
|
||||||
|
* @param tokenizer the tokenizer to turn strings into tokens
|
||||||
|
* @param parser the parser to turn tokens into a tree
|
||||||
|
*/
|
||||||
|
public TreeBuilder(Tokenizer<T> tokenizer, Parser<T> parser) {
|
||||||
|
this.tokenizer = tokenizer;
|
||||||
|
this.parser = parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the given string into a tree.
|
||||||
|
*
|
||||||
|
* @param input the string to parse into a tree.
|
||||||
|
* @return the resulting tree.
|
||||||
|
*/
|
||||||
|
public TreeNode fromString(String input) {
|
||||||
|
List<T> tokens = tokenizer.tokenizeString(input);
|
||||||
|
if (tokens == null) return null;
|
||||||
|
return parser.constructTree(tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
80
src/main/java/org/nwapw/abacus/plugin/ClassFinder.java
Normal file
80
src/main/java/org/nwapw/abacus/plugin/ClassFinder.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package org.nwapw.abacus.plugin;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that loads plugin classes from their jars.
|
||||||
|
*/
|
||||||
|
public class ClassFinder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all the plugin classes from the given plugin folder.
|
||||||
|
*
|
||||||
|
* @param filePath the path for the plugin folder.
|
||||||
|
* @return the list of all loaded classes.
|
||||||
|
* @throws IOException thrown if an error occurred scanning the plugin folder.
|
||||||
|
* @throws ClassNotFoundException thrown if the class listed in the file doesn't get loaded.
|
||||||
|
*/
|
||||||
|
public static List<Class<?>> loadJars(String filePath) throws IOException, ClassNotFoundException {
|
||||||
|
return loadJars(new File(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all the plugin classes from the given plugin folder.
|
||||||
|
*
|
||||||
|
* @param pluginFolderPath the folder in which to look for plugins.
|
||||||
|
* @return the list of all loaded classes.
|
||||||
|
* @throws IOException thrown if an error occurred scanning the plugin folder.
|
||||||
|
* @throws ClassNotFoundException thrown if the class listed in the file doesn't get loaded.
|
||||||
|
*/
|
||||||
|
public static List<Class<?>> loadJars(File pluginFolderPath) throws IOException, ClassNotFoundException {
|
||||||
|
ArrayList<Class<?>> toReturn = new ArrayList<>();
|
||||||
|
if (!pluginFolderPath.exists()) return toReturn;
|
||||||
|
ArrayList<File> files = Files.walk(pluginFolderPath.toPath())
|
||||||
|
.map(Path::toFile)
|
||||||
|
.filter(f -> f.getName().endsWith(".jar"))
|
||||||
|
.collect(Collectors.toCollection(ArrayList::new));
|
||||||
|
for (File file : files) {
|
||||||
|
toReturn.addAll(loadJar(file));
|
||||||
|
}
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the classes from a single path, given by the file.
|
||||||
|
*
|
||||||
|
* @param jarLocation the location of the jar to load.
|
||||||
|
* @return the list of loaded classes loaded from the jar.
|
||||||
|
* @throws IOException thrown if there was an error reading the file
|
||||||
|
* @throws ClassNotFoundException thrown if the class could not be loaded.
|
||||||
|
*/
|
||||||
|
public static List<Class<?>> loadJar(File jarLocation) throws IOException, ClassNotFoundException {
|
||||||
|
ArrayList<Class<?>> loadedClasses = new ArrayList<>();
|
||||||
|
String path = jarLocation.getPath();
|
||||||
|
URL[] urls = new URL[]{new URL("jar:file:" + path + "!/")};
|
||||||
|
|
||||||
|
URLClassLoader classLoader = URLClassLoader.newInstance(urls);
|
||||||
|
JarFile jarFolder = new JarFile(jarLocation);
|
||||||
|
Enumeration jarEntityList = jarFolder.entries();
|
||||||
|
|
||||||
|
while (jarEntityList.hasMoreElements()) {
|
||||||
|
JarEntry jarEntity = (JarEntry) jarEntityList.nextElement();
|
||||||
|
if (jarEntity.getName().endsWith(".class")) {
|
||||||
|
loadedClasses.add(classLoader.loadClass(jarEntity.getName().replace('/', '.').substring(0, jarEntity.getName().length() - 6)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loadedClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package org.nwapw.abacus.plugin;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that holds data about a number implementation.
|
||||||
|
*/
|
||||||
|
public abstract class NumberImplementation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of paths through which this implementation can be promoted.
|
||||||
|
*/
|
||||||
|
protected Map<Class<? extends NumberInterface>, Function<NumberInterface, NumberInterface>> promotionPaths;
|
||||||
|
/**
|
||||||
|
* The implementation class for this implementation.
|
||||||
|
*/
|
||||||
|
private Class<? extends NumberInterface> implementation;
|
||||||
|
/**
|
||||||
|
* The priority of converting into this number implementation.
|
||||||
|
*/
|
||||||
|
private int priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new number implementation with the given data.
|
||||||
|
*
|
||||||
|
* @param implementation the implementation class.
|
||||||
|
* @param priority the priority, higher means more likely to be converted into.
|
||||||
|
*/
|
||||||
|
public NumberImplementation(Class<? extends NumberInterface> implementation, int priority) {
|
||||||
|
this.implementation = implementation;
|
||||||
|
this.priority = priority;
|
||||||
|
promotionPaths = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of all promotion paths this implementation can take.
|
||||||
|
*
|
||||||
|
* @return the map of documentation paths.
|
||||||
|
*/
|
||||||
|
public final Map<Class<? extends NumberInterface>, Function<NumberInterface, NumberInterface>> getPromotionPaths() {
|
||||||
|
return promotionPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the implementation class used by this implementation.
|
||||||
|
*
|
||||||
|
* @return the implementation class.
|
||||||
|
*/
|
||||||
|
public final Class<? extends NumberInterface> getImplementation() {
|
||||||
|
return implementation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the priority of this number implementation.
|
||||||
|
*
|
||||||
|
* @return the priority.
|
||||||
|
*/
|
||||||
|
public final int getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract function to create a new instance from a string.
|
||||||
|
*
|
||||||
|
* @param string the string to create a number from.
|
||||||
|
* @return the resulting number.
|
||||||
|
*/
|
||||||
|
public abstract NumberInterface instanceForString(String string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the instance of pi with the given implementation.
|
||||||
|
*
|
||||||
|
* @return pi
|
||||||
|
*/
|
||||||
|
public abstract NumberInterface instanceForPi();
|
||||||
|
|
||||||
|
}
|
||||||
233
src/main/java/org/nwapw/abacus/plugin/Plugin.java
Normal file
233
src/main/java/org/nwapw/abacus/plugin/Plugin.java
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
package org.nwapw.abacus.plugin;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.function.Function;
|
||||||
|
import org.nwapw.abacus.function.Operator;
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
|
||||||
|
import java.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
|
||||||
|
* with the "hasFunction" and "getFunction" functions,
|
||||||
|
* and can use "registerFunction" and "functionFor" for
|
||||||
|
* loading internally.
|
||||||
|
*/
|
||||||
|
public abstract class Plugin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hash map of functions mapped to their string names.
|
||||||
|
*/
|
||||||
|
private Map<String, Function> functions;
|
||||||
|
/**
|
||||||
|
* A hash map of operators mapped to their string names.
|
||||||
|
*/
|
||||||
|
private Map<String, Operator> operators;
|
||||||
|
/**
|
||||||
|
* The map of the number implementations this plugin provides.
|
||||||
|
*/
|
||||||
|
private Map<String, NumberImplementation> numberImplementations;
|
||||||
|
/**
|
||||||
|
* The plugin manager in which to search for functions
|
||||||
|
* not inside this package,
|
||||||
|
*/
|
||||||
|
private PluginManager manager;
|
||||||
|
/**
|
||||||
|
* Whether this plugin has been loaded.
|
||||||
|
*/
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
private Plugin() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new plugin with the given PluginManager.
|
||||||
|
*
|
||||||
|
* @param manager the manager controlling this plugin.
|
||||||
|
*/
|
||||||
|
public Plugin(PluginManager manager) {
|
||||||
|
this.manager = manager;
|
||||||
|
functions = new HashMap<>();
|
||||||
|
operators = new HashMap<>();
|
||||||
|
numberImplementations = new HashMap<>();
|
||||||
|
enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of functions provided by this plugin.
|
||||||
|
*
|
||||||
|
* @return the list of registered functions.
|
||||||
|
*/
|
||||||
|
public final Set<String> providedFunctions() {
|
||||||
|
return functions.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of functions provided by this plugin.
|
||||||
|
*
|
||||||
|
* @return the list of registered functions.
|
||||||
|
*/
|
||||||
|
public final Set<String> providedOperators() {
|
||||||
|
return operators.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of number implementations provided by this plugin.
|
||||||
|
*
|
||||||
|
* @return the list of registered number implementations.
|
||||||
|
*/
|
||||||
|
public final Set<String> providedNumberImplementations() {
|
||||||
|
return numberImplementations.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a function under the given function name.
|
||||||
|
*
|
||||||
|
* @param functionName the name of the function to get
|
||||||
|
* @return the function, or null if this plugin doesn't provide it.
|
||||||
|
*/
|
||||||
|
public final Function getFunction(String functionName) {
|
||||||
|
return functions.get(functionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an operator under the given operator name.
|
||||||
|
*
|
||||||
|
* @param operatorName the name of the operator to get.
|
||||||
|
* @return the operator, or null if this plugin doesn't provide it.
|
||||||
|
*/
|
||||||
|
public final Operator getOperator(String operatorName) {
|
||||||
|
return operators.get(operatorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number implementation under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the number implementation to look up.
|
||||||
|
* @return the number implementation associated with that name, or null if the plugin doesn't provide it.
|
||||||
|
*/
|
||||||
|
public final NumberImplementation getNumberImplementation(String name) {
|
||||||
|
return numberImplementations.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables the function, loading the necessary instances
|
||||||
|
* of functions.
|
||||||
|
*/
|
||||||
|
public final void enable() {
|
||||||
|
if (enabled) return;
|
||||||
|
onEnable();
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the plugin, clearing loaded data store by default
|
||||||
|
* and calling its disable() method.
|
||||||
|
*/
|
||||||
|
public final void disable() {
|
||||||
|
if (!enabled) return;
|
||||||
|
onDisable();
|
||||||
|
functions.clear();
|
||||||
|
operators.clear();
|
||||||
|
enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be used in load(). Registers a 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 function implementation.
|
||||||
|
*/
|
||||||
|
protected final void registerFunction(String name, Function toRegister) {
|
||||||
|
functions.put(name, toRegister);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be used in load(). Registers an operator abstract class
|
||||||
|
* with the plugin internally, which makes it accessible to
|
||||||
|
* the plugin manager.
|
||||||
|
*
|
||||||
|
* @param name the name of the operator.
|
||||||
|
* @param operator the operator to register.
|
||||||
|
*/
|
||||||
|
protected final void registerOperator(String name, Operator operator) {
|
||||||
|
operators.put(name, operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be used in load(). Registers a new number implementation with the plugin.
|
||||||
|
* This makes it accessible to the plugin manager.
|
||||||
|
*
|
||||||
|
* @param name the name of the implementation.
|
||||||
|
* @param implementation the actual implementation class to register.
|
||||||
|
*/
|
||||||
|
protected final void registerNumberImplementation(String name, NumberImplementation implementation) {
|
||||||
|
numberImplementations.put(name, implementation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 function, or null if none was found for that name.
|
||||||
|
*/
|
||||||
|
protected final Function functionFor(String name) {
|
||||||
|
return manager.functionFor(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the PluginManager for the given 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 operator, or null if none was found for that name.
|
||||||
|
*/
|
||||||
|
protected final Operator operatorFor(String name) {
|
||||||
|
return manager.operatorFor(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the PluginManager for the given number implementation
|
||||||
|
* name. This can be used by the plugins internally in order to find
|
||||||
|
* implementations that they do not provide.
|
||||||
|
*
|
||||||
|
* @param name the name for which to search.
|
||||||
|
* @return the resulting number implementation, or null if none was found.
|
||||||
|
*/
|
||||||
|
protected final NumberImplementation numberImplementationFor(String name) {
|
||||||
|
return manager.numberImplementationFor(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the plugin manager for a Pi value for the given number implementation.
|
||||||
|
* This is done so that number implementations with various degrees of precision
|
||||||
|
* can provide their own pi values, without losing said precision by
|
||||||
|
* promoting NaiveNumbers.
|
||||||
|
*
|
||||||
|
* @param forClass the class to which to find the pi instance.
|
||||||
|
* @return the pi value for the given class.
|
||||||
|
*/
|
||||||
|
protected final NumberInterface getPi(Class<? extends NumberInterface> forClass) {
|
||||||
|
return manager.piFor(forClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract method to be overridden by plugin implementation, in which the plugins
|
||||||
|
* are supposed to register the functions they provide and do any other
|
||||||
|
* necessary setup.
|
||||||
|
*/
|
||||||
|
public abstract void onEnable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract method overridden by the plugin implementation, in which the plugins
|
||||||
|
* are supposed to dispose of loaded functions, operators, and macros.
|
||||||
|
*/
|
||||||
|
public abstract void onDisable();
|
||||||
|
|
||||||
|
}
|
||||||
22
src/main/java/org/nwapw/abacus/plugin/PluginListener.java
Normal file
22
src/main/java/org/nwapw/abacus/plugin/PluginListener.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package org.nwapw.abacus.plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener that responds to changes in the PluginManager.
|
||||||
|
*/
|
||||||
|
public interface PluginListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the PluginManager loads plugins.
|
||||||
|
*
|
||||||
|
* @param manager the manager that fired the event.
|
||||||
|
*/
|
||||||
|
public void onLoad(PluginManager manager);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the PluginManager unloads all its plugins.
|
||||||
|
*
|
||||||
|
* @param manager the manager that fired the event.
|
||||||
|
*/
|
||||||
|
public void onUnload(PluginManager manager);
|
||||||
|
|
||||||
|
}
|
||||||
322
src/main/java/org/nwapw/abacus/plugin/PluginManager.java
Normal file
322
src/main/java/org/nwapw/abacus/plugin/PluginManager.java
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
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.number.NumberInterface;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that controls instances of plugins, allowing for them
|
||||||
|
* to interact with each other and the calculator.
|
||||||
|
*/
|
||||||
|
public class PluginManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of classes loaded by this manager.
|
||||||
|
*/
|
||||||
|
private Set<Class<?>> loadedPluginClasses;
|
||||||
|
/**
|
||||||
|
* A list of loaded plugins.
|
||||||
|
*/
|
||||||
|
private Set<Plugin> plugins;
|
||||||
|
/**
|
||||||
|
* List of functions that have been cached,
|
||||||
|
* that is, found in a plugin and returned.
|
||||||
|
*/
|
||||||
|
private Map<String, Function> cachedFunctions;
|
||||||
|
/**
|
||||||
|
* List of operators that have been cached,
|
||||||
|
* that is, found in a plugin and returned.
|
||||||
|
*/
|
||||||
|
private Map<String, Operator> cachedOperators;
|
||||||
|
/**
|
||||||
|
* The list of number implementations that have
|
||||||
|
* been cached, that is, found in a plugin and returned.
|
||||||
|
*/
|
||||||
|
private Map<String, NumberImplementation> cachedNumberImplementations;
|
||||||
|
/**
|
||||||
|
* The list of number implementations that have been
|
||||||
|
* found by their implementation class.
|
||||||
|
*/
|
||||||
|
private Map<Class<? extends NumberInterface>, NumberImplementation> cachedInterfaceImplementations;
|
||||||
|
/**
|
||||||
|
* The pi values for each implementation class that have already been computer.
|
||||||
|
*/
|
||||||
|
private Map<Class<? extends NumberInterface>, NumberInterface> cachedPi;
|
||||||
|
/**
|
||||||
|
* List of all functions loaded by the plugins.
|
||||||
|
*/
|
||||||
|
private Set<String> allFunctions;
|
||||||
|
/**
|
||||||
|
* List of all operators loaded by the plugins.
|
||||||
|
*/
|
||||||
|
private Set<String> allOperators;
|
||||||
|
/**
|
||||||
|
* List of all the number implementations loaded by the plugins.
|
||||||
|
*/
|
||||||
|
private Set<String> allNumberImplementations;
|
||||||
|
/**
|
||||||
|
* The list of plugin listeners attached to this instance.
|
||||||
|
*/
|
||||||
|
private Set<PluginListener> listeners;
|
||||||
|
/**
|
||||||
|
* The abacus instance used to access other
|
||||||
|
* components of the application.
|
||||||
|
*/
|
||||||
|
private Abacus abacus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new plugin manager.
|
||||||
|
*
|
||||||
|
* @param abacus the abacus instance.
|
||||||
|
*/
|
||||||
|
public PluginManager(Abacus abacus) {
|
||||||
|
this.abacus = abacus;
|
||||||
|
loadedPluginClasses = new HashSet<>();
|
||||||
|
plugins = new HashSet<>();
|
||||||
|
cachedFunctions = new HashMap<>();
|
||||||
|
cachedOperators = new HashMap<>();
|
||||||
|
cachedNumberImplementations = new HashMap<>();
|
||||||
|
cachedInterfaceImplementations = new HashMap<>();
|
||||||
|
cachedPi = new HashMap<>();
|
||||||
|
allFunctions = new HashSet<>();
|
||||||
|
allOperators = new HashSet<>();
|
||||||
|
allNumberImplementations = new HashSet<>();
|
||||||
|
listeners = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the plugin list for a certain value, retrieving the Plugin's
|
||||||
|
* list of items of the type using the setFunction and getting the value
|
||||||
|
* of it is available via getFunction. If the value is contained
|
||||||
|
* in the cache, it returns the cached value instead.
|
||||||
|
*
|
||||||
|
* @param plugins the plugin list to search.
|
||||||
|
* @param cache the cache to use
|
||||||
|
* @param setFunction the function to retrieve a set of available T's from the plugin
|
||||||
|
* @param getFunction the function to get the T value under the given name
|
||||||
|
* @param name the name to search for
|
||||||
|
* @param <T> the type of element being search
|
||||||
|
* @param <K> the type of key that the cache is indexed by.
|
||||||
|
* @return the retrieved element, or null if it was not found.
|
||||||
|
*/
|
||||||
|
private static <T, K> T searchCached(Collection<Plugin> plugins, Map<K, T> cache,
|
||||||
|
java.util.function.Function<Plugin, Set<K>> setFunction,
|
||||||
|
java.util.function.BiFunction<Plugin, K, T> getFunction,
|
||||||
|
K name) {
|
||||||
|
if (cache.containsKey(name)) return cache.get(name);
|
||||||
|
|
||||||
|
T loadedValue = null;
|
||||||
|
for (Plugin plugin : plugins) {
|
||||||
|
if (setFunction.apply(plugin).contains(name)) {
|
||||||
|
loadedValue = getFunction.apply(plugin, name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.put(name, loadedValue);
|
||||||
|
return loadedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a function under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the function
|
||||||
|
* @return the function under the given name.
|
||||||
|
*/
|
||||||
|
public Function functionFor(String name) {
|
||||||
|
return searchCached(plugins, cachedFunctions, Plugin::providedFunctions, Plugin::getFunction, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an operator under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the operator.
|
||||||
|
* @return the operator under the given name.
|
||||||
|
*/
|
||||||
|
public Operator operatorFor(String name) {
|
||||||
|
return searchCached(plugins, cachedOperators, Plugin::providedOperators, Plugin::getOperator, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number implementation under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the implementation.
|
||||||
|
* @return the implementation.
|
||||||
|
*/
|
||||||
|
public NumberImplementation numberImplementationFor(String name) {
|
||||||
|
return searchCached(plugins, cachedNumberImplementations, Plugin::providedNumberImplementations,
|
||||||
|
Plugin::getNumberImplementation, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number implementation for the given implementation class.
|
||||||
|
*
|
||||||
|
* @param name the class for which to find the implementation.
|
||||||
|
* @return the implementation.
|
||||||
|
*/
|
||||||
|
public NumberImplementation interfaceImplementationFor(Class<? extends NumberInterface> name) {
|
||||||
|
if (cachedInterfaceImplementations.containsKey(name)) return cachedInterfaceImplementations.get(name);
|
||||||
|
NumberImplementation toReturn = null;
|
||||||
|
outside:
|
||||||
|
for (Plugin plugin : plugins) {
|
||||||
|
for (String implementationName : plugin.providedNumberImplementations()) {
|
||||||
|
NumberImplementation implementation = plugin.getNumberImplementation(implementationName);
|
||||||
|
if (implementation.getImplementation().equals(name)) {
|
||||||
|
toReturn = implementation;
|
||||||
|
break outside;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cachedInterfaceImplementations.put(name, toReturn);
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the mathematical constant pi for the given implementation class.
|
||||||
|
*
|
||||||
|
* @param forClass the class for which to find pi.
|
||||||
|
* @return pi
|
||||||
|
*/
|
||||||
|
public NumberInterface piFor(Class<? extends NumberInterface> forClass) {
|
||||||
|
if (cachedPi.containsKey(forClass)) return cachedPi.get(forClass);
|
||||||
|
NumberImplementation implementation = interfaceImplementationFor(forClass);
|
||||||
|
NumberInterface generatedPi = null;
|
||||||
|
if (implementation != null) {
|
||||||
|
generatedPi = implementation.instanceForPi();
|
||||||
|
}
|
||||||
|
cachedPi.put(forClass, generatedPi);
|
||||||
|
return generatedPi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an instance of Plugin that already has been instantiated.
|
||||||
|
*
|
||||||
|
* @param plugin the plugin to add.
|
||||||
|
*/
|
||||||
|
public void addInstantiated(Plugin plugin) {
|
||||||
|
if (loadedPluginClasses.contains(plugin.getClass())) return;
|
||||||
|
plugins.add(plugin);
|
||||||
|
loadedPluginClasses.add(plugin.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a class of plugin, and adds it to this
|
||||||
|
* plugin manager.
|
||||||
|
*
|
||||||
|
* @param newClass the new class to instantiate.
|
||||||
|
*/
|
||||||
|
public void addClass(Class<?> newClass) {
|
||||||
|
if (!Plugin.class.isAssignableFrom(newClass) || newClass == Plugin.class) return;
|
||||||
|
try {
|
||||||
|
addInstantiated((Plugin) newClass.getConstructor(PluginManager.class).newInstance(this));
|
||||||
|
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all the plugins in the PluginManager.
|
||||||
|
*/
|
||||||
|
public void load() {
|
||||||
|
Set<String> disabledPlugins = abacus.getConfiguration().getDisabledPlugins();
|
||||||
|
for (Plugin plugin : plugins) {
|
||||||
|
if (disabledPlugins.contains(plugin.getClass().getName())) continue;
|
||||||
|
plugin.enable();
|
||||||
|
}
|
||||||
|
for (Plugin plugin : plugins) {
|
||||||
|
if (disabledPlugins.contains(plugin.getClass().getName())) continue;
|
||||||
|
allFunctions.addAll(plugin.providedFunctions());
|
||||||
|
allOperators.addAll(plugin.providedOperators());
|
||||||
|
allNumberImplementations.addAll(plugin.providedNumberImplementations());
|
||||||
|
}
|
||||||
|
listeners.forEach(e -> e.onLoad(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unloads all the plugins in the PluginManager.
|
||||||
|
*/
|
||||||
|
public void unload() {
|
||||||
|
listeners.forEach(e -> e.onUnload(this));
|
||||||
|
Set<String> disabledPlugins = abacus.getConfiguration().getDisabledPlugins();
|
||||||
|
for (Plugin plugin : plugins) {
|
||||||
|
if (disabledPlugins.contains(plugin.getClass().getName())) continue;
|
||||||
|
plugin.disable();
|
||||||
|
}
|
||||||
|
cachedFunctions.clear();
|
||||||
|
cachedOperators.clear();
|
||||||
|
cachedNumberImplementations.clear();
|
||||||
|
cachedInterfaceImplementations.clear();
|
||||||
|
cachedPi.clear();
|
||||||
|
allFunctions.clear();
|
||||||
|
allOperators.clear();
|
||||||
|
allNumberImplementations.clear();
|
||||||
|
listeners.forEach(e -> e.onUnload(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads all the plugins in the PluginManager.
|
||||||
|
*/
|
||||||
|
public void reload() {
|
||||||
|
unload();
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the functions loaded by the Plugin Manager.
|
||||||
|
*
|
||||||
|
* @return the set of all functions that were loaded.
|
||||||
|
*/
|
||||||
|
public Set<String> getAllFunctions() {
|
||||||
|
return allFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the operators loaded by the Plugin Manager.
|
||||||
|
*
|
||||||
|
* @return the set of all operators that were loaded.
|
||||||
|
*/
|
||||||
|
public Set<String> getAllOperators() {
|
||||||
|
return allOperators;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the number implementations loaded by the Plugin Manager.
|
||||||
|
*
|
||||||
|
* @return the set of all implementations that were loaded.
|
||||||
|
*/
|
||||||
|
public Set<String> getAllNumberImplementations() {
|
||||||
|
return allNumberImplementations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a plugin change listener to this plugin manager.
|
||||||
|
*
|
||||||
|
* @param listener the listener to add.
|
||||||
|
*/
|
||||||
|
public void addListener(PluginListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the plugin change listener from this plugin manager.
|
||||||
|
*
|
||||||
|
* @param listener the listener to remove.
|
||||||
|
*/
|
||||||
|
public void removeListener(PluginListener listener) {
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of all the plugin class files that have been
|
||||||
|
* added to the plugin manager.
|
||||||
|
*
|
||||||
|
* @return the list of all the added plugin classes.
|
||||||
|
*/
|
||||||
|
public Set<Class<?>> getLoadedPluginClasses() {
|
||||||
|
return loadedPluginClasses;
|
||||||
|
}
|
||||||
|
}
|
||||||
584
src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java
Executable file
584
src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java
Executable file
@@ -0,0 +1,584 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NumberInterface applyInternal(NumberInterface[] params) {
|
||||||
|
if (params[0].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0)
|
||||||
|
return NaiveNumber.ZERO.promoteTo(params[0].getClass());
|
||||||
|
else if (params[1].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0)
|
||||||
|
return NaiveNumber.ONE.promoteTo(params[1].getClass());
|
||||||
|
return FUNCTION_EXP.apply(FUNCTION_LN.apply(FUNCTION_ABS.apply(params[0])).multiply(params[1]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* The sine function (the argument is interpreted in radians).
|
||||||
|
*/
|
||||||
|
public final Function functionSin = new Function() {
|
||||||
|
@Override
|
||||||
|
protected boolean matchesParams(NumberInterface[] params) {
|
||||||
|
return params.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NumberInterface applyInternal(NumberInterface[] params) {
|
||||||
|
NumberInterface pi = getPi(params[0].getClass());
|
||||||
|
NumberInterface twoPi = pi.multiply(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(getPi(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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
108
src/main/java/org/nwapw/abacus/tree/BinaryNode.java
Normal file
108
src/main/java/org/nwapw/abacus/tree/BinaryNode.java
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package org.nwapw.abacus.tree;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tree node that represents an operation being applied to two operands.
|
||||||
|
*/
|
||||||
|
public class BinaryNode extends TreeNode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The operation being applied.
|
||||||
|
*/
|
||||||
|
private String operation;
|
||||||
|
/**
|
||||||
|
* The left node of the operation.
|
||||||
|
*/
|
||||||
|
private TreeNode left;
|
||||||
|
/**
|
||||||
|
* The right node of the operation.
|
||||||
|
*/
|
||||||
|
private TreeNode right;
|
||||||
|
|
||||||
|
private BinaryNode() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new operation node with the given operation
|
||||||
|
* and no child nodes.
|
||||||
|
*
|
||||||
|
* @param operation the operation.
|
||||||
|
*/
|
||||||
|
public BinaryNode(String operation) {
|
||||||
|
this(operation, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new operation node with the given operation
|
||||||
|
* and child nodes.
|
||||||
|
*
|
||||||
|
* @param operation the operation.
|
||||||
|
* @param left the left node of the expression.
|
||||||
|
* @param right the right node of the expression.
|
||||||
|
*/
|
||||||
|
public BinaryNode(String operation, TreeNode left, TreeNode right) {
|
||||||
|
this.operation = operation;
|
||||||
|
this.left = left;
|
||||||
|
this.right = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the operation in this node.
|
||||||
|
*
|
||||||
|
* @return the operation in this node.
|
||||||
|
*/
|
||||||
|
public String getOperation() {
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the left sub-expression of this node.
|
||||||
|
*
|
||||||
|
* @return the left node.
|
||||||
|
*/
|
||||||
|
public TreeNode getLeft() {
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the left sub-expression of this node.
|
||||||
|
*
|
||||||
|
* @param left the sub-expression to apply.
|
||||||
|
*/
|
||||||
|
public void setLeft(TreeNode left) {
|
||||||
|
this.left = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the right sub-expression of this node.
|
||||||
|
*
|
||||||
|
* @return the right node.
|
||||||
|
*/
|
||||||
|
public TreeNode getRight() {
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the right sub-expression of this node.
|
||||||
|
*
|
||||||
|
* @param right the sub-expression to apply.
|
||||||
|
*/
|
||||||
|
public void setRight(TreeNode right) {
|
||||||
|
this.right = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T reduce(Reducer<T> reducer) {
|
||||||
|
T leftReduce = left.reduce(reducer);
|
||||||
|
T rightReduce = right.reduce(reducer);
|
||||||
|
if (leftReduce == null || rightReduce == null) return null;
|
||||||
|
return reducer.reduceNode(this, leftReduce, rightReduce);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String leftString = left != null ? left.toString() : "null";
|
||||||
|
String rightString = right != null ? right.toString() : "null";
|
||||||
|
|
||||||
|
return "(" + leftString + operation + rightString + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/main/java/org/nwapw/abacus/tree/FunctionNode.java
Normal file
85
src/main/java/org/nwapw/abacus/tree/FunctionNode.java
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package org.nwapw.abacus.tree;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node that represents a function call.
|
||||||
|
*/
|
||||||
|
public class FunctionNode extends TreeNode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the function being called
|
||||||
|
*/
|
||||||
|
private String function;
|
||||||
|
/**
|
||||||
|
* The list of arguments to the function.
|
||||||
|
*/
|
||||||
|
private List<TreeNode> children;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a function node with no function.
|
||||||
|
*/
|
||||||
|
private FunctionNode() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new function node with the given function name.
|
||||||
|
*
|
||||||
|
* @param function the function name.
|
||||||
|
*/
|
||||||
|
public FunctionNode(String function) {
|
||||||
|
this.function = function;
|
||||||
|
children = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the function name for this node.
|
||||||
|
*
|
||||||
|
* @return the function name.
|
||||||
|
*/
|
||||||
|
public String getFunction() {
|
||||||
|
return function;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a child to the end of this node's child list.
|
||||||
|
*
|
||||||
|
* @param node the child to add.
|
||||||
|
*/
|
||||||
|
public void appendChild(TreeNode node) {
|
||||||
|
children.add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new child to the beginning of this node's child list.
|
||||||
|
*
|
||||||
|
* @param node the node to add.
|
||||||
|
*/
|
||||||
|
public void prependChild(TreeNode node) {
|
||||||
|
children.add(0, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T reduce(Reducer<T> reducer) {
|
||||||
|
Object[] reducedChildren = new Object[children.size()];
|
||||||
|
for (int i = 0; i < reducedChildren.length; i++) {
|
||||||
|
reducedChildren[i] = children.get(i).reduce(reducer);
|
||||||
|
if (reducedChildren[i] == null) return null;
|
||||||
|
}
|
||||||
|
return reducer.reduceNode(this, reducedChildren);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
buffer.append(function);
|
||||||
|
buffer.append("(");
|
||||||
|
for (int i = 0; i < children.size(); i++) {
|
||||||
|
buffer.append(children.get(i));
|
||||||
|
buffer.append(i == children.size() - 1 ? "" : ", ");
|
||||||
|
}
|
||||||
|
buffer.append(")");
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/main/java/org/nwapw/abacus/tree/NumberNode.java
Normal file
49
src/main/java/org/nwapw/abacus/tree/NumberNode.java
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package org.nwapw.abacus.tree;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node implementation that represents a single number.
|
||||||
|
*/
|
||||||
|
public class NumberNode extends TreeNode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number that is represented by this number node.
|
||||||
|
*/
|
||||||
|
private NumberInterface number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a number node with no number.
|
||||||
|
*/
|
||||||
|
public NumberNode() {
|
||||||
|
number = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new number node with the given double value.
|
||||||
|
*
|
||||||
|
* @param newNumber the number for which to create a number node.
|
||||||
|
*/
|
||||||
|
public NumberNode(NumberInterface newNumber) {
|
||||||
|
this.number = newNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number value of this node.
|
||||||
|
*
|
||||||
|
* @return the number value of this node.
|
||||||
|
*/
|
||||||
|
public NumberInterface getNumber() {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T reduce(Reducer<T> reducer) {
|
||||||
|
return reducer.reduceNode(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return number != null ? number.toString() : "null";
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/main/java/org/nwapw/abacus/tree/NumberReducer.java
Normal file
54
src/main/java/org/nwapw/abacus/tree/NumberReducer.java
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
19
src/main/java/org/nwapw/abacus/tree/Reducer.java
Normal file
19
src/main/java/org/nwapw/abacus/tree/Reducer.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
26
src/main/java/org/nwapw/abacus/tree/TokenType.java
Normal file
26
src/main/java/org/nwapw/abacus/tree/TokenType.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package org.nwapw.abacus.tree;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum to represent the type of the token that has been matched
|
||||||
|
* by the lexer.
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The priority by which this token gets sorted.
|
||||||
|
*/
|
||||||
|
public final int priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new token type with the given priority.
|
||||||
|
*
|
||||||
|
* @param priority the priority of this token type.
|
||||||
|
*/
|
||||||
|
TokenType(int priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
17
src/main/java/org/nwapw/abacus/tree/TreeNode.java
Normal file
17
src/main/java/org/nwapw/abacus/tree/TreeNode.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package org.nwapw.abacus.tree;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract class that represents an expression tree node.
|
||||||
|
*/
|
||||||
|
public abstract class TreeNode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function that reduces a tree to a single vale.
|
||||||
|
*
|
||||||
|
* @param reducer the reducer used to reduce the tree.
|
||||||
|
* @param <T> the type the reducer produces.
|
||||||
|
* @return the result of the reduction, or null on error.
|
||||||
|
*/
|
||||||
|
public abstract <T> T reduce(Reducer<T> reducer);
|
||||||
|
|
||||||
|
}
|
||||||
63
src/main/java/org/nwapw/abacus/tree/UnaryNode.java
Normal file
63
src/main/java/org/nwapw/abacus/tree/UnaryNode.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package org.nwapw.abacus.tree;
|
||||||
|
|
||||||
|
public class UnaryNode extends TreeNode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The operation this node will apply.
|
||||||
|
*/
|
||||||
|
private String operation;
|
||||||
|
/**
|
||||||
|
* The tree node to apply the operation to.
|
||||||
|
*/
|
||||||
|
private TreeNode applyTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new node with the given operation and no child.
|
||||||
|
*
|
||||||
|
* @param operation the operation for this node.
|
||||||
|
*/
|
||||||
|
public UnaryNode(String operation) {
|
||||||
|
this(operation, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new node with the given operation and child.
|
||||||
|
*
|
||||||
|
* @param operation the operation for this node.
|
||||||
|
* @param applyTo the node to apply the function to.
|
||||||
|
*/
|
||||||
|
public UnaryNode(String operation, TreeNode applyTo) {
|
||||||
|
this.operation = operation;
|
||||||
|
this.applyTo = applyTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T reduce(Reducer<T> reducer) {
|
||||||
|
Object reducedChild = applyTo.reduce(reducer);
|
||||||
|
if (reducedChild == null) return null;
|
||||||
|
return reducer.reduceNode(this, reducedChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the operation of this node.
|
||||||
|
*
|
||||||
|
* @return the operation this node performs.
|
||||||
|
*/
|
||||||
|
public String getOperation() {
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the node to which this node's operation applies.
|
||||||
|
*
|
||||||
|
* @return the tree node to which the operation will be applied.
|
||||||
|
*/
|
||||||
|
public TreeNode getApplyTo() {
|
||||||
|
return applyTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(" + (applyTo == null ? "null" : applyTo.toString()) + ")" + operation;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/main/java/org/nwapw/abacus/window/HistoryTableModel.java
Normal file
106
src/main/java/org/nwapw/abacus/window/HistoryTableModel.java
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
255
src/main/java/org/nwapw/abacus/window/Window.java
Normal file
255
src/main/java/org/nwapw/abacus/window/Window.java
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/main/resources/abacus.fxml
Normal file
64
src/main/resources/abacus.fxml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.text.Text?>
|
||||||
|
<BorderPane xmlns:fx="http://javafx.com/fxml"
|
||||||
|
xmlns="http://javafx.com/javafx"
|
||||||
|
fx:controller="org.nwapw.abacus.fx.AbacusController">
|
||||||
|
<center>
|
||||||
|
<TabPane fx:id="coreTabPane">
|
||||||
|
<Tab fx:id="calculateTab" text="Calculator" closable="false">
|
||||||
|
<BorderPane>
|
||||||
|
<center>
|
||||||
|
<TableView fx:id="historyTable">
|
||||||
|
<columnResizePolicy>
|
||||||
|
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
|
||||||
|
</columnResizePolicy>
|
||||||
|
<columns>
|
||||||
|
<TableColumn fx:id="inputColumn" text="Input" sortable="false"/>
|
||||||
|
<TableColumn fx:id="parsedColumn" text="Parsed" sortable="false"/>
|
||||||
|
<TableColumn fx:id="outputColumn" text="Output" sortable="false"/>
|
||||||
|
</columns>
|
||||||
|
</TableView>
|
||||||
|
</center>
|
||||||
|
<bottom>
|
||||||
|
<VBox>
|
||||||
|
<ScrollPane prefHeight="50" vbarPolicy="NEVER">
|
||||||
|
<padding>
|
||||||
|
<Insets top="10" bottom="10" left="10" right="10"/>
|
||||||
|
</padding>
|
||||||
|
<Text fx:id="outputText"/>
|
||||||
|
</ScrollPane>
|
||||||
|
<TextField fx:id="inputField" onAction="#performCalculation"/>
|
||||||
|
<Button fx:id="inputButton" text="Calculate" maxWidth="Infinity"
|
||||||
|
onAction="#performCalculation"/>
|
||||||
|
<Button fx:id="stopButton" text="Stop" maxWidth="Infinity"
|
||||||
|
onAction="#performStop" disable="true"/>
|
||||||
|
</VBox>
|
||||||
|
</bottom>
|
||||||
|
</BorderPane>
|
||||||
|
</Tab>
|
||||||
|
<Tab fx:id="settingsTab" text="Settings" closable="false">
|
||||||
|
<GridPane hgap="10" vgap="10">
|
||||||
|
<padding>
|
||||||
|
<Insets left="10" right="10" top="10" bottom="10"/>
|
||||||
|
</padding>
|
||||||
|
<Label text="Number Implementation" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
|
||||||
|
<ComboBox fx:id="numberImplementationBox" GridPane.columnIndex="1" GridPane.rowIndex="0"/>
|
||||||
|
<ListView fx:id="enabledPluginView"
|
||||||
|
GridPane.rowIndex="1" GridPane.columnIndex="0"
|
||||||
|
GridPane.columnSpan="2" maxHeight="100"/>
|
||||||
|
<FlowPane GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="2" hgap="10"
|
||||||
|
vgap="10">
|
||||||
|
<Button text="Apply" onAction="#performSave"/>
|
||||||
|
<Button text="Reload Plugins" onAction="#performReload"/>
|
||||||
|
<Button text="Apply and Reload" onAction="#performSaveAndReload"/>
|
||||||
|
</FlowPane>
|
||||||
|
</GridPane>
|
||||||
|
</Tab>
|
||||||
|
</TabPane>
|
||||||
|
</center>
|
||||||
|
|
||||||
|
</BorderPane>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package org.nwapw.abacus;
|
|
||||||
|
|
||||||
public class Abacus {
|
|
||||||
|
|
||||||
public static void main(String[] args){
|
|
||||||
System.out.println("Hello world!");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package org.nwapw.abacus.lexing;
|
|
||||||
|
|
||||||
import org.nwapw.abacus.lexing.pattern.EndNode;
|
|
||||||
import org.nwapw.abacus.lexing.pattern.Match;
|
|
||||||
import org.nwapw.abacus.lexing.pattern.Pattern;
|
|
||||||
import org.nwapw.abacus.lexing.pattern.PatternNode;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
public class Lexer<T> {
|
|
||||||
|
|
||||||
private ArrayList<Pattern<T>> patterns;
|
|
||||||
|
|
||||||
public Lexer(){
|
|
||||||
patterns = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void register(String pattern, T id){
|
|
||||||
Pattern<T> compiledPattern = new Pattern<>(pattern, id);
|
|
||||||
if(compiledPattern.getHead() != null) patterns.add(compiledPattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Match<T> lexOne(String from, int startAt, Comparator<T> compare){
|
|
||||||
ArrayList<Match<T>> matches = new ArrayList<>();
|
|
||||||
HashSet<PatternNode<T>> currentSet = new HashSet<>();
|
|
||||||
HashSet<PatternNode<T>> futureSet = new HashSet<>();
|
|
||||||
int index = startAt;
|
|
||||||
for(Pattern<T> pattern : patterns){
|
|
||||||
pattern.getHead().addInto(currentSet);
|
|
||||||
}
|
|
||||||
while(!currentSet.isEmpty()){
|
|
||||||
for(PatternNode<T> node : currentSet){
|
|
||||||
if(index < from.length() && node.matches(from.charAt(index))) {
|
|
||||||
node.addOutputsInto(futureSet);
|
|
||||||
} else if(node instanceof EndNode){
|
|
||||||
matches.add(new Match<>(startAt, index, ((EndNode<T>) node).getPatternId()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HashSet<PatternNode<T>> tmp = currentSet;
|
|
||||||
currentSet = futureSet;
|
|
||||||
futureSet = tmp;
|
|
||||||
futureSet.clear();
|
|
||||||
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
matches.sort((a, b) -> compare.compare(a.getType(), b.getType()));
|
|
||||||
if(compare != null) {
|
|
||||||
matches.sort(Comparator.comparingInt(a -> a.getTo() - a.getFrom()));
|
|
||||||
}
|
|
||||||
return matches.isEmpty() ? null : matches.get(matches.size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<Match<T>> lexAll(String from, int startAt, Comparator<T> compare){
|
|
||||||
int index = startAt;
|
|
||||||
ArrayList<Match<T>> matches = new ArrayList<>();
|
|
||||||
Match<T> lastMatch = null;
|
|
||||||
while((lastMatch = lexOne(from, index, compare)) != null && index < from.length()){
|
|
||||||
if(lastMatch.getTo() == lastMatch.getFrom()) return null;
|
|
||||||
matches.add(lastMatch);
|
|
||||||
index += lastMatch.getTo() - lastMatch.getFrom();
|
|
||||||
}
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package org.nwapw.abacus.lexing.pattern;
|
|
||||||
|
|
||||||
public class EndNode<T> extends PatternNode<T> {
|
|
||||||
|
|
||||||
private T patternId;
|
|
||||||
|
|
||||||
public EndNode(T patternId){
|
|
||||||
this.patternId = patternId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getPatternId(){
|
|
||||||
return patternId;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package org.nwapw.abacus.lexing.pattern;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public class LinkNode<T> extends PatternNode<T> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addInto(Collection<PatternNode<T>> into) {
|
|
||||||
addOutputsInto(into);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package org.nwapw.abacus.lexing.pattern;
|
|
||||||
|
|
||||||
public class Match<T> {
|
|
||||||
|
|
||||||
private int from;
|
|
||||||
private int to;
|
|
||||||
private T type;
|
|
||||||
|
|
||||||
public Match(int from, int to, T type){
|
|
||||||
this.from = from;
|
|
||||||
this.to = to;
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getFrom() {
|
|
||||||
return from;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTo() {
|
|
||||||
return to;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
package org.nwapw.abacus.lexing.pattern;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Stack;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public class Pattern<T> {
|
|
||||||
|
|
||||||
private T id;
|
|
||||||
private PatternNode<T> head;
|
|
||||||
private String source;
|
|
||||||
private int index;
|
|
||||||
|
|
||||||
private HashMap<Character, Function<PatternChain<T>, PatternChain<T>>> operations =
|
|
||||||
new HashMap<Character, Function<PatternChain<T>, PatternChain<T>>>() {{
|
|
||||||
put('+', Pattern.this::transformPlus);
|
|
||||||
put('*', Pattern.this::transformStar);
|
|
||||||
put('?', Pattern.this::transformQuestion);
|
|
||||||
}};
|
|
||||||
|
|
||||||
private PatternChain<T> transformPlus(PatternChain<T> chain){
|
|
||||||
chain.tail.outputStates.add(chain.head);
|
|
||||||
return chain;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PatternChain<T> transformStar(PatternChain<T> chain){
|
|
||||||
LinkNode<T> newTail = new LinkNode<>();
|
|
||||||
LinkNode<T> newHead = new LinkNode<>();
|
|
||||||
newHead.outputStates.add(chain.head);
|
|
||||||
newHead.outputStates.add(newTail);
|
|
||||||
chain.tail.outputStates.add(newTail);
|
|
||||||
newTail.outputStates.add(newHead);
|
|
||||||
chain.head = newHead;
|
|
||||||
chain.tail = newTail;
|
|
||||||
return chain;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PatternChain<T> transformQuestion(PatternChain<T> chain){
|
|
||||||
LinkNode<T> newTail = new LinkNode<>();
|
|
||||||
LinkNode<T> newHead = new LinkNode<>();
|
|
||||||
newHead.outputStates.add(chain.head);
|
|
||||||
newHead.outputStates.add(newTail);
|
|
||||||
chain.tail.outputStates.add(newTail);
|
|
||||||
chain.head = newHead;
|
|
||||||
chain.tail = newTail;
|
|
||||||
return chain;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PatternChain<T> combineChains(Collection<PatternChain<T>> collection){
|
|
||||||
LinkNode<T> head = new LinkNode<>();
|
|
||||||
LinkNode<T> tail = new LinkNode<>();
|
|
||||||
PatternChain<T> newChain = new PatternChain<>(head, tail);
|
|
||||||
for(PatternChain<T> chain : collection){
|
|
||||||
head.outputStates.add(chain.head);
|
|
||||||
chain.tail.outputStates.add(tail);
|
|
||||||
}
|
|
||||||
return newChain;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PatternChain<T> parseValue(){
|
|
||||||
if(index >= source.length()) return null;
|
|
||||||
if(source.charAt(index) == '\\'){
|
|
||||||
if(++index >= source.length()) return null;
|
|
||||||
}
|
|
||||||
return new PatternChain<>(new ValueNode<>(source.charAt(index++)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private PatternChain<T> parseOr(){
|
|
||||||
Stack<PatternChain<T>> orStack = new Stack<>();
|
|
||||||
index++;
|
|
||||||
while(index < source.length() && source.charAt(index) != ']'){
|
|
||||||
if(source.charAt(index) == '-'){
|
|
||||||
index++;
|
|
||||||
if(orStack.empty() || orStack.peek().tail.range() == '\0') return null;
|
|
||||||
PatternChain<T> bottomRange = orStack.pop();
|
|
||||||
PatternChain<T> topRange = parseValue();
|
|
||||||
if(topRange == null || topRange.tail.range() == '\0') return null;
|
|
||||||
|
|
||||||
orStack.push(new PatternChain<>(new RangeNode<>(bottomRange.tail.range(), topRange.tail.range())));
|
|
||||||
} else {
|
|
||||||
PatternChain<T> newChain = parseValue();
|
|
||||||
if(newChain == null) return null;
|
|
||||||
orStack.push(newChain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(index++ >= source.length()) return null;
|
|
||||||
return (orStack.size() == 1) ? orStack.pop() : combineChains(orStack);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PatternChain<T> parseSegment(boolean isSubsegment){
|
|
||||||
if(index >= source.length() || ((source.charAt(index) != '(') && isSubsegment)) return null;
|
|
||||||
if(isSubsegment) index++;
|
|
||||||
|
|
||||||
Stack<PatternChain<T>> orChain = new Stack<>();
|
|
||||||
PatternChain<T> fullChain = new PatternChain<>();
|
|
||||||
PatternChain<T> currentChain = null;
|
|
||||||
while (index < source.length() && source.charAt(index) != ')'){
|
|
||||||
char currentChar = source.charAt(index);
|
|
||||||
if(operations.containsKey(currentChar)){
|
|
||||||
if(currentChain == null) return null;
|
|
||||||
|
|
||||||
currentChain = operations.get(currentChar).apply(currentChain);
|
|
||||||
fullChain.append(currentChain);
|
|
||||||
currentChain = null;
|
|
||||||
index++;
|
|
||||||
} else if(currentChar == '|'){
|
|
||||||
if(currentChain == null) return null;
|
|
||||||
|
|
||||||
fullChain.append(currentChain);
|
|
||||||
orChain.push(fullChain);
|
|
||||||
currentChain = null;
|
|
||||||
fullChain = new PatternChain<>();
|
|
||||||
if(++index >= source.length()) return null;
|
|
||||||
} else if(currentChar == '('){
|
|
||||||
if(currentChain != null) {
|
|
||||||
fullChain.append(currentChain);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentChain = parseSegment(true);
|
|
||||||
if(currentChain == null) return null;
|
|
||||||
} else if(currentChar == '['){
|
|
||||||
if(currentChain != null){
|
|
||||||
fullChain.append(currentChain);
|
|
||||||
}
|
|
||||||
currentChain = parseOr();
|
|
||||||
if(currentChain == null) return null;
|
|
||||||
} else if(currentChar == '.'){
|
|
||||||
if(currentChain != null){
|
|
||||||
fullChain.append(currentChain);
|
|
||||||
}
|
|
||||||
currentChain = new PatternChain<>(new AnyNode<>());
|
|
||||||
index++;
|
|
||||||
} else {
|
|
||||||
if(currentChain != null){
|
|
||||||
fullChain.append(currentChain);
|
|
||||||
}
|
|
||||||
currentChain = parseValue();
|
|
||||||
if(currentChain == null) return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!(!isSubsegment || (index < source.length() && source.charAt(index) == ')'))) return null;
|
|
||||||
if(isSubsegment) index++;
|
|
||||||
|
|
||||||
if(currentChain != null) fullChain.append(currentChain);
|
|
||||||
if(!orChain.empty()){
|
|
||||||
orChain.push(fullChain);
|
|
||||||
fullChain = combineChains(orChain);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fullChain;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Pattern(String from, T id){
|
|
||||||
this.id = id;
|
|
||||||
index = 0;
|
|
||||||
source = from;
|
|
||||||
|
|
||||||
PatternChain<T> chain = parseSegment(false);
|
|
||||||
if(chain == null) {
|
|
||||||
head = null;
|
|
||||||
} else {
|
|
||||||
chain.append(new EndNode<>(id));
|
|
||||||
head = chain.head;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PatternNode<T> getHead() {
|
|
||||||
return head;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package org.nwapw.abacus.lexing.pattern;
|
|
||||||
|
|
||||||
public class PatternChain<T> {
|
|
||||||
|
|
||||||
public PatternNode<T> head;
|
|
||||||
public PatternNode<T> tail;
|
|
||||||
|
|
||||||
public PatternChain(PatternNode<T> head, PatternNode<T> tail){
|
|
||||||
this.head = head;
|
|
||||||
this.tail = tail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PatternChain(PatternNode<T> node){
|
|
||||||
this(node, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PatternChain(){
|
|
||||||
this(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void append(PatternChain<T> other){
|
|
||||||
if(other.head == null || tail == null) {
|
|
||||||
this.head = other.head;
|
|
||||||
this.tail = other.tail;
|
|
||||||
} else {
|
|
||||||
tail.outputStates.add(other.head);
|
|
||||||
tail = other.tail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void append(PatternNode<T> node){
|
|
||||||
if(tail == null){
|
|
||||||
head = tail = node;
|
|
||||||
} else {
|
|
||||||
tail.outputStates.add(node);
|
|
||||||
tail = node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package org.nwapw.abacus.lexing.pattern;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
public class PatternNode<T> {
|
|
||||||
|
|
||||||
protected HashSet<PatternNode<T>> outputStates;
|
|
||||||
|
|
||||||
public PatternNode(){
|
|
||||||
outputStates = new HashSet<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean matches(char other){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public char range(){
|
|
||||||
return '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addInto(Collection<PatternNode<T>> into){
|
|
||||||
into.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addOutputsInto(Collection<PatternNode<T>> into){
|
|
||||||
outputStates.forEach(e -> e.addInto(into));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package org.nwapw.abacus.lexing.pattern;
|
|
||||||
|
|
||||||
public class RangeNode<T> extends PatternNode<T> {
|
|
||||||
|
|
||||||
private char from;
|
|
||||||
private char to;
|
|
||||||
|
|
||||||
public RangeNode(char from, char to){
|
|
||||||
this.from = from;
|
|
||||||
this.to = to;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(char other) {
|
|
||||||
return other >= from && other <= to;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package org.nwapw.abacus.lexing.pattern;
|
|
||||||
|
|
||||||
public class ValueNode<T> extends PatternNode<T> {
|
|
||||||
|
|
||||||
private char value;
|
|
||||||
|
|
||||||
public ValueNode(char value){
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(char other) {
|
|
||||||
return other == value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char range() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package org.nwapw.abacus.number;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
public abstract class Function {
|
|
||||||
|
|
||||||
private static final HashMap<Class<? extends Number>, Integer> priorityMap =
|
|
||||||
new HashMap<Class<? extends Number>, Integer>() {{
|
|
||||||
put(NaiveNumber.class, 0);
|
|
||||||
}};
|
|
||||||
|
|
||||||
protected abstract boolean matchesParams(Number[] params);
|
|
||||||
protected abstract Number applyInternal(Number[] params);
|
|
||||||
|
|
||||||
public Number apply(Number...params) {
|
|
||||||
if(!matchesParams(params)) return null;
|
|
||||||
return applyInternal(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package org.nwapw.abacus.number;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
public class FunctionDatabase {
|
|
||||||
|
|
||||||
private HashMap<String, Function> functions;
|
|
||||||
|
|
||||||
private void registerDefault(){
|
|
||||||
|
|
||||||
functions.put("+", new Function() {
|
|
||||||
@Override
|
|
||||||
protected boolean matchesParams(Number[] params) {
|
|
||||||
return params.length >= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Number applyInternal(Number[] params) {
|
|
||||||
Number sum = params[0];
|
|
||||||
for(int i = 1; i < params.length; i++){
|
|
||||||
sum = sum.add(params[i]);
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
functions.put("-", new Function() {
|
|
||||||
@Override
|
|
||||||
protected boolean matchesParams(Number[] params) {
|
|
||||||
return params.length == 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Number applyInternal(Number[] params) {
|
|
||||||
return params[0].subtract(params[1]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
functions.put("*", new Function() {
|
|
||||||
@Override
|
|
||||||
protected boolean matchesParams(Number[] params) {
|
|
||||||
return params.length >= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Number applyInternal(Number[] params) {
|
|
||||||
Number product = params[1];
|
|
||||||
for(int i = 1; i < params.length; i++){
|
|
||||||
product = product.multiply(params[i]);
|
|
||||||
}
|
|
||||||
return product;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
functions.put("/", new Function() {
|
|
||||||
@Override
|
|
||||||
protected boolean matchesParams(Number[] params) {
|
|
||||||
return params.length == 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Number applyInternal(Number[] params) {
|
|
||||||
return params[0].divide(params[1]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public FunctionDatabase(){
|
|
||||||
functions = new HashMap<>();
|
|
||||||
registerDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Function getFunction(String name){
|
|
||||||
return functions.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package org.nwapw.abacus.number;
|
|
||||||
|
|
||||||
public class NaiveNumber implements Number {
|
|
||||||
|
|
||||||
private double value;
|
|
||||||
|
|
||||||
public NaiveNumber(double value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int precision() {
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Number multiply(Number multiplier) {
|
|
||||||
return new NaiveNumber(value * ((NaiveNumber)multiplier).value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Number divide(Number divisor) {
|
|
||||||
return new NaiveNumber(value / ((NaiveNumber)divisor).value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Number add(Number summand) {
|
|
||||||
return new NaiveNumber(value + ((NaiveNumber)summand).value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Number subtract(Number subtrahend) {
|
|
||||||
return new NaiveNumber(value - ((NaiveNumber)subtrahend).value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Number negate() {
|
|
||||||
return new NaiveNumber(-value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Number zero() {
|
|
||||||
return new NaiveNumber(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Number one() {
|
|
||||||
return new NaiveNumber(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Number promoteTo(Class<? extends Number> toClass) {
|
|
||||||
if(toClass == this.getClass()) return this;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package org.nwapw.abacus.number;
|
|
||||||
|
|
||||||
public interface Number {
|
|
||||||
|
|
||||||
int precision();
|
|
||||||
Number multiply(Number multiplier);
|
|
||||||
Number divide(Number divisor);
|
|
||||||
Number add(Number summand);
|
|
||||||
Number subtract(Number subtrahend);
|
|
||||||
Number negate();
|
|
||||||
|
|
||||||
Number zero();
|
|
||||||
Number one();
|
|
||||||
|
|
||||||
Number promoteTo(Class<? extends Number> toClass);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package org.nwapw.abacus.tree;
|
|
||||||
|
|
||||||
import org.nwapw.abacus.number.NaiveNumber;
|
|
||||||
import org.nwapw.abacus.number.Number;
|
|
||||||
|
|
||||||
public class NumberNode extends TreeNode {
|
|
||||||
|
|
||||||
private Number number;
|
|
||||||
|
|
||||||
public NumberNode(){
|
|
||||||
number = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NumberNode(double value){
|
|
||||||
number = new NaiveNumber(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NumberNode(String value){
|
|
||||||
this(Double.parseDouble(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Number getNumber() {
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package org.nwapw.abacus.tree;
|
|
||||||
|
|
||||||
public class OpNode extends TreeNode {
|
|
||||||
|
|
||||||
private String operation;
|
|
||||||
private TreeNode left;
|
|
||||||
private TreeNode right;
|
|
||||||
|
|
||||||
public OpNode(String operation){
|
|
||||||
this(operation, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpNode(String operation, TreeNode left, TreeNode right){
|
|
||||||
this.operation = operation;
|
|
||||||
this.left = left;
|
|
||||||
this.right = right;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOperation() {
|
|
||||||
return operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TreeNode getLeft() {
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLeft(TreeNode left) {
|
|
||||||
this.left = left;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TreeNode getRight() {
|
|
||||||
return right;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRight(TreeNode right) {
|
|
||||||
this.right = right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package org.nwapw.abacus.tree;
|
|
||||||
|
|
||||||
public enum OperatorAssociativity {
|
|
||||||
LEFT, RIGHT
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package org.nwapw.abacus.tree;
|
|
||||||
|
|
||||||
public enum TokenType {
|
|
||||||
|
|
||||||
ANY(0), OP(1), NUM(2), WORD(3), OPEN_PARENTH(4), CLOSE_PARENTH(5);
|
|
||||||
|
|
||||||
public final int priority;
|
|
||||||
|
|
||||||
TokenType(int priority){
|
|
||||||
this.priority = priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
package org.nwapw.abacus.tree;
|
|
||||||
|
|
||||||
import org.nwapw.abacus.lexing.Lexer;
|
|
||||||
import org.nwapw.abacus.lexing.pattern.Match;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public abstract class TreeNode {
|
|
||||||
|
|
||||||
private static Lexer<TokenType> lexer = new Lexer<TokenType>(){{
|
|
||||||
register(".", TokenType.ANY);
|
|
||||||
register("\\+|-|\\*|/|^", TokenType.OP);
|
|
||||||
register("[0-9]+(\\.[0-9]+)?", TokenType.NUM);
|
|
||||||
register("[a-zA-Z]+", TokenType.WORD);
|
|
||||||
register("\\(", TokenType.OPEN_PARENTH);
|
|
||||||
register("\\)", TokenType.CLOSE_PARENTH);
|
|
||||||
}};
|
|
||||||
private static HashMap<String, Integer> precedenceMap = new HashMap<String, Integer>(){{
|
|
||||||
put("+", 0);
|
|
||||||
put("-", 0);
|
|
||||||
put("*", 1);
|
|
||||||
put("/", 1);
|
|
||||||
put("^", 2);
|
|
||||||
}};
|
|
||||||
private static HashMap<String, OperatorAssociativity> associativityMap =
|
|
||||||
new HashMap<String, OperatorAssociativity>() {{
|
|
||||||
put("+", OperatorAssociativity.LEFT);
|
|
||||||
put("-", OperatorAssociativity.LEFT);
|
|
||||||
put("*", OperatorAssociativity.LEFT);
|
|
||||||
put("/", OperatorAssociativity.LEFT);
|
|
||||||
put("^", OperatorAssociativity.RIGHT);
|
|
||||||
}};
|
|
||||||
|
|
||||||
private static Comparator<TokenType> tokenSorter = Comparator.comparingInt(e -> e.priority);
|
|
||||||
|
|
||||||
public static ArrayList<Match<TokenType>> tokenize(String string){
|
|
||||||
return lexer.lexAll(string, 0, tokenSorter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArrayList<Match<TokenType>> intoPostfix(String source, ArrayList<Match<TokenType>> from){
|
|
||||||
ArrayList<Match<TokenType>> output = new ArrayList<>();
|
|
||||||
Stack<Match<TokenType>> tokenStack = new Stack<>();
|
|
||||||
while(!from.isEmpty()){
|
|
||||||
Match<TokenType> match = from.remove(0);
|
|
||||||
if(match.getType() == TokenType.NUM) {
|
|
||||||
output.add(match);
|
|
||||||
} else if(match.getType() == TokenType.OP){
|
|
||||||
String tokenString = source.substring(match.getFrom(), match.getTo());
|
|
||||||
int precedence = precedenceMap.get(tokenString);
|
|
||||||
OperatorAssociativity associativity = associativityMap.get(tokenString);
|
|
||||||
|
|
||||||
while(!tokenStack.empty()) {
|
|
||||||
Match<TokenType> otherMatch = tokenStack.peek();
|
|
||||||
if(otherMatch.getType() != TokenType.OP) break;
|
|
||||||
|
|
||||||
int otherPrecdence = precedenceMap.get(source.substring(otherMatch.getFrom(), otherMatch.getTo()));
|
|
||||||
if(otherPrecdence < precedence ||
|
|
||||||
(associativity == OperatorAssociativity.RIGHT && otherPrecdence == precedence)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
output.add(tokenStack.pop());
|
|
||||||
}
|
|
||||||
tokenStack.push(match);
|
|
||||||
} else if(match.getType() == TokenType.OPEN_PARENTH){
|
|
||||||
tokenStack.push(match);
|
|
||||||
} else if(match.getType() == TokenType.CLOSE_PARENTH){
|
|
||||||
while(!tokenStack.empty() && tokenStack.peek().getType() != TokenType.OPEN_PARENTH){
|
|
||||||
output.add(tokenStack.pop());
|
|
||||||
}
|
|
||||||
if(tokenStack.empty()) return null;
|
|
||||||
tokenStack.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while(!tokenStack.empty()){
|
|
||||||
if(!(tokenStack.peek().getType() == TokenType.OP)) return null;
|
|
||||||
output.add(tokenStack.pop());
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TreeNode fromStringRecursive(String source, ArrayList<Match<TokenType>> matches){
|
|
||||||
if(matches.size() == 0) return null;
|
|
||||||
Match<TokenType> match = matches.remove(0);
|
|
||||||
if(match.getType() == TokenType.OP){
|
|
||||||
TreeNode right = fromStringRecursive(source, matches);
|
|
||||||
TreeNode left = fromStringRecursive(source, matches);
|
|
||||||
if(left == null || right == null) return null;
|
|
||||||
else return new OpNode(source.substring(match.getFrom(), match.getTo()), left, right);
|
|
||||||
} else if(match.getType() == TokenType.NUM){
|
|
||||||
return new NumberNode(Double.parseDouble(source.substring(match.getFrom(), match.getTo())));
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TreeNode fromString(String string){
|
|
||||||
ArrayList<Match<TokenType>> matches = intoPostfix(string, tokenize(string));
|
|
||||||
if(matches == null) return null;
|
|
||||||
|
|
||||||
Collections.reverse(matches);
|
|
||||||
return fromStringRecursive(string, matches);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
104
src/test/java/org/nwapw/abacus/tests/CalculationTests.java
Normal file
104
src/test/java/org/nwapw/abacus/tests/CalculationTests.java
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package org.nwapw.abacus.tests;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.nwapw.abacus.Abacus;
|
||||||
|
import org.nwapw.abacus.config.Configuration;
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
import org.nwapw.abacus.plugin.StandardPlugin;
|
||||||
|
import org.nwapw.abacus.tree.TreeNode;
|
||||||
|
|
||||||
|
public class CalculationTests {
|
||||||
|
|
||||||
|
private static Abacus abacus = new Abacus(new Configuration("precise", new String[]{}));
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void prepareTests(){
|
||||||
|
abacus.getPluginManager().addInstantiated(new StandardPlugin(abacus.getPluginManager()));
|
||||||
|
abacus.getPluginManager().load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testOutput(String input, String parseOutput, String output){
|
||||||
|
TreeNode parsedTree = abacus.parseString(input);
|
||||||
|
Assert.assertNotNull(parsedTree);
|
||||||
|
Assert.assertEquals(parsedTree.toString(), parseOutput);
|
||||||
|
NumberInterface result = abacus.evaluateTree(parsedTree);
|
||||||
|
Assert.assertNotNull(result);
|
||||||
|
Assert.assertTrue(result.toString().startsWith(output));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testEvalError(String input, String parseOutput){
|
||||||
|
TreeNode parsedTree = abacus.parseString(input);
|
||||||
|
Assert.assertNotNull(parsedTree);
|
||||||
|
Assert.assertEquals(parsedTree.toString(), parseOutput);
|
||||||
|
Assert.assertNull(abacus.evaluateTree(parsedTree));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddition(){
|
||||||
|
testOutput("9.5+10", "(9.5+10)", "19.5");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSubtraction(){
|
||||||
|
testOutput("9.5-10", "(9.5-10)", "-0.5");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultiplication(){
|
||||||
|
testOutput("9.5*10", "(9.5*10)", "95");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDivision(){
|
||||||
|
testOutput("9.5/2", "(9.5/2)", "4.75");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNegation(){
|
||||||
|
testOutput("-9.5", "(9.5)`", "-9.5");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFactorial(){
|
||||||
|
testOutput("7!", "(7)!", "5040");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAbs(){
|
||||||
|
testOutput("abs(-1)", "abs((1)`)", "1");
|
||||||
|
testOutput("abs(1)", "abs(1)", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLn(){
|
||||||
|
testEvalError("ln(-1)", "ln((1)`)");
|
||||||
|
testOutput("ln2", "ln(2)", "0.6931471805599453094172321214581765680755");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSqrt(){
|
||||||
|
testOutput("sqrt0", "sqrt(0)", "0");
|
||||||
|
testOutput("sqrt4", "sqrt(4)", "2");
|
||||||
|
testOutput("sqrt2", "sqrt(2)", "1.4142135623730950488016887242096980785696");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExp(){
|
||||||
|
testOutput("exp0", "exp(0)", "1");
|
||||||
|
testOutput("exp1", "exp(1)", "2.718281828459045235360287471352662497757247");
|
||||||
|
testOutput("exp300", "exp(300)", "19424263952412559365842088360176992193662086");
|
||||||
|
testOutput("exp300", "exp(300)", "19424263952412559365842088360176992193662086");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPow(){
|
||||||
|
testOutput("0^2", "(0^2)", "0");
|
||||||
|
testOutput("2^0", "(2^0)", "1");
|
||||||
|
testOutput("2^1", "(2^1)", "2");
|
||||||
|
testOutput("2^-1", "(2^(1)`)", "0.5");
|
||||||
|
testOutput("2^50", "(2^50)", "112589990684262");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
133
src/test/java/org/nwapw/abacus/tests/LexerTests.java
Normal file
133
src/test/java/org/nwapw/abacus/tests/LexerTests.java
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package org.nwapw.abacus.tests;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.nwapw.abacus.lexing.Lexer;
|
||||||
|
import org.nwapw.abacus.lexing.pattern.Match;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class LexerTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicSuccess() {
|
||||||
|
Lexer<Integer> lexer = new Lexer<>();
|
||||||
|
lexer.register("abc", 0);
|
||||||
|
lexer.register("def", 1);
|
||||||
|
List<Match<Integer>> matchedIntegers = lexer.lexAll("abcdefabc", 0, Integer::compare);
|
||||||
|
Assert.assertNotNull(matchedIntegers);
|
||||||
|
Assert.assertEquals(matchedIntegers.get(0).getType(), Integer.valueOf(0));
|
||||||
|
Assert.assertEquals(matchedIntegers.get(1).getType(), Integer.valueOf(1));
|
||||||
|
Assert.assertEquals(matchedIntegers.get(2).getType(), Integer.valueOf(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicFailure() {
|
||||||
|
Lexer<Integer> lexer = new Lexer<>();
|
||||||
|
lexer.register("abc", 0);
|
||||||
|
lexer.register("def", 1);
|
||||||
|
Assert.assertNull(lexer.lexAll("abcdefabcz", 0, Integer::compare));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoPatterns() {
|
||||||
|
Lexer<Integer> lexer = new Lexer<>();
|
||||||
|
Assert.assertNull(lexer.lexAll("abcdefabc", 0, Integer::compare));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyMatches() {
|
||||||
|
Lexer<Integer> lexer = new Lexer<>();
|
||||||
|
lexer.register("a?", 0);
|
||||||
|
Assert.assertNull(lexer.lexAll("", 0, Integer::compare));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOneOrMore() {
|
||||||
|
Lexer<Integer> lexer = new Lexer<>();
|
||||||
|
lexer.register("a+", 0);
|
||||||
|
List<Match<Integer>> tokens = lexer.lexAll("aaaa", 0, Integer::compare);
|
||||||
|
Assert.assertNotNull(tokens);
|
||||||
|
Assert.assertEquals(tokens.size(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testZeroOrMore() {
|
||||||
|
Lexer<Integer> lexer = new Lexer<>();
|
||||||
|
lexer.register("a*", 0);
|
||||||
|
List<Match<Integer>> tokens = lexer.lexAll("aaaa", 0, Integer::compare);
|
||||||
|
Assert.assertNotNull(tokens);
|
||||||
|
Assert.assertEquals(tokens.size(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testZeroOrOne() {
|
||||||
|
Lexer<Integer> lexer = new Lexer<>();
|
||||||
|
lexer.register("a?", 0);
|
||||||
|
List<Match<Integer>> tokens = lexer.lexAll("aaaa", 0, Integer::compare);
|
||||||
|
Assert.assertNotNull(tokens);
|
||||||
|
Assert.assertEquals(tokens.size(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGreedyMatching() {
|
||||||
|
Lexer<Integer> lexer = new Lexer<>();
|
||||||
|
lexer.register("a*a", 0);
|
||||||
|
List<Match<Integer>> tokens = lexer.lexAll("aaaa", 0, Integer::compare);
|
||||||
|
Assert.assertNotNull(tokens);
|
||||||
|
Assert.assertEquals(tokens.size(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAnyCharacter() {
|
||||||
|
String testString = "abcdef";
|
||||||
|
Lexer<Integer> lexer = new Lexer<>();
|
||||||
|
lexer.register(".", 0);
|
||||||
|
List<Match<Integer>> tokens = lexer.lexAll(testString, 0, Integer::compare);
|
||||||
|
Assert.assertNotNull(tokens);
|
||||||
|
Assert.assertEquals(tokens.size(), testString.length());
|
||||||
|
for (int i = 0; i < tokens.size(); i++) {
|
||||||
|
Assert.assertEquals(testString.substring(i, i + 1), tokens.get(i).getContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicGroup() {
|
||||||
|
Lexer<Integer> lexer = new Lexer<>();
|
||||||
|
lexer.register("(abc)", 0);
|
||||||
|
List<Match<Integer>> tokens = lexer.lexAll("abc", 0, Integer::compare);
|
||||||
|
Assert.assertNotNull(tokens);
|
||||||
|
Assert.assertEquals(tokens.size(), 1);
|
||||||
|
Assert.assertEquals(tokens.get(0).getContent(), "abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicRangeSuccess() {
|
||||||
|
String testString = "abcdef";
|
||||||
|
Lexer<Integer> lexer = new Lexer<>();
|
||||||
|
lexer.register("[a-f]", 0);
|
||||||
|
List<Match<Integer>> tokens = lexer.lexAll(testString, 0, Integer::compare);
|
||||||
|
Assert.assertNotNull(tokens);
|
||||||
|
Assert.assertEquals(testString.length(), tokens.size());
|
||||||
|
for (int i = 0; i < tokens.size(); i++) {
|
||||||
|
Assert.assertEquals(testString.substring(i, i + 1), tokens.get(i).getContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicRangeFailure() {
|
||||||
|
Lexer<Integer> lexer = new Lexer<>();
|
||||||
|
lexer.register("[a-f]", 0);
|
||||||
|
Assert.assertNull(lexer.lexAll("g", 0, Integer::compare));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGroupAndOperator() {
|
||||||
|
Lexer<Integer> lexer = new Lexer<>();
|
||||||
|
lexer.register("(abc)+", 0);
|
||||||
|
List<Match<Integer>> tokens = lexer.lexAll("abcabc", 0, Integer::compare);
|
||||||
|
Assert.assertNotNull(tokens);
|
||||||
|
Assert.assertEquals(tokens.size(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
125
src/test/java/org/nwapw/abacus/tests/TokenizerTests.java
Normal file
125
src/test/java/org/nwapw/abacus/tests/TokenizerTests.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package org.nwapw.abacus.tests;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.nwapw.abacus.Abacus;
|
||||||
|
import org.nwapw.abacus.config.Configuration;
|
||||||
|
import org.nwapw.abacus.function.Function;
|
||||||
|
import org.nwapw.abacus.function.Operator;
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity;
|
||||||
|
import org.nwapw.abacus.function.OperatorType;
|
||||||
|
import org.nwapw.abacus.lexing.pattern.Match;
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
import org.nwapw.abacus.parsing.LexerTokenizer;
|
||||||
|
import org.nwapw.abacus.plugin.Plugin;
|
||||||
|
import org.nwapw.abacus.tree.TokenType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TokenizerTests {
|
||||||
|
|
||||||
|
private static Abacus abacus = new Abacus(new Configuration("precise", new String[]{}));
|
||||||
|
private static LexerTokenizer lexerTokenizer = new LexerTokenizer();
|
||||||
|
private static Function subtractFunction = new Function() {
|
||||||
|
@Override
|
||||||
|
protected boolean matchesParams(NumberInterface[] params) {
|
||||||
|
return params.length == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected 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));
|
||||||
|
registerFunction("subtract", subtractFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static void assertTokensMatch(List<Match<TokenType>> tokenList, TokenType[] expectedTypes) {
|
||||||
|
Assert.assertNotNull(tokenList);
|
||||||
|
Assert.assertEquals(tokenList.size(), expectedTypes.length);
|
||||||
|
for (int i = 0; i < expectedTypes.length; i++) {
|
||||||
|
Assert.assertEquals(expectedTypes[i], tokenList.get(i).getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void prepareTests() {
|
||||||
|
abacus.getPluginManager().addListener(lexerTokenizer);
|
||||||
|
abacus.getPluginManager().addInstantiated(testPlugin);
|
||||||
|
abacus.getPluginManager().load();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInteger() {
|
||||||
|
assertTokensMatch(lexerTokenizer.tokenizeString("11"), new TokenType[]{TokenType.NUM});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLeadingZeroDecimal() {
|
||||||
|
assertTokensMatch(lexerTokenizer.tokenizeString("0.1"), new TokenType[]{TokenType.NUM});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNonLeadingDecimal() {
|
||||||
|
assertTokensMatch(lexerTokenizer.tokenizeString(".1"), new TokenType[]{TokenType.NUM});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleChars() {
|
||||||
|
TokenType[] types = {
|
||||||
|
TokenType.OPEN_PARENTH,
|
||||||
|
TokenType.WHITESPACE,
|
||||||
|
TokenType.COMMA,
|
||||||
|
TokenType.CLOSE_PARENTH
|
||||||
|
};
|
||||||
|
assertTokensMatch(lexerTokenizer.tokenizeString("( ,)"), types);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFunctionParsing() {
|
||||||
|
TokenType[] types = {
|
||||||
|
TokenType.FUNCTION,
|
||||||
|
TokenType.OPEN_PARENTH,
|
||||||
|
TokenType.NUM,
|
||||||
|
TokenType.COMMA,
|
||||||
|
TokenType.NUM,
|
||||||
|
TokenType.CLOSE_PARENTH
|
||||||
|
};
|
||||||
|
assertTokensMatch(lexerTokenizer.tokenizeString("subtract(1,2)"), types);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperatorParsing() {
|
||||||
|
TokenType[] types = {
|
||||||
|
TokenType.NUM,
|
||||||
|
TokenType.OP,
|
||||||
|
TokenType.NUM
|
||||||
|
};
|
||||||
|
assertTokensMatch(lexerTokenizer.tokenizeString("1-1"), types);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSanitizedOperators() {
|
||||||
|
TokenType[] types = {
|
||||||
|
TokenType.NUM,
|
||||||
|
TokenType.OP,
|
||||||
|
TokenType.NUM
|
||||||
|
};
|
||||||
|
assertTokensMatch(lexerTokenizer.tokenizeString("1+1"), types);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user