mirror of
https://github.com/DanilaFe/abacus
synced 2024-09-29 18:04:41 -07:00
Compare commits
460 Commits
v0.1.0-ALP
...
master
Author | SHA1 | Date | |
---|---|---|---|
15394b7ea0 | |||
cc2da711e7 | |||
ef39bcbaa2 | |||
ac17246317 | |||
18e0bdebc5 | |||
251d0fc2c5 | |||
93c1c53612 | |||
0b6798e28d | |||
4188c83a66 | |||
af988a34d1 | |||
751fd97fcb | |||
899e0e65a5 | |||
b4b64ac963 | |||
50cc51d089 | |||
8f682e96af | |||
9bdf188ca7 | |||
9fdb3fc0d9 | |||
095d374949 | |||
981211ccfa | |||
d91536e746 | |||
7c252fddf1 | |||
0d26167f31 | |||
07abe4d17a | |||
8ef0904d26 | |||
92489551ca | |||
2a9026f748 | |||
08e5b69c04 | |||
6a0b667c32 | |||
5a1fdfe4bc | |||
378ff946d9 | |||
0511c58b13 | |||
e82a13cde5 | |||
40362a7afe | |||
c990d4c50a | |||
d7bb838866 | |||
e05b2ac8d5 | |||
b4214f5714 | |||
bd02749706 | |||
f809183126 | |||
579ff78a99 | |||
81d0999c11 | |||
4fd8f7badf | |||
bc475a22f9 | |||
e0ccb67ad3 | |||
ea4588be44 | |||
31996219ad | |||
a3bfc34c1c | |||
8dc7acd4b3 | |||
76fcd8ec1c | |||
fbdf2c7e52 | |||
3057f66e66 | |||
f385a48aa2 | |||
fd3f56aa8f | |||
e364f4e94b | |||
4c94abb18b | |||
ba63dd7874 | |||
566598b702 | |||
eb91a5b875 | |||
fcd4694203 | |||
566831246c | |||
ad8a0a9b2a | |||
e430e738cf | |||
f6e326e0f1 | |||
07581557c7 | |||
14ac9c67f4 | |||
0ff071e212 | |||
88e3bb7109 | |||
540e5d6099 | |||
c9e93d87a2 | |||
337edd68fa | |||
08967fbb8f | |||
46f78bb2ed | |||
5b4773dee1 | |||
be94394a5c | |||
45de25cd50 | |||
52ab357fe1 | |||
1575d3e574 | |||
87529da15f | |||
7cd117dac1 | |||
8975bfdb99 | |||
00f8475044 | |||
f0efae21be | |||
9f11fd20a2 | |||
1667edc72b | |||
5d2a988f75 | |||
91978686e6 | |||
9a8d0afc19 | |||
5aba5c350b | |||
21b7bd5e2b | |||
f2ac7b109a | |||
67d240b8f6 | |||
dc4eee6342 | |||
6909f210d6 | |||
059226a4d4 | |||
ef1890f24d | |||
782669a32b | |||
924849bd8b | |||
91986112a1 | |||
58fea9c52b | |||
863be5bcfc | |||
f0e38fed87 | |||
fd246f935c | |||
6604af5b0f | |||
d49a763e8f | |||
48a4d8adc2 | |||
5417b45106 | |||
585cabc478 | |||
28802cfed3 | |||
428df8bfd3 | |||
146f3994ef | |||
daffdb6b42 | |||
178f59ef7b | |||
61616a428a | |||
9c77fa8aeb | |||
9ddfeb02cf | |||
bc4a26aafb | |||
1f6aa70230 | |||
e62722ce2f | |||
ce82fd56dd | |||
6a65e66935 | |||
e172108476 | |||
2b700d3911 | |||
f7c07ca04d | |||
ecb5139e70 | |||
453cd0ea77 | |||
7a296e4e8b | |||
cbceee4abc | |||
8ea34b8f6e | |||
672252ef41 | |||
824f391fc7 | |||
63a160659a | |||
879d09e5b8 | |||
ae0ec0c375 | |||
337a38a07d | |||
fbfc68ebfe | |||
01e7a03444 | |||
0cb180284a | |||
192269ea9a | |||
f134e5aa04 | |||
e3c37cf10a | |||
823c788148 | |||
ece9f1ae04 | |||
fbc12ec41c | |||
385a64eace | |||
c2feedee32 | |||
b98b08b872 | |||
f8eb051583 | |||
20b2e77ee1 | |||
bfc1ed5819 | |||
9d52d55e68 | |||
07d7343339 | |||
73075c57b9 | |||
5b1a48c02e | |||
ca2681cc21 | |||
8a3c614602 | |||
da1c78945e | |||
225a926f86 | |||
f83f2a7aaa | |||
d04adf4da5 | |||
1f0addccea | |||
1a47e07e97 | |||
26305c3bae | |||
6b9252f902 | |||
bc26ad0b88 | |||
c5cd0f81ad | |||
ac19c7b230 | |||
40c80db914 | |||
00462281fe | |||
01f80bbb53 | |||
553c7354c1 | |||
50ede6460c | |||
beb583a231 | |||
e0ff229df4 | |||
1c751353f1 | |||
0a15043b63 | |||
21e059c1ca | |||
16faceb3cc | |||
251da90d57 | |||
a7536b198f | |||
66bda5db39 | |||
2425adaea5 | |||
f5d913d527 | |||
beae822d76 | |||
bcd3e3b68a | |||
8b42acfbb1 | |||
952eeac704 | |||
b57f24854e | |||
6758771c45 | |||
415a05288a | |||
334439e075 | |||
852776566d | |||
07020de1f9 | |||
06c2397a09 | |||
69fe28d5e4 | |||
367c282497 | |||
2876eda2ca | |||
4ec8c7a535 | |||
5167946fe2 | |||
418e896108 | |||
d3012b9624 | |||
bf7881faab | |||
b75e92838d | |||
620f087e38 | |||
205d5dbc77 | |||
d861444d13 | |||
eac2a9ed6b | |||
31c61fdf95 | |||
8f251d2d13 | |||
99ffd51a43 | |||
00c51c62fd | |||
400ed6e70a | |||
|
5902ba8c5c | ||
|
efdc4af31b | ||
2e0b1201a3 | |||
|
d94edaa33f | ||
|
1e6cf08ec2 | ||
7c378b401a | |||
1c448048f2 | |||
fd21014c39 | |||
|
fee9f091fd | ||
021e569491 | |||
|
4226df72f5 | ||
|
1f0e6a7ce4 | ||
|
efe76a6fdc | ||
|
ca6d8d2ba2 | ||
e6f5af3727 | |||
4e042bd0eb | |||
d6f4838f05 | |||
61475a24d9 | |||
c498a5b643 | |||
5e3daaed43 | |||
b99ad5a09a | |||
ff8701a7bf | |||
|
1d6957c4d9 | ||
33b175a3c6 | |||
c95a6df304 | |||
3316f02e2b | |||
6767a0e4aa | |||
400e4578a0 | |||
9d92d0eebb | |||
fdcf2b5c6d | |||
8c3de54d0c | |||
5a57544067 | |||
61f40c72aa | |||
ea5ff08c09 | |||
5f80c0bf14 | |||
7dcc80fcae | |||
d10536155b | |||
536cac7b23 | |||
|
b6e4c6d2ea | ||
|
f8b3559cec | ||
|
4cf4ba98a8 | ||
12710c625b | |||
e71b037195 | |||
fe92929856 | |||
e61cfdca46 | |||
ff7d90967e | |||
355a91d690 | |||
5f0fba15eb | |||
3bdc0e2ae5 | |||
|
68fbcd2d7c | ||
|
ed92b382f0 | ||
e54b5cdd66 | |||
fd87cb66a3 | |||
200f4c7288 | |||
|
1cd544e712 | ||
|
a8c70a6bbe | ||
f28e915c9a | |||
7a0863380a | |||
|
8a9df051cf | ||
|
4eda15b3fb | ||
c9e0d4f8d3 | |||
213d7af10b | |||
585a3839c1 | |||
205b73f62c | |||
1cd332b97d | |||
314552f95a | |||
c5ec521996 | |||
4712bbfded | |||
7ae7f6d9a5 | |||
|
d0ccb8b625 | ||
|
7d5efa1fe6 | ||
35254d3e99 | |||
44f018060d | |||
|
6a15c266c4 | ||
9f61fc5dbe | |||
bae6ee5526 | |||
4f94700aef | |||
b7152da58d | |||
d17a8a9fa7 | |||
71c9f0d141 | |||
fb02984e60 | |||
a9ac4681f0 | |||
62d7053441 | |||
f3cbb600ac | |||
abc0e2d59f | |||
f7d1be086b | |||
21a925d6d2 | |||
0d21898f20 | |||
3e39087fde | |||
a984f2960d | |||
a6832e09f4 | |||
0bcb3b25d9 | |||
2f5f967be4 | |||
72a2a8f1c1 | |||
58fc94e9d0 | |||
9cedb100ad | |||
99be2d80f1 | |||
2523b9b04b | |||
cd60c9d52f | |||
23a3eb88f1 | |||
508e98413d | |||
d06f611a2e | |||
c541eaab97 | |||
0058ec9c71 | |||
|
f8bf60f383 | ||
|
4369eba107 | ||
385a0c960d | |||
|
d7ae1a80f1 | ||
|
e4a45c0ec4 | ||
22cf99d23d | |||
39b36f84e0 | |||
b036b6c242 | |||
eb3410f854 | |||
f967053e3d | |||
75824a2a77 | |||
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 |
11
.gitignore
vendored
11
.gitignore
vendored
|
@ -20,3 +20,14 @@
|
||||||
|
|
||||||
# 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/*
|
||||||
|
**/out/**
|
||||||
|
**/.DS_Store
|
||||||
|
|
||||||
|
# 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
|
||||||
|
[![Build Status](https://travis-ci.org/DanilaFe/abacus.svg?branch=master)](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.
|
||||||
|
|
29
build.gradle
Normal file
29
build.gradle
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.2.40'
|
||||||
|
ext.dokka_version = '0.9.16'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
|
||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
apply plugin: 'org.jetbrains.dokka'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.40"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
3
core/build.gradle
Normal file
3
core/build.gradle
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
dependencies {
|
||||||
|
testCompile 'junit:junit:4.12'
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.nwapw.abacus.exception;
|
||||||
|
|
||||||
|
public class AbacusException extends RuntimeException {
|
||||||
|
|
||||||
|
public AbacusException(String baseMessage, String description){
|
||||||
|
super(baseMessage + ((description.equals("")) ? "." : (": " + description)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.nwapw.abacus.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when the computation is interrupted by
|
||||||
|
* the user.
|
||||||
|
*/
|
||||||
|
public class ComputationInterruptedException extends AbacusException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new exception of this type.
|
||||||
|
*/
|
||||||
|
public ComputationInterruptedException() {
|
||||||
|
super("Computation interrupted", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.nwapw.abacus.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown by the Context in cases where lookup fails
|
||||||
|
* where it should not.
|
||||||
|
*/
|
||||||
|
public class ContextException extends AbacusException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ContextException without an extra message.
|
||||||
|
*/
|
||||||
|
public ContextException() {
|
||||||
|
this("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a ContextException with the given message.
|
||||||
|
* @param message the message to use.
|
||||||
|
*/
|
||||||
|
public ContextException(String message){
|
||||||
|
super("Context exception", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.nwapw.abacus.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown if the function parameters do not match
|
||||||
|
* requirements.
|
||||||
|
*/
|
||||||
|
public class DomainException extends AbacusException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new DomainException.
|
||||||
|
* @param reason the reason for which the exception is thrown.
|
||||||
|
*/
|
||||||
|
public DomainException(String reason) {
|
||||||
|
super("Domain error", reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new DomainException with a default message.
|
||||||
|
*/
|
||||||
|
public DomainException(){
|
||||||
|
this("");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.nwapw.abacus.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown by the NumberReducer if something goes wrong when
|
||||||
|
* transforming a parse tree into a single value.
|
||||||
|
*/
|
||||||
|
public class NumberReducerException extends AbacusException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new NumberReducerException with
|
||||||
|
* no additional message.
|
||||||
|
*/
|
||||||
|
public NumberReducerException() {
|
||||||
|
this("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new NumberReducerException with the given message.
|
||||||
|
* @param message the message.
|
||||||
|
*/
|
||||||
|
public NumberReducerException(String message) {
|
||||||
|
super("Error evaluating expression", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.nwapw.abacus.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown by parsers.
|
||||||
|
*/
|
||||||
|
public class ParseException extends AbacusException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ParseException with no additional message.
|
||||||
|
*/
|
||||||
|
public ParseException(){
|
||||||
|
this("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ParseException with the given additional message.
|
||||||
|
* @param message the message.
|
||||||
|
*/
|
||||||
|
public ParseException(String message){
|
||||||
|
super("Failed to parse string", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.nwapw.abacus.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a promotion fails.
|
||||||
|
*/
|
||||||
|
public class PromotionException extends AbacusException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new PromotionException with the default message
|
||||||
|
* and no additional information.
|
||||||
|
*/
|
||||||
|
public PromotionException() {
|
||||||
|
this("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new PromotionException with the given additional message.
|
||||||
|
* @param message the additional message to include with the error.
|
||||||
|
*/
|
||||||
|
public PromotionException(String message) {
|
||||||
|
super("Failed to promote number instances", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.nwapw.abacus.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown from TreeReducers.
|
||||||
|
*/
|
||||||
|
public class ReductionException extends AbacusException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new EvaluationException with the default string.
|
||||||
|
*/
|
||||||
|
public ReductionException() {
|
||||||
|
this("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new EvaluationError with the given message string.
|
||||||
|
* @param message the message string.
|
||||||
|
*/
|
||||||
|
public ReductionException(String message) {
|
||||||
|
super("Evaluation error", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.nwapw.abacus.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown by Lexers when they are unable to tokenize the input string.
|
||||||
|
*/
|
||||||
|
public class TokenizeException extends AbacusException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new tokenize exception with no additional data.
|
||||||
|
*/
|
||||||
|
public TokenizeException() {
|
||||||
|
this("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new tokenize exception with the given message.
|
||||||
|
* @param message the message to use.
|
||||||
|
*/
|
||||||
|
public TokenizeException(String message){
|
||||||
|
super("Failed to tokenize string", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package org.nwapw.abacus.function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum that holds the type of documentation that has been
|
||||||
|
* registered with Abacus.
|
||||||
|
*/
|
||||||
|
public enum DocumentationType {
|
||||||
|
|
||||||
|
FUNCTION, TREE_VALUE_FUNCTION
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,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
|
||||||
|
}
|
|
@ -1,24 +1,119 @@
|
||||||
package org.nwapw.abacus.lexing;
|
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.Pattern;
|
||||||
import org.nwapw.abacus.lexing.pattern.PatternNode;
|
import org.nwapw.abacus.lexing.pattern.nodes.EndNode;
|
||||||
|
import org.nwapw.abacus.lexing.pattern.nodes.PatternNode;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A lexer that can generate tokens of a given type given a list of regular expressions
|
* A lexer that can generate tokens of a given type given a list of regular expressions
|
||||||
* to operate on.
|
* to operate on.
|
||||||
|
*
|
||||||
* @param <T> the type used to identify which match belongs to which pattern.
|
* @param <T> the type used to identify which match belongs to which pattern.
|
||||||
*/
|
*/
|
||||||
public class Lexer<T> {
|
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++;
|
||||||
|
}
|
||||||
|
if (compare != null) {
|
||||||
|
Collections.sort(matches, (a, b) -> compare.compare(a.getType(), b.getType()));
|
||||||
|
}
|
||||||
|
Collections.sort(matches, (o1, o2) -> o1.getContent().length() - o2.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.
|
* An entry that represents a pattern that has been registered with the lexer.
|
||||||
|
*
|
||||||
* @param <T> the type used to identify the pattern.
|
* @param <T> the type used to identify the pattern.
|
||||||
*/
|
*/
|
||||||
private static class PatternEntry<T>{
|
private static class PatternEntry<T> {
|
||||||
/**
|
/**
|
||||||
* The name of the entry.
|
* The name of the entry.
|
||||||
*/
|
*/
|
||||||
|
@ -30,17 +125,21 @@ public class Lexer<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new pattern entry with the given name and id.
|
* Creates a new pattern entry with the given name and id.
|
||||||
|
*
|
||||||
* @param name the name of the pattern entry.
|
* @param name the name of the pattern entry.
|
||||||
* @param id the id of the pattern entry.
|
* @param id the id of the pattern entry.
|
||||||
*/
|
*/
|
||||||
public PatternEntry(String name, T id){
|
public PatternEntry(String name, T id) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(name, id);
|
return Arrays.hashCode(new Object[] {
|
||||||
|
this.name,
|
||||||
|
this.id
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -51,93 +150,4 @@ 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<>(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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){
|
|
||||||
if(lastMatch.getTo() == lastMatch.getFrom()) return null;
|
|
||||||
matches.add(lastMatch);
|
|
||||||
index += lastMatch.getTo() - lastMatch.getFrom();
|
|
||||||
}
|
|
||||||
if(lastMatch == null) return null;
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
47
core/src/main/java/org/nwapw/abacus/lexing/Match.java
Normal file
47
core/src/main/java/org/nwapw/abacus/lexing/Match.java
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package org.nwapw.abacus.lexing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,15 @@
|
||||||
package org.nwapw.abacus.lexing.pattern;
|
package org.nwapw.abacus.lexing.pattern;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.lexing.pattern.nodes.*;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pattern that can be compiled from a string and used in lexing.
|
* 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.
|
* @param <T> the type that is used to identify and sort this pattern.
|
||||||
*/
|
*/
|
||||||
public class Pattern<T> {
|
public class Pattern<T> {
|
||||||
|
@ -33,195 +35,26 @@ public class Pattern<T> {
|
||||||
* A map of regex operator to functions that modify a PatternChain
|
* A map of regex operator to functions that modify a PatternChain
|
||||||
* with the appropriate operation.
|
* with the appropriate operation.
|
||||||
*/
|
*/
|
||||||
private Map<Character, Function<PatternChain<T>, PatternChain<T>>> operations =
|
private Map<Character, Transformation<T>> operations =
|
||||||
new HashMap<Character, Function<PatternChain<T>, PatternChain<T>>>() {{
|
new HashMap<Character, Transformation<T>>() {{
|
||||||
put('+', Pattern.this::transformPlus);
|
put('+', Pattern.this::transformPlus);
|
||||||
put('*', Pattern.this::transformStar);
|
put('*', Pattern.this::transformStar);
|
||||||
put('?', Pattern.this::transformQuestion);
|
put('?', Pattern.this::transformQuestion);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates / compiles a new pattern with the given id from the given string.
|
* Creates / compiles a new pattern with the given id from the given string.
|
||||||
|
*
|
||||||
* @param from the string to compile a pattern from.
|
* @param from the string to compile a pattern from.
|
||||||
* @param id the ID to use.
|
* @param id the ID to use.
|
||||||
*/
|
*/
|
||||||
public Pattern(String from, T id){
|
public Pattern(String from, T id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
index = 0;
|
index = 0;
|
||||||
source = from;
|
source = from;
|
||||||
|
|
||||||
PatternChain<T> chain = parseSegment(false);
|
PatternChain<T> chain = parseSegment(false);
|
||||||
if(chain == null) {
|
if (chain == null) {
|
||||||
head = null;
|
head = null;
|
||||||
} else {
|
} else {
|
||||||
chain.append(new EndNode<>(id));
|
chain.append(new EndNode<>(id));
|
||||||
|
@ -229,29 +62,208 @@ public class Pattern<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the head PatternNode, for use in matching
|
|
||||||
* @return the pattern node.
|
|
||||||
*/
|
|
||||||
public PatternNode<T> getHead() {
|
|
||||||
return head;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all characters that are considered "special" from
|
* Removes all characters that are considered "special" from
|
||||||
* the given string.
|
* the given string.
|
||||||
|
*
|
||||||
* @param from the string to sanitize.
|
* @param from the string to sanitize.
|
||||||
* @return the resulting string.
|
* @return the resulting string.
|
||||||
*/
|
*/
|
||||||
public static String sanitize(String from){
|
public static String sanitize(String from) {
|
||||||
Pattern<Integer> pattern = new Pattern<>("", 0);
|
Pattern<Integer> pattern = new Pattern<>("", 0);
|
||||||
from = from.replace(".", "\\.");
|
from = from.replace(".", "\\.");
|
||||||
from = from.replace("|", "\\|");
|
from = from.replace("|", "\\|");
|
||||||
from = from.replace("(", "\\(");
|
from = from.replace("(", "\\(");
|
||||||
from = from.replace(")", "\\)");
|
from = from.replace(")", "\\)");
|
||||||
for(Character key : pattern.operations.keySet()){
|
for (Character key : pattern.operations.keySet()) {
|
||||||
from = from.replace("" + key, "\\" + key);
|
from = from.replace("" + key, "\\" + key);
|
||||||
}
|
}
|
||||||
return from;
|
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.getOutputStates().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.getOutputStates().add(chain.head);
|
||||||
|
newHead.getOutputStates().add(newTail);
|
||||||
|
chain.tail.getOutputStates().add(newTail);
|
||||||
|
newTail.getOutputStates().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.getOutputStates().add(chain.head);
|
||||||
|
newHead.getOutputStates().add(newTail);
|
||||||
|
chain.tail.getOutputStates().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.getOutputStates().add(chain.head);
|
||||||
|
chain.tail.getOutputStates().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).transform(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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
package org.nwapw.abacus.lexing.pattern;
|
package org.nwapw.abacus.lexing.pattern;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.lexing.pattern.nodes.PatternNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A chain of nodes that can be treated as a single unit.
|
* A chain of nodes that can be treated as a single unit.
|
||||||
* Used during pattern compilation.
|
* Used during pattern compilation.
|
||||||
|
*
|
||||||
* @param <T> the type used to identify which pattern has been matched.
|
* @param <T> the type used to identify which pattern has been matched.
|
||||||
*/
|
*/
|
||||||
public class PatternChain<T> {
|
public class PatternChain<T> {
|
||||||
|
@ -18,26 +21,28 @@ public class PatternChain<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new chain with the given start and end.
|
* Creates a new chain with the given start and end.
|
||||||
|
*
|
||||||
* @param head the start of the chain.
|
* @param head the start of the chain.
|
||||||
* @param tail the end of the chain.
|
* @param tail the end of the chain.
|
||||||
*/
|
*/
|
||||||
public PatternChain(PatternNode<T> head, PatternNode<T> tail){
|
public PatternChain(PatternNode<T> head, PatternNode<T> tail) {
|
||||||
this.head = head;
|
this.head = head;
|
||||||
this.tail = tail;
|
this.tail = tail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a chain that starts and ends with the same node.
|
* Creates a chain that starts and ends with the same node.
|
||||||
|
*
|
||||||
* @param node the node to use.
|
* @param node the node to use.
|
||||||
*/
|
*/
|
||||||
public PatternChain(PatternNode<T> node){
|
public PatternChain(PatternNode<T> node) {
|
||||||
this(node, node);
|
this(node, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an empty chain.
|
* Creates an empty chain.
|
||||||
*/
|
*/
|
||||||
public PatternChain(){
|
public PatternChain() {
|
||||||
this(null);
|
this(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,14 +50,15 @@ public class PatternChain<T> {
|
||||||
* Appends the other chain to this one. This modifies
|
* Appends the other chain to this one. This modifies
|
||||||
* the nodes, as well.
|
* the nodes, as well.
|
||||||
* If this chain is empty, it is set to the other.
|
* If this chain is empty, it is set to the other.
|
||||||
|
*
|
||||||
* @param other the other chain to append.
|
* @param other the other chain to append.
|
||||||
*/
|
*/
|
||||||
public void append(PatternChain<T> other){
|
public void append(PatternChain<T> other) {
|
||||||
if(other.head == null || tail == null) {
|
if (other.head == null || tail == null) {
|
||||||
this.head = other.head;
|
this.head = other.head;
|
||||||
this.tail = other.tail;
|
this.tail = other.tail;
|
||||||
} else {
|
} else {
|
||||||
tail.outputStates.add(other.head);
|
tail.getOutputStates().add(other.head);
|
||||||
tail = other.tail;
|
tail = other.tail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,13 +67,14 @@ public class PatternChain<T> {
|
||||||
* Appends a single node to this chain. This modifies
|
* Appends a single node to this chain. This modifies
|
||||||
* the nodes, as well.
|
* the nodes, as well.
|
||||||
* If this chain is empty, it is set to the node.
|
* If this chain is empty, it is set to the node.
|
||||||
|
*
|
||||||
* @param node the node to append to this chain.
|
* @param node the node to append to this chain.
|
||||||
*/
|
*/
|
||||||
public void append(PatternNode<T> node){
|
public void append(PatternNode<T> node) {
|
||||||
if(tail == null){
|
if (tail == null) {
|
||||||
head = tail = node;
|
head = tail = node;
|
||||||
} else {
|
} else {
|
||||||
tail.outputStates.add(node);
|
tail.getOutputStates().add(node);
|
||||||
tail = node;
|
tail = node;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.nwapw.abacus.lexing.pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface that transforms a pattern chain into a different pattern chain.
|
||||||
|
* @param <T> the type used to identify the nodes in the pattern chain.
|
||||||
|
*/
|
||||||
|
public interface Transformation<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the actual transformation.
|
||||||
|
* @param from the original chain.
|
||||||
|
* @return the resulting chain.
|
||||||
|
*/
|
||||||
|
PatternChain<T> transform(PatternChain<T> from);
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
package org.nwapw.abacus.lexing.pattern;
|
package org.nwapw.abacus.lexing.pattern.nodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pattern node that matches any character.
|
* A pattern node that matches any character.
|
||||||
|
*
|
||||||
* @param <T> the type that's used to tell which pattern this node belongs to.
|
* @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> {
|
|
@ -1,7 +1,8 @@
|
||||||
package org.nwapw.abacus.lexing.pattern;
|
package org.nwapw.abacus.lexing.pattern.nodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A node that represents a successful match.
|
* A node that represents a successful match.
|
||||||
|
*
|
||||||
* @param <T> the type that's used to tell which pattern this node belongs to.
|
* @param <T> the type that's used to tell which pattern this node belongs to.
|
||||||
*/
|
*/
|
||||||
public class EndNode<T> extends PatternNode<T> {
|
public class EndNode<T> extends PatternNode<T> {
|
||||||
|
@ -13,17 +14,19 @@ public class EndNode<T> extends PatternNode<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new end node with the given ID.
|
* Creates a new end node with the given ID.
|
||||||
|
*
|
||||||
* @param patternId the pattern ID.
|
* @param patternId the pattern ID.
|
||||||
*/
|
*/
|
||||||
public EndNode(T patternId){
|
public EndNode(T patternId) {
|
||||||
this.patternId = patternId;
|
this.patternId = patternId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the pattern ID.
|
* Gets the pattern ID.
|
||||||
|
*
|
||||||
* @return the pattern ID.
|
* @return the pattern ID.
|
||||||
*/
|
*/
|
||||||
public T getPatternId(){
|
public T getPatternId() {
|
||||||
return patternId;
|
return patternId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
package org.nwapw.abacus.lexing.pattern;
|
package org.nwapw.abacus.lexing.pattern.nodes;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A node that is used as structural glue in pattern compilation.
|
* 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.
|
* @param <T> the type that's used to tell which pattern this node belongs to.
|
||||||
*/
|
*/
|
||||||
public class LinkNode<T> extends PatternNode<T> {
|
public class LinkNode<T> extends PatternNode<T> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInto(Collection<PatternNode<T>> into) {
|
public void addInto(Collection<PatternNode<T>> into) {
|
||||||
|
if (!into.contains(this)) {
|
||||||
|
into.add(this);
|
||||||
addOutputsInto(into);
|
addOutputsInto(into);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package org.nwapw.abacus.lexing.pattern;
|
package org.nwapw.abacus.lexing.pattern.nodes;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -9,6 +8,7 @@ import java.util.Set;
|
||||||
* A base class for a pattern node. Provides all functions
|
* A base class for a pattern node. Provides all functions
|
||||||
* necessary for matching, and is constructed by a Pattern instance
|
* necessary for matching, and is constructed by a Pattern instance
|
||||||
* from a string.
|
* from a string.
|
||||||
|
*
|
||||||
* @param <T> the type that's used to tell which pattern this node belongs to.
|
* @param <T> the type that's used to tell which pattern this node belongs to.
|
||||||
*/
|
*/
|
||||||
public class PatternNode<T> {
|
public class PatternNode<T> {
|
||||||
|
@ -22,43 +22,54 @@ public class PatternNode<T> {
|
||||||
/**
|
/**
|
||||||
* Creates a new pattern node.
|
* Creates a new pattern node.
|
||||||
*/
|
*/
|
||||||
public PatternNode(){
|
public PatternNode() {
|
||||||
outputStates = new HashSet<>();
|
outputStates = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the current input character can
|
* Determines whether the current input character can
|
||||||
* be matched by this node.
|
* be matched by this node.
|
||||||
|
*
|
||||||
* @param other the character being matched.
|
* @param other the character being matched.
|
||||||
* @return true if the character can be matched, false otherwise.
|
* @return true if the character can be matched, false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean matches(char other){
|
public boolean matches(char other) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this node can be used as part of a range, returns that value.
|
* If this node can be used as part of a range, returns that value.
|
||||||
|
*
|
||||||
* @return a NULL terminator if this character cannot be converted
|
* @return a NULL terminator if this character cannot be converted
|
||||||
* into a range bound, or the appropriate range bound if it can.
|
* into a range bound, or the appropriate range bound if it can.
|
||||||
*/
|
*/
|
||||||
public char range(){
|
public char range() {
|
||||||
return '\0';
|
return '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds this node in a collection of other nodes.
|
* Adds this node in a collection of other nodes.
|
||||||
|
*
|
||||||
* @param into the collection to add into.
|
* @param into the collection to add into.
|
||||||
*/
|
*/
|
||||||
public void addInto(Collection<PatternNode<T>> into){
|
public void addInto(Collection<PatternNode<T>> into) {
|
||||||
into.add(this);
|
into.add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the node's children into a collection of other nodes.
|
* Adds the node's children into a collection of other nodes.
|
||||||
|
*
|
||||||
* @param into the collection to add into.
|
* @param into the collection to add into.
|
||||||
*/
|
*/
|
||||||
public void addOutputsInto(Collection<PatternNode<T>> into){
|
public void addOutputsInto(Collection<PatternNode<T>> into) {
|
||||||
outputStates.forEach(e -> e.addInto(into));
|
outputStates.forEach(e -> e.addInto(into));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the output states of this node.
|
||||||
|
* @return the output states.
|
||||||
|
*/
|
||||||
|
public Set<PatternNode<T>> getOutputStates() {
|
||||||
|
return outputStates;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
package org.nwapw.abacus.lexing.pattern;
|
package org.nwapw.abacus.lexing.pattern.nodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A node that matches a range of characters.
|
* A node that matches a range of characters.
|
||||||
|
*
|
||||||
* @param <T> the type that's used to tell which pattern this node belongs to.
|
* @param <T> the type that's used to tell which pattern this node belongs to.
|
||||||
*/
|
*/
|
||||||
public class RangeNode<T> extends PatternNode<T> {
|
public class RangeNode<T> extends PatternNode<T> {
|
||||||
|
@ -17,10 +18,11 @@ public class RangeNode<T> extends PatternNode<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new range node from the given range.
|
* Creates a new range node from the given range.
|
||||||
|
*
|
||||||
* @param from the bottom bound of the range.
|
* @param from the bottom bound of the range.
|
||||||
* @param to the top bound of hte range.
|
* @param to the top bound of hte range.
|
||||||
*/
|
*/
|
||||||
public RangeNode(char from, char to){
|
public RangeNode(char from, char to) {
|
||||||
this.from = from;
|
this.from = from;
|
||||||
this.to = to;
|
this.to = to;
|
||||||
}
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
package org.nwapw.abacus.lexing.pattern;
|
package org.nwapw.abacus.lexing.pattern.nodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A node that matches a single value.
|
* A node that matches a single value.
|
||||||
|
*
|
||||||
* @param <T> the type that's used to tell which pattern this node belongs to.
|
* @param <T> the type that's used to tell which pattern this node belongs to.
|
||||||
*/
|
*/
|
||||||
public class ValueNode<T> extends PatternNode<T> {
|
public class ValueNode<T> extends PatternNode<T> {
|
||||||
|
@ -13,9 +14,10 @@ public class ValueNode<T> extends PatternNode<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new node that matches the given character.
|
* Creates a new node that matches the given character.
|
||||||
* @param value
|
*
|
||||||
|
* @param value the character value of the node.
|
||||||
*/
|
*/
|
||||||
public ValueNode(char value){
|
public ValueNode(char value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
129
core/src/main/java/org/nwapw/abacus/number/standard/NaiveNumber.java
Executable file
129
core/src/main/java/org/nwapw/abacus/number/standard/NaiveNumber.java
Executable file
|
@ -0,0 +1,129 @@
|
||||||
|
package org.nwapw.abacus.number.standard;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
double shiftBy = Math.pow(10, 10);
|
||||||
|
return Double.toString(Math.round(value * shiftBy) / shiftBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface getMaxError() {
|
||||||
|
return new NaiveNumber(Math.pow(10, -18));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
166
core/src/main/java/org/nwapw/abacus/number/standard/PreciseNumber.java
Executable file
166
core/src/main/java/org/nwapw/abacus/number/standard/PreciseNumber.java
Executable file
|
@ -0,0 +1,166 @@
|
||||||
|
package org.nwapw.abacus.number.standard;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.MathContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 number of extra significant figures kept in calculations before rounding for output.
|
||||||
|
*/
|
||||||
|
private static int numExtraInternalSigFigs = 15;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MathContext that is used when rounding a number prior to output.
|
||||||
|
*/
|
||||||
|
private static MathContext outputContext = new MathContext(50);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MathContext that is actually used in calculations.
|
||||||
|
*/
|
||||||
|
private static MathContext internalContext = new MathContext(outputContext.getPrecision() + numExtraInternalSigFigs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value of the PreciseNumber.
|
||||||
|
*/
|
||||||
|
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 internalContext.getPrecision();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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, internalContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
NumberInterface floor = new PreciseNumber(str.substring(0, decimalIndex));
|
||||||
|
if (signum() == -1) {
|
||||||
|
floor = floor.subtract(ONE);
|
||||||
|
}
|
||||||
|
return floor;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface fractionalPartInternal() {
|
||||||
|
return this.subtractInternal(floorInternal());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int intValue() {
|
||||||
|
return value.intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface negateInternal() {
|
||||||
|
return new PreciseNumber(value.negate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value.round(outputContext).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface getMaxError() {
|
||||||
|
return new PreciseNumber(value.ulp()).multiplyInternal(TEN.intPowInternal(value.precision() - internalContext.getPrecision()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package org.nwapw.abacus.parsing.standard;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.exception.TokenizeException;
|
||||||
|
import org.nwapw.abacus.lexing.Lexer;
|
||||||
|
import org.nwapw.abacus.lexing.Match;
|
||||||
|
import org.nwapw.abacus.lexing.pattern.Pattern;
|
||||||
|
import org.nwapw.abacus.parsing.Tokenizer;
|
||||||
|
import org.nwapw.abacus.plugin.PluginListener;
|
||||||
|
import org.nwapw.abacus.plugin.PluginManager;
|
||||||
|
|
||||||
|
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 = (o1, o2) -> o1.priority - o2.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("[a-zA-Z]+", TokenType.VARIABLE);
|
||||||
|
register("\\(", TokenType.OPEN_PARENTH);
|
||||||
|
register("\\)", TokenType.CLOSE_PARENTH);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Match<TokenType>> tokenizeString(String string) {
|
||||||
|
List<Match<TokenType>> tokens = lexer.lexAll(string, 0, TOKEN_SORTER);
|
||||||
|
if(tokens == null) throw new TokenizeException();
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad(PluginManager manager) {
|
||||||
|
for (String operator : manager.getAllOperators()) {
|
||||||
|
lexer.register(Pattern.sanitize(operator), TokenType.OP);
|
||||||
|
}
|
||||||
|
for (String operator : manager.getAllTreeValueOperators()) {
|
||||||
|
lexer.register(Pattern.sanitize(operator), TokenType.TREE_VALUE_OP);
|
||||||
|
}
|
||||||
|
for (String function : manager.getAllFunctions()) {
|
||||||
|
lexer.register(Pattern.sanitize(function), TokenType.FUNCTION);
|
||||||
|
}
|
||||||
|
for (String function : manager.getAllTreeValueFunctions()) {
|
||||||
|
lexer.register(Pattern.sanitize(function), TokenType.TREE_VALUE_FUNCTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnload(PluginManager manager) {
|
||||||
|
for (String operator : manager.getAllOperators()) {
|
||||||
|
lexer.unregister(Pattern.sanitize(operator), TokenType.OP);
|
||||||
|
}
|
||||||
|
for (String operator : manager.getAllTreeValueOperators()) {
|
||||||
|
lexer.unregister(Pattern.sanitize(operator), TokenType.TREE_VALUE_OP);
|
||||||
|
}
|
||||||
|
for (String function : manager.getAllFunctions()) {
|
||||||
|
lexer.unregister(Pattern.sanitize(function), TokenType.FUNCTION);
|
||||||
|
}
|
||||||
|
for (String function : manager.getAllTreeValueFunctions()) {
|
||||||
|
lexer.unregister(Pattern.sanitize(function), TokenType.TREE_VALUE_FUNCTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,207 @@
|
||||||
|
package org.nwapw.abacus.parsing.standard;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.exception.ParseException;
|
||||||
|
import org.nwapw.abacus.function.Operator;
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity;
|
||||||
|
import org.nwapw.abacus.function.OperatorType;
|
||||||
|
import org.nwapw.abacus.lexing.Match;
|
||||||
|
import org.nwapw.abacus.parsing.Parser;
|
||||||
|
import org.nwapw.abacus.plugin.PluginListener;
|
||||||
|
import org.nwapw.abacus.plugin.PluginManager;
|
||||||
|
import org.nwapw.abacus.tree.nodes.*;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
public ShuntingYardParser() {
|
||||||
|
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 || matchType == TokenType.VARIABLE) {
|
||||||
|
output.add(match);
|
||||||
|
} else if (matchType == TokenType.FUNCTION || matchType == TokenType.TREE_VALUE_FUNCTION) {
|
||||||
|
output.add(new Match<>("", TokenType.INTERNAL_FUNCTION_END));
|
||||||
|
tokenStack.push(match);
|
||||||
|
} else if (matchType == TokenType.OP || matchType == TokenType.TREE_VALUE_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.TREE_VALUE_OP || previousType == TokenType.OPEN_PARENTH)) {
|
||||||
|
from.add(0, new Match<>("`", TokenType.OP));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!tokenStack.empty() && type == OperatorType.BINARY_INFIX) {
|
||||||
|
Match<TokenType> otherMatch = tokenStack.peek();
|
||||||
|
TokenType otherMatchType = otherMatch.getType();
|
||||||
|
if (!(otherMatchType == TokenType.OP ||
|
||||||
|
otherMatchType == TokenType.TREE_VALUE_OP ||
|
||||||
|
otherMatchType == TokenType.FUNCTION ||
|
||||||
|
otherMatchType == TokenType.TREE_VALUE_FUNCTION)) break;
|
||||||
|
|
||||||
|
if (otherMatchType == TokenType.OP || otherMatchType == TokenType.TREE_VALUE_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()) throw new ParseException("mismatched parentheses");
|
||||||
|
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.TREE_VALUE_OP ||
|
||||||
|
newMatchType == TokenType.FUNCTION ||
|
||||||
|
newMatchType == TokenType.TREE_VALUE_FUNCTION)) throw new ParseException("mismatched parentheses");
|
||||||
|
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<? extends Match<TokenType>> matches) {
|
||||||
|
if (matches.size() == 0) throw new ParseException("no tokens left in input");
|
||||||
|
Match<TokenType> match = matches.remove(0);
|
||||||
|
TokenType matchType = match.getType();
|
||||||
|
if (matchType == TokenType.OP || matchType == TokenType.TREE_VALUE_OP) {
|
||||||
|
String operator = match.getContent();
|
||||||
|
OperatorType type = typeMap.get(operator);
|
||||||
|
if (type == OperatorType.BINARY_INFIX) {
|
||||||
|
TreeNode right = constructRecursive(matches);
|
||||||
|
TreeNode left = constructRecursive(matches);
|
||||||
|
if (matchType == TokenType.OP) {
|
||||||
|
return new NumberBinaryNode(operator, left, right);
|
||||||
|
} else {
|
||||||
|
return new TreeValueBinaryNode(operator, left, right);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TreeNode applyTo = constructRecursive(matches);
|
||||||
|
if (matchType == TokenType.OP) {
|
||||||
|
return new NumberUnaryNode(operator, applyTo);
|
||||||
|
} else {
|
||||||
|
return new TreeValueUnaryNode(operator, applyTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (matchType == TokenType.NUM) {
|
||||||
|
return new NumberNode(match.getContent());
|
||||||
|
} else if (matchType == TokenType.VARIABLE) {
|
||||||
|
return new VariableNode(match.getContent());
|
||||||
|
} else if (matchType == TokenType.FUNCTION || matchType == TokenType.TREE_VALUE_FUNCTION) {
|
||||||
|
String functionName = match.getContent();
|
||||||
|
List<TreeNode> children = new ArrayList<>();
|
||||||
|
while (!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END) {
|
||||||
|
TreeNode argument = constructRecursive(matches);
|
||||||
|
children.add(0, argument);
|
||||||
|
}
|
||||||
|
if (matches.isEmpty()) throw new ParseException("incorrectly formatted function call");
|
||||||
|
matches.remove(0);
|
||||||
|
CallNode node;
|
||||||
|
if (matchType == TokenType.FUNCTION) {
|
||||||
|
node = new NumberFunctionNode(functionName, children);
|
||||||
|
} else {
|
||||||
|
node = new TreeValueFunctionNode(functionName, children);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
throw new ParseException("unrecognized token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TreeNode constructTree(List<? extends Match<TokenType>> tokens) {
|
||||||
|
if (tokens.isEmpty()) throw new ParseException("no input tokens");
|
||||||
|
tokens = intoPostfix(new ArrayList<>(tokens));
|
||||||
|
Collections.reverse(tokens);
|
||||||
|
TreeNode constructedTree = constructRecursive(tokens);
|
||||||
|
if(tokens.size() == 0) return constructedTree;
|
||||||
|
throw new ParseException("could not parse all input");
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
for (String operator : manager.getAllTreeValueOperators()) {
|
||||||
|
Operator operatorInstance = manager.treeValueOperatorFor(operator);
|
||||||
|
precedenceMap.put(operator, operatorInstance.getPrecedence());
|
||||||
|
associativityMap.put(operator, operatorInstance.getAssociativity());
|
||||||
|
typeMap.put(operator, operatorInstance.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnload(PluginManager manager) {
|
||||||
|
precedenceMap.clear();
|
||||||
|
associativityMap.clear();
|
||||||
|
typeMap.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.nwapw.abacus.tree;
|
package org.nwapw.abacus.parsing.standard;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum to represent the type of the token that has been matched
|
* Enum to represent the type of the token that has been matched
|
||||||
|
@ -7,7 +7,8 @@ package org.nwapw.abacus.tree;
|
||||||
public enum TokenType {
|
public enum TokenType {
|
||||||
|
|
||||||
INTERNAL_FUNCTION_END(-1),
|
INTERNAL_FUNCTION_END(-1),
|
||||||
ANY(0), WHITESPACE(1), COMMA(2), OP(3), NUM(4), FUNCTION(5), OPEN_PARENTH(6), CLOSE_PARENTH(7);
|
ANY(0), WHITESPACE(1), COMMA(2), VARIABLE(3), OP(4), TREE_VALUE_OP(4),
|
||||||
|
NUM(5), FUNCTION(6), TREE_VALUE_FUNCTION(6), OPEN_PARENTH(7), CLOSE_PARENTH(7);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The priority by which this token gets sorted.
|
* The priority by which this token gets sorted.
|
||||||
|
@ -16,9 +17,10 @@ public enum TokenType {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new token type with the given priority.
|
* Creates a new token type with the given priority.
|
||||||
|
*
|
||||||
* @param priority the priority of this token type.
|
* @param priority the priority of this token type.
|
||||||
*/
|
*/
|
||||||
TokenType(int priority){
|
TokenType(int priority) {
|
||||||
this.priority = priority;
|
this.priority = priority;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
package org.nwapw.abacus.plugin;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
import org.nwapw.abacus.number.promotion.PromotionFunction;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that holds data about a number implementation.
|
||||||
|
*/
|
||||||
|
public abstract class NumberImplementation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of paths through which this implementation can be promoted.
|
||||||
|
*/
|
||||||
|
private Map<String, PromotionFunction> 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<String, PromotionFunction> 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();
|
||||||
|
|
||||||
|
}
|
227
core/src/main/java/org/nwapw/abacus/plugin/Plugin.java
Normal file
227
core/src/main/java/org/nwapw/abacus/plugin/Plugin.java
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
package org.nwapw.abacus.plugin;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.function.Documentation;
|
||||||
|
import org.nwapw.abacus.function.DocumentationType;
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberFunction;
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberOperator;
|
||||||
|
import org.nwapw.abacus.function.interfaces.TreeValueFunction;
|
||||||
|
import org.nwapw.abacus.function.interfaces.TreeValueOperator;
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
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, NumberFunction toRegister) {
|
||||||
|
manager.registerFunction(name, toRegister);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be used in load(). Registers a tree value function abstract class
|
||||||
|
* with the plugin internally, which makes it accessible to the plugin manager.
|
||||||
|
*
|
||||||
|
* @param name the name to register by.
|
||||||
|
* @param toRegister the tree value function implementation.
|
||||||
|
*/
|
||||||
|
protected final void registerTreeValueFunction(String name, TreeValueFunction toRegister) {
|
||||||
|
manager.registerTreeValueFunction(name, toRegister);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be used in load(). Registers an operator abstract class
|
||||||
|
* with the plugin internally, which makes it accessible to
|
||||||
|
* the plugin manager.
|
||||||
|
*
|
||||||
|
* @param name the name of the operator.
|
||||||
|
* @param operator the operator to register.
|
||||||
|
*/
|
||||||
|
protected final void registerOperator(String name, NumberOperator operator) {
|
||||||
|
manager.registerOperator(name, operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be used in load(). Registers an operator
|
||||||
|
* with the plugin internally, which makes it accessible
|
||||||
|
* to the plugin manager.
|
||||||
|
*
|
||||||
|
* @param name the name of the tree value operator.
|
||||||
|
* @param operator the tree value operator to register.
|
||||||
|
*/
|
||||||
|
protected final void registerTreeValueOperator(String name, TreeValueOperator operator) {
|
||||||
|
manager.registerTreeValueOperator(name, operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be used in load(). Registers a new number implementation with the plugin.
|
||||||
|
* This makes it accessible to the plugin manager.
|
||||||
|
*
|
||||||
|
* @param name the name of the implementation.
|
||||||
|
* @param implementation the actual implementation class to register.
|
||||||
|
*/
|
||||||
|
protected final void registerNumberImplementation(String name, NumberImplementation implementation) {
|
||||||
|
manager.registerNumberImplementation(name, implementation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be used in load(). Registers a documentation instance
|
||||||
|
* used to explain some element of the plugin to the user.
|
||||||
|
*
|
||||||
|
* @param documentation the documentation instance.
|
||||||
|
*/
|
||||||
|
protected final void registerDocumentation(Documentation documentation) {
|
||||||
|
manager.registerDocumentation(documentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 NumberFunction functionFor(String name) {
|
||||||
|
return manager.functionFor(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the PluginManager for the given function name.
|
||||||
|
* This can be used by the plugins internally in order to call functions
|
||||||
|
* they do not provide.
|
||||||
|
*
|
||||||
|
* @param name the name for which to search.
|
||||||
|
* @return the resulting tree value function, or null if none was found for that name.
|
||||||
|
*/
|
||||||
|
protected final TreeValueFunction treeValueFunctionFor(String name) {
|
||||||
|
return manager.treeValueFunctionFor(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the PluginManager for the given operator name.
|
||||||
|
* This can be used by the plugins internally in order to call
|
||||||
|
* 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 NumberOperator operatorFor(String name) {
|
||||||
|
return manager.operatorFor(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the PluginManager for the given tree value operator name.
|
||||||
|
* This can be used by the plugins internally in order to call
|
||||||
|
* operations they do not provide.
|
||||||
|
*
|
||||||
|
* @param name the name for which to search.
|
||||||
|
* @return the resulting tree value operator, or null if none was found for that name.
|
||||||
|
*/
|
||||||
|
protected final TreeValueOperator treeValueOperatorFor(String name) {
|
||||||
|
return manager.treeValueOperatorFor(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the PluginManager for the given number implementation
|
||||||
|
* name. This can be used by the plugins internally in order to find
|
||||||
|
* 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 PluginManager for the given documentation name and type.
|
||||||
|
*
|
||||||
|
* @param name the name for which to search.
|
||||||
|
* @param type the type of documentation to search for.
|
||||||
|
* @return the found documentation, or null if none was found.
|
||||||
|
*/
|
||||||
|
protected final Documentation documentationFor(String name, DocumentationType type) {
|
||||||
|
return manager.documentationFor(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the plugin manager for a Pi value for the given number implementation.
|
||||||
|
* This is done so that number implementations with various degrees of precision
|
||||||
|
* can provide their own pi values, without losing said precision by
|
||||||
|
* promoting NaiveNumbers.
|
||||||
|
*
|
||||||
|
* @param forClass the class to which to find the pi instance.
|
||||||
|
* @return the pi value for the given class.
|
||||||
|
*/
|
||||||
|
protected final NumberInterface piFor(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();
|
||||||
|
|
||||||
|
}
|
|
@ -7,12 +7,14 @@ public interface PluginListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the PluginManager loads plugins.
|
* Called when the PluginManager loads plugins.
|
||||||
|
*
|
||||||
* @param manager the manager that fired the event.
|
* @param manager the manager that fired the event.
|
||||||
*/
|
*/
|
||||||
public void onLoad(PluginManager manager);
|
public void onLoad(PluginManager manager);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the PluginManager unloads all its plugins.
|
* Called when the PluginManager unloads all its plugins.
|
||||||
|
*
|
||||||
* @param manager the manager that fired the event.
|
* @param manager the manager that fired the event.
|
||||||
*/
|
*/
|
||||||
public void onUnload(PluginManager manager);
|
public void onUnload(PluginManager manager);
|
420
core/src/main/java/org/nwapw/abacus/plugin/PluginManager.java
Normal file
420
core/src/main/java/org/nwapw/abacus/plugin/PluginManager.java
Normal file
|
@ -0,0 +1,420 @@
|
||||||
|
package org.nwapw.abacus.plugin;
|
||||||
|
|
||||||
|
import org.nwapw.abacus.Abacus;
|
||||||
|
import org.nwapw.abacus.function.Documentation;
|
||||||
|
import org.nwapw.abacus.function.DocumentationType;
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberFunction;
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberOperator;
|
||||||
|
import org.nwapw.abacus.function.interfaces.TreeValueFunction;
|
||||||
|
import org.nwapw.abacus.function.interfaces.TreeValueOperator;
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that controls instances of plugins, allowing for them
|
||||||
|
* to interact with each other and the calculator.
|
||||||
|
*/
|
||||||
|
public class PluginManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of classes loaded by this manager.
|
||||||
|
*/
|
||||||
|
private Set<Class<?>> loadedPluginClasses;
|
||||||
|
/**
|
||||||
|
* A list of loaded plugins.
|
||||||
|
*/
|
||||||
|
private Set<Plugin> plugins;
|
||||||
|
/**
|
||||||
|
* The map of functions registered by the plugins.
|
||||||
|
*/
|
||||||
|
private Map<String, NumberFunction> registeredFunctions;
|
||||||
|
/**
|
||||||
|
* The map of tree value functions regstered by the plugins.
|
||||||
|
*/
|
||||||
|
private Map<String, TreeValueFunction> registeredTreeValueFunctions;
|
||||||
|
/**
|
||||||
|
* The map of operators registered by the plugins
|
||||||
|
*/
|
||||||
|
private Map<String, NumberOperator> registeredOperators;
|
||||||
|
/**
|
||||||
|
* The map of tree value operators registered by the plugins.
|
||||||
|
*/
|
||||||
|
private Map<String, TreeValueOperator> registeredTreeValueOperators;
|
||||||
|
/**
|
||||||
|
* The map of number implementations registered by the plugins.
|
||||||
|
*/
|
||||||
|
private Map<String, NumberImplementation> registeredNumberImplementations;
|
||||||
|
/**
|
||||||
|
* The map of documentation for functions registered by the plugins.
|
||||||
|
*/
|
||||||
|
private Set<Documentation> registeredDocumentation;
|
||||||
|
/**
|
||||||
|
* The list of number implementation names.
|
||||||
|
*/
|
||||||
|
private Map<Class<? extends NumberInterface>, String> interfaceImplementationNames;
|
||||||
|
/**
|
||||||
|
* The list of number implementations.
|
||||||
|
*/
|
||||||
|
private Map<Class<? extends NumberInterface>, NumberImplementation> interfaceImplementations;
|
||||||
|
/**
|
||||||
|
* The pi values for each implementation class that have already been computer.
|
||||||
|
*/
|
||||||
|
private Map<Class<? extends NumberInterface>, NumberInterface> cachedPi;
|
||||||
|
/**
|
||||||
|
* The list of plugin listeners attached to this instance.
|
||||||
|
*/
|
||||||
|
private Set<PluginListener> listeners;
|
||||||
|
/**
|
||||||
|
* The abacus instance used to access other
|
||||||
|
* components of the application.
|
||||||
|
*/
|
||||||
|
private Abacus abacus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new plugin manager.
|
||||||
|
*
|
||||||
|
* @param abacus the abacus instance.
|
||||||
|
*/
|
||||||
|
public PluginManager(Abacus abacus) {
|
||||||
|
this.abacus = abacus;
|
||||||
|
loadedPluginClasses = new HashSet<>();
|
||||||
|
plugins = new HashSet<>();
|
||||||
|
registeredFunctions = new HashMap<>();
|
||||||
|
registeredTreeValueFunctions = new HashMap<>();
|
||||||
|
registeredOperators = new HashMap<>();
|
||||||
|
registeredTreeValueOperators = new HashMap<>();
|
||||||
|
registeredNumberImplementations = new HashMap<>();
|
||||||
|
registeredDocumentation = new HashSet<>();
|
||||||
|
interfaceImplementations = new HashMap<>();
|
||||||
|
interfaceImplementationNames = new HashMap<>();
|
||||||
|
cachedPi = new HashMap<>();
|
||||||
|
listeners = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a function under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the function.
|
||||||
|
* @param function the function to register.
|
||||||
|
*/
|
||||||
|
public void registerFunction(String name, NumberFunction function) {
|
||||||
|
registeredFunctions.put(name, function);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a tree value function under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the function.
|
||||||
|
* @param function the function to register.
|
||||||
|
*/
|
||||||
|
public void registerTreeValueFunction(String name, TreeValueFunction function) {
|
||||||
|
registeredTreeValueFunctions.put(name, function);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an operator under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the operator.
|
||||||
|
* @param operator the operator to register.
|
||||||
|
*/
|
||||||
|
public void registerOperator(String name, NumberOperator operator) {
|
||||||
|
registeredOperators.put(name, operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a tree value operator under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the tree value operator.
|
||||||
|
* @param operator the tree value operator to register.
|
||||||
|
*/
|
||||||
|
public void registerTreeValueOperator(String name, TreeValueOperator operator) {
|
||||||
|
registeredTreeValueOperators.put(name, operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a number implementation under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the number implementation.
|
||||||
|
* @param implementation the number implementation to register.
|
||||||
|
*/
|
||||||
|
public void registerNumberImplementation(String name, NumberImplementation implementation) {
|
||||||
|
registeredNumberImplementations.put(name, implementation);
|
||||||
|
interfaceImplementationNames.put(implementation.getImplementation(), name);
|
||||||
|
interfaceImplementations.put(implementation.getImplementation(), implementation);
|
||||||
|
cachedPi.put(implementation.getImplementation(), implementation.instanceForPi());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the given documentation with the plugin manager,
|
||||||
|
* making it accessible to the plugin manager etc.
|
||||||
|
*
|
||||||
|
* @param documentation the documentation to register.
|
||||||
|
*/
|
||||||
|
public void registerDocumentation(Documentation documentation) {
|
||||||
|
registeredDocumentation.add(documentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the function registered under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the function.
|
||||||
|
* @return the function, or null if it was not found.
|
||||||
|
*/
|
||||||
|
public NumberFunction functionFor(String name) {
|
||||||
|
return registeredFunctions.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the tree value function registered under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the function.
|
||||||
|
* @return the function, or null if it was not found.
|
||||||
|
*/
|
||||||
|
public TreeValueFunction treeValueFunctionFor(String name) {
|
||||||
|
return registeredTreeValueFunctions.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the operator registered under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the operator.
|
||||||
|
* @return the operator, or null if it was not found.
|
||||||
|
*/
|
||||||
|
public NumberOperator operatorFor(String name) {
|
||||||
|
return registeredOperators.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the tree value operator registered under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the tree value operator.
|
||||||
|
* @return the operator, or null if it was not found.
|
||||||
|
*/
|
||||||
|
public TreeValueOperator treeValueOperatorFor(String name) {
|
||||||
|
return registeredTreeValueOperators.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number implementation registered under the given name.
|
||||||
|
*
|
||||||
|
* @param name the name of the number implementation.
|
||||||
|
* @return the number implementation, or null if it was not found.
|
||||||
|
*/
|
||||||
|
public NumberImplementation numberImplementationFor(String name) {
|
||||||
|
return registeredNumberImplementations.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the documentation for the given entity of the given type.
|
||||||
|
*
|
||||||
|
* @param name the name of the entity to search for.
|
||||||
|
* @param type the type that this entity is, to filter out similarly named documentation.
|
||||||
|
* @return the documentation object.
|
||||||
|
*/
|
||||||
|
public Documentation documentationFor(String name, DocumentationType type) {
|
||||||
|
Documentation toReturn = null;
|
||||||
|
for (Documentation entry : registeredDocumentation) {
|
||||||
|
if (entry.getCodeName().equals(name) && entry.getType() == type) {
|
||||||
|
toReturn = entry;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number implementation for the given implementation class.
|
||||||
|
*
|
||||||
|
* @param name the class for which to find the implementation.
|
||||||
|
* @return the implementation.
|
||||||
|
*/
|
||||||
|
public NumberImplementation interfaceImplementationFor(Class<? extends NumberInterface> name) {
|
||||||
|
return interfaceImplementations.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number implementation name for the given implementation class.
|
||||||
|
*
|
||||||
|
* @param name the class for which to find the implementation name.
|
||||||
|
* @return the implementation name.
|
||||||
|
*/
|
||||||
|
public String interfaceImplementationNameFor(Class<? extends NumberInterface> name) {
|
||||||
|
return interfaceImplementationNames.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the mathematical constant pi for the given implementation class.
|
||||||
|
*
|
||||||
|
* @param forClass the class for which to find pi.
|
||||||
|
* @return pi
|
||||||
|
*/
|
||||||
|
public NumberInterface piFor(Class<? extends NumberInterface> forClass) {
|
||||||
|
return cachedPi.get(forClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an instance of Plugin that already has been instantiated.
|
||||||
|
*
|
||||||
|
* @param plugin the plugin to add.
|
||||||
|
*/
|
||||||
|
public void addInstantiated(Plugin plugin) {
|
||||||
|
if (loadedPluginClasses.contains(plugin.getClass())) return;
|
||||||
|
plugins.add(plugin);
|
||||||
|
loadedPluginClasses.add(plugin.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a class of plugin, and adds it to this
|
||||||
|
* plugin manager.
|
||||||
|
*
|
||||||
|
* @param newClass the new class to instantiate.
|
||||||
|
*/
|
||||||
|
public void addClass(Class<?> newClass) {
|
||||||
|
if (!Plugin.class.isAssignableFrom(newClass) || newClass == Plugin.class) return;
|
||||||
|
try {
|
||||||
|
addInstantiated((Plugin) newClass.getConstructor(PluginManager.class).newInstance(this));
|
||||||
|
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the plugin with the given class from the manager.
|
||||||
|
*
|
||||||
|
* @param toRemove the plugin to remove.
|
||||||
|
*/
|
||||||
|
public void removeClass(Class<? extends Plugin> toRemove) {
|
||||||
|
if (!loadedPluginClasses.contains(toRemove)) return;
|
||||||
|
plugins.removeIf(plugin -> plugin.getClass() == toRemove);
|
||||||
|
loadedPluginClasses.remove(toRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all plugins from this plugin manager.
|
||||||
|
*/
|
||||||
|
public void removeAll() {
|
||||||
|
loadedPluginClasses.clear();
|
||||||
|
plugins.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all the plugins in the PluginManager.
|
||||||
|
*/
|
||||||
|
public void load() {
|
||||||
|
Set<String> disabledPlugins = abacus.getConfiguration().getDisabledPlugins();
|
||||||
|
for (Plugin plugin : plugins) {
|
||||||
|
if (disabledPlugins.contains(plugin.getClass().getName())) continue;
|
||||||
|
plugin.enable();
|
||||||
|
}
|
||||||
|
listeners.forEach(e -> e.onLoad(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unloads all the plugins in the PluginManager.
|
||||||
|
*/
|
||||||
|
public void unload() {
|
||||||
|
listeners.forEach(e -> e.onUnload(this));
|
||||||
|
Set<String> disabledPlugins = abacus.getConfiguration().getDisabledPlugins();
|
||||||
|
for (Plugin plugin : plugins) {
|
||||||
|
if (disabledPlugins.contains(plugin.getClass().getName())) continue;
|
||||||
|
plugin.disable();
|
||||||
|
}
|
||||||
|
registeredFunctions.clear();
|
||||||
|
registeredTreeValueFunctions.clear();
|
||||||
|
registeredOperators.clear();
|
||||||
|
registeredTreeValueOperators.clear();
|
||||||
|
registeredNumberImplementations.clear();
|
||||||
|
registeredDocumentation.clear();
|
||||||
|
interfaceImplementationNames.clear();
|
||||||
|
interfaceImplementations.clear();
|
||||||
|
cachedPi.clear();
|
||||||
|
listeners.forEach(e -> e.onUnload(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads all the plugins in the PluginManager.
|
||||||
|
*/
|
||||||
|
public void reload() {
|
||||||
|
unload();
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the functions loaded by the Plugin Manager.
|
||||||
|
*
|
||||||
|
* @return the set of all functions that were loaded.
|
||||||
|
*/
|
||||||
|
public Set<String> getAllFunctions() {
|
||||||
|
return registeredFunctions.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the tree vlaue functions loaded by the PluginManager.
|
||||||
|
*
|
||||||
|
* @return the set of all the tree value functions that were loaded.
|
||||||
|
*/
|
||||||
|
public Set<String> getAllTreeValueFunctions() {
|
||||||
|
return registeredTreeValueFunctions.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the operators loaded by the Plugin Manager.
|
||||||
|
*
|
||||||
|
* @return the set of all operators that were loaded.
|
||||||
|
*/
|
||||||
|
public Set<String> getAllOperators() {
|
||||||
|
return registeredOperators.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the tree value operators loaded by the PluginManager.
|
||||||
|
*
|
||||||
|
* @return the set of all tree value operators that were loaded.
|
||||||
|
*/
|
||||||
|
public Set<String> getAllTreeValueOperators() {
|
||||||
|
return registeredTreeValueOperators.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the number implementations loaded by the Plugin Manager.
|
||||||
|
*
|
||||||
|
* @return the set of all implementations that were loaded.
|
||||||
|
*/
|
||||||
|
public Set<String> getAllNumberImplementations() {
|
||||||
|
return registeredNumberImplementations.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a plugin change listener to this plugin manager.
|
||||||
|
*
|
||||||
|
* @param listener the listener to add.
|
||||||
|
*/
|
||||||
|
public void addListener(PluginListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the plugin change listener from this plugin manager.
|
||||||
|
*
|
||||||
|
* @param listener the listener to remove.
|
||||||
|
*/
|
||||||
|
public void removeListener(PluginListener listener) {
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of all the plugin class files that have been
|
||||||
|
* added to the plugin manager.
|
||||||
|
*
|
||||||
|
* @return the list of all the added plugin classes.
|
||||||
|
*/
|
||||||
|
public Set<Class<?>> getLoadedPluginClasses() {
|
||||||
|
return loadedPluginClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
679
core/src/main/java/org/nwapw/abacus/plugin/standard/StandardPlugin.java
Executable file
679
core/src/main/java/org/nwapw/abacus/plugin/standard/StandardPlugin.java
Executable file
|
@ -0,0 +1,679 @@
|
||||||
|
package org.nwapw.abacus.plugin.standard;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.nwapw.abacus.context.MutableEvaluationContext;
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext;
|
||||||
|
import org.nwapw.abacus.function.Documentation;
|
||||||
|
import org.nwapw.abacus.function.DocumentationType;
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberFunction;
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberOperator;
|
||||||
|
import org.nwapw.abacus.function.interfaces.TreeValueOperator;
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
import org.nwapw.abacus.number.standard.NaiveNumber;
|
||||||
|
import org.nwapw.abacus.number.standard.PreciseNumber;
|
||||||
|
import org.nwapw.abacus.plugin.NumberImplementation;
|
||||||
|
import org.nwapw.abacus.plugin.Plugin;
|
||||||
|
import org.nwapw.abacus.plugin.PluginManager;
|
||||||
|
import org.nwapw.abacus.plugin.standard.operator.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The plugin providing standard functions such as addition and subtraction to
|
||||||
|
* the calculator.
|
||||||
|
*/
|
||||||
|
public class StandardPlugin extends Plugin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set operator.
|
||||||
|
*/
|
||||||
|
public static final TreeValueOperator OP_SET = new OperatorSet();
|
||||||
|
/**
|
||||||
|
* The define operator.
|
||||||
|
*/
|
||||||
|
public final TreeValueOperator OP_DEFINE = new OperatorDefine();
|
||||||
|
/**
|
||||||
|
* The addition operator, +
|
||||||
|
*/
|
||||||
|
public static final NumberOperator OP_ADD = new OperatorAdd();
|
||||||
|
/**
|
||||||
|
* The subtraction operator, -
|
||||||
|
*/
|
||||||
|
public static final NumberOperator OP_SUBTRACT = new OperatorSubtract();
|
||||||
|
/**
|
||||||
|
* The negation operator, -
|
||||||
|
*/
|
||||||
|
public static final NumberOperator OP_NEGATE = new OperatorNegate();
|
||||||
|
/**
|
||||||
|
* The multiplication operator, *
|
||||||
|
*/
|
||||||
|
public static final NumberOperator OP_MULTIPLY = new OperatorMultiply();
|
||||||
|
/**
|
||||||
|
* The implementation for double-based naive numbers.
|
||||||
|
*/
|
||||||
|
public static final NumberImplementation IMPLEMENTATION_NAIVE = new NumberImplementation(NaiveNumber.class, 0) {
|
||||||
|
@Override
|
||||||
|
public NumberInterface instanceForString(String string) {
|
||||||
|
return new NaiveNumber(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface instanceForPi() {
|
||||||
|
return new NaiveNumber(Math.PI);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The implementation for the infinite-precision BigDecimal.
|
||||||
|
*/
|
||||||
|
public static final NumberImplementation IMPLEMENTATION_PRECISE = new NumberImplementation(PreciseNumber.class, 0) {
|
||||||
|
@Override
|
||||||
|
public NumberInterface instanceForString(String string) {
|
||||||
|
return new PreciseNumber(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface instanceForPi() {
|
||||||
|
MutableEvaluationContext dummyContext = new MutableEvaluationContext(null, this, null);
|
||||||
|
NumberInterface C = FUNCTION_SQRT.apply(dummyContext, new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
|
||||||
|
NumberInterface M = PreciseNumber.ONE;
|
||||||
|
NumberInterface L = new PreciseNumber("13591409");
|
||||||
|
NumberInterface X = M;
|
||||||
|
NumberInterface sum = L;
|
||||||
|
int termsNeeded = C.getMaxPrecision() / 13 + 1;
|
||||||
|
|
||||||
|
NumberInterface lSummand = new PreciseNumber("545140134");
|
||||||
|
NumberInterface xMultiplier = new PreciseNumber("262537412")
|
||||||
|
.multiply(new PreciseNumber("1000000000"))
|
||||||
|
.add(new PreciseNumber("640768000"))
|
||||||
|
.negate();
|
||||||
|
for (int i = 0; i < termsNeeded; i++) {
|
||||||
|
M = M
|
||||||
|
.multiply(new PreciseNumber((12 * i + 2) + ""))
|
||||||
|
.multiply(new PreciseNumber((12 * i + 6) + ""))
|
||||||
|
.multiply(new PreciseNumber((12 * i + 10) + ""))
|
||||||
|
.divide(new PreciseNumber(Math.pow(i + 1, 3) + ""));
|
||||||
|
L = L.add(lSummand);
|
||||||
|
X = X.multiply(xMultiplier);
|
||||||
|
sum = sum.add(M.multiply(L).divide(X));
|
||||||
|
}
|
||||||
|
return C.divide(sum);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The division operator, /
|
||||||
|
*/
|
||||||
|
public static final NumberOperator OP_DIVIDE = new OperatorDivide();
|
||||||
|
/**
|
||||||
|
* The factorial operator, !
|
||||||
|
*/
|
||||||
|
public static final NumberOperator OP_FACTORIAL = new OperatorFactorial();
|
||||||
|
/**
|
||||||
|
* The permutation operator.
|
||||||
|
*/
|
||||||
|
public static final NumberOperator OP_NPR = new OperatorNpr();
|
||||||
|
/**
|
||||||
|
* The combination operator.
|
||||||
|
*/
|
||||||
|
public static final NumberOperator OP_NCR = new OperatorNcr();
|
||||||
|
/**
|
||||||
|
* The absolute value function, abs(-3) = 3
|
||||||
|
*/
|
||||||
|
public static final NumberFunction FUNCTION_ABS = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params[0].multiply(context.getInheritedNumberImplementation().instanceForString(Integer.toString(params[0].signum())));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The natural log function.
|
||||||
|
*/
|
||||||
|
public static final NumberFunction FUNCTION_LN = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1 && params[0].compareTo(context.getInheritedNumberImplementation().instanceForString("0")) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||||
|
NumberInterface param = params[0];
|
||||||
|
NumberInterface one = implementation.instanceForString("1");
|
||||||
|
int powersOf2 = 0;
|
||||||
|
while (FUNCTION_ABS.apply(context, param.subtract(one)).compareTo(implementation.instanceForString(".1")) >= 0) {
|
||||||
|
if (param.subtract(one).signum() == 1) {
|
||||||
|
param = param.divide(implementation.instanceForString("2"));
|
||||||
|
powersOf2++;
|
||||||
|
if (param.subtract(one).signum() != 1) {
|
||||||
|
break;
|
||||||
|
//No infinite loop for you.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
param = param.multiply(implementation.instanceForString("2"));
|
||||||
|
powersOf2--;
|
||||||
|
if (param.subtract(one).signum() != -1) {
|
||||||
|
break;
|
||||||
|
//No infinite loop for you.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getLog2(context.getInheritedNumberImplementation(), param).multiply(implementation.instanceForString(Integer.toString(powersOf2))).add(getLogPartialSum(context, param));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the partial sum of the Taylor series for logx (around x=1).
|
||||||
|
* Automatically determines the number of terms needed based on the precision of x.
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* @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(PluginEvaluationContext context, NumberInterface x) {
|
||||||
|
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||||
|
NumberInterface maxError = x.getMaxError();
|
||||||
|
x = x.subtract(implementation.instanceForString("1")); //Terms used are for log(x+1).
|
||||||
|
NumberInterface currentNumerator = x, currentTerm = x, sum = x;
|
||||||
|
int n = 1;
|
||||||
|
while (FUNCTION_ABS.apply(context, currentTerm).compareTo(maxError) > 0) {
|
||||||
|
n++;
|
||||||
|
currentNumerator = currentNumerator.multiply(x).negate();
|
||||||
|
currentTerm = currentNumerator.divide(implementation.instanceForString(Integer.toString(n)));
|
||||||
|
sum = sum.add(currentTerm);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns natural log of 2 to the required precision of the class of number.
|
||||||
|
* @param number a number of the same type as the return type. (Used for precision.)
|
||||||
|
* @return the value of log(2) with the appropriate precision.
|
||||||
|
*/
|
||||||
|
private NumberInterface getLog2(NumberImplementation implementation, NumberInterface number) {
|
||||||
|
NumberInterface maxError = number.getMaxError();
|
||||||
|
//NumberInterface errorBound = implementation.instanceForString("1");
|
||||||
|
//We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n)
|
||||||
|
//In the following, a=1/3^n, b=1/4^n, c = 1/n.
|
||||||
|
//a is also an error bound.
|
||||||
|
NumberInterface a = implementation.instanceForString("1"), b = a, c;
|
||||||
|
NumberInterface sum = implementation.instanceForString("0");
|
||||||
|
NumberInterface one = implementation.instanceForString("1");
|
||||||
|
int n = 0;
|
||||||
|
while (a.compareTo(maxError) >= 1) {
|
||||||
|
n++;
|
||||||
|
a = a.divide(implementation.instanceForString("3"));
|
||||||
|
b = b.divide(implementation.instanceForString("4"));
|
||||||
|
c = one.divide(implementation.instanceForString(Integer.toString(n)));
|
||||||
|
sum = sum.add(a.add(b).multiply(c));
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Gets a random number smaller or equal to the given number's integer value.
|
||||||
|
*/
|
||||||
|
public static final NumberFunction FUNCTION_RAND_INT = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return context.getInheritedNumberImplementation().instanceForString(Long.toString(Math.round(Math.random() * params[0].floor().intValue())));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The caret / pow operator, ^
|
||||||
|
*/
|
||||||
|
public static final NumberOperator OP_CARET = new OperatorCaret();
|
||||||
|
/**
|
||||||
|
* The square root function.
|
||||||
|
*/
|
||||||
|
public static final NumberFunction FUNCTION_SQRT = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return OP_CARET.apply(context, params[0], context.getInheritedNumberImplementation().instanceForString(".5"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final HashMap<NumberImplementation, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* The exponential function, exp(1) = e^1 = 2.71...
|
||||||
|
*/
|
||||||
|
public static final NumberFunction FUNCTION_EXP = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||||
|
NumberInterface maxError = params[0].getMaxError();
|
||||||
|
int n = 0;
|
||||||
|
if (params[0].signum() < 0) {
|
||||||
|
NumberInterface[] negatedParams = {params[0].negate()};
|
||||||
|
return implementation.instanceForString("1").divide(applyInternal(context, negatedParams));
|
||||||
|
} else {
|
||||||
|
//We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!.
|
||||||
|
//right and left refer to lhs and rhs in the above inequality.
|
||||||
|
NumberInterface sum = implementation.instanceForString("1");
|
||||||
|
NumberInterface nextNumerator = params[0];
|
||||||
|
NumberInterface left = params[0].multiply(implementation.instanceForString("3").intPow(params[0].ceiling().intValue())), right = maxError;
|
||||||
|
do {
|
||||||
|
sum = sum.add(nextNumerator.divide(factorial(implementation, n + 1)));
|
||||||
|
n++;
|
||||||
|
nextNumerator = nextNumerator.multiply(params[0]);
|
||||||
|
left = left.multiply(params[0]);
|
||||||
|
NumberInterface nextN = implementation.instanceForString(Integer.toString(n + 1));
|
||||||
|
right = right.multiply(nextN);
|
||||||
|
//System.out.println(left + ", " + right);
|
||||||
|
}
|
||||||
|
while (left.compareTo(right) > 0);
|
||||||
|
//System.out.println(n+1);
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The sine function (the argument is interpreted in radians).
|
||||||
|
*/
|
||||||
|
public final NumberFunction functionSin = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||||
|
NumberInterface pi = piFor(params[0].getClass());
|
||||||
|
NumberInterface twoPi = pi.multiply(implementation.instanceForString("2"));
|
||||||
|
NumberInterface theta = getSmallAngle(context, params[0], pi);
|
||||||
|
//System.out.println(theta);
|
||||||
|
if (theta.compareTo(pi.multiply(implementation.instanceForString("1.5"))) >= 0) {
|
||||||
|
theta = theta.subtract(twoPi);
|
||||||
|
} else if (theta.compareTo(pi.divide(implementation.instanceForString("2"))) > 0) {
|
||||||
|
theta = pi.subtract(theta);
|
||||||
|
}
|
||||||
|
//System.out.println(theta);
|
||||||
|
return sinTaylor(context, theta);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The cosine function (the argument is in radians).
|
||||||
|
*/
|
||||||
|
public final NumberFunction functionCos = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return functionSin.apply(context, piFor(params[0].getClass()).divide(context.getInheritedNumberImplementation().instanceForString("2"))
|
||||||
|
.subtract(params[0]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The tangent function (the argument is in radians).
|
||||||
|
*/
|
||||||
|
public final NumberFunction functionTan = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return functionSin.apply(context, params[0]).divide(functionCos.apply(context, params[0]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The secant function (the argument is in radians).
|
||||||
|
*/
|
||||||
|
public final NumberFunction functionSec = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return context.getInheritedNumberImplementation().instanceForString("1").divide(functionCos.apply(context, params[0]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The cosecant function (the argument is in radians).
|
||||||
|
*/
|
||||||
|
public final NumberFunction functionCsc = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return context.getInheritedNumberImplementation().instanceForString("1").divide(functionSin.apply(context, params[0]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The cotangent function (the argument is in radians).
|
||||||
|
*/
|
||||||
|
public final NumberFunction functionCot = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return functionCos.apply(context, params[0]).divide(functionSin.apply(context, params[0]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The arcsine function (return type in radians).
|
||||||
|
*/
|
||||||
|
public final NumberFunction functionArcsin = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1
|
||||||
|
&& FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||||
|
if (FUNCTION_ABS.apply(context, params[0]).compareTo(implementation.instanceForString(".8")) >= 0) {
|
||||||
|
NumberInterface[] newParams = {FUNCTION_SQRT.apply(context, implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
|
||||||
|
return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
|
||||||
|
.subtract(applyInternal(context, newParams)).multiply(implementation.instanceForString(Integer.toString(params[0].signum())));
|
||||||
|
}
|
||||||
|
NumberInterface currentTerm = params[0], sum = currentTerm,
|
||||||
|
multiplier = currentTerm.multiply(currentTerm), summandBound = sum.getMaxError().multiply(implementation.instanceForString("1").subtract(multiplier)),
|
||||||
|
power = currentTerm, coefficient = implementation.instanceForString("1");
|
||||||
|
int exponent = 1;
|
||||||
|
while (FUNCTION_ABS.apply(context, currentTerm).compareTo(summandBound) > 0) {
|
||||||
|
exponent += 2;
|
||||||
|
power = power.multiply(multiplier);
|
||||||
|
coefficient = coefficient.multiply(implementation.instanceForString(Integer.toString(exponent - 2)))
|
||||||
|
.divide(implementation.instanceForString(Integer.toString(exponent - 1)));
|
||||||
|
currentTerm = power.multiply(coefficient).divide(implementation.instanceForString(Integer.toString(exponent)));
|
||||||
|
sum = sum.add(currentTerm);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The arccosine function.
|
||||||
|
*/
|
||||||
|
public final NumberFunction functionArccos = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1 && FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return piFor(params[0].getClass()).divide(context.getInheritedNumberImplementation().instanceForString("2"))
|
||||||
|
.subtract(functionArcsin.apply(context, params));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The arccosecant function.
|
||||||
|
*/
|
||||||
|
public final NumberFunction functionArccsc = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1 && FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
NumberInterface[] reciprocalParamArr = {context.getInheritedNumberImplementation().instanceForString("1").divide(params[0])};
|
||||||
|
return functionArcsin.apply(context, reciprocalParamArr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The arcsecant function.
|
||||||
|
*/
|
||||||
|
public final NumberFunction functionArcsec = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1 && FUNCTION_ABS.apply(context, params[0]).compareTo(context.getInheritedNumberImplementation().instanceForString("1")) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
NumberInterface[] reciprocalParamArr = {context.getInheritedNumberImplementation().instanceForString("1").divide(params[0])};
|
||||||
|
return functionArccos.apply(context, reciprocalParamArr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The arctangent function.
|
||||||
|
*/
|
||||||
|
public final NumberFunction functionArctan = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
NumberImplementation implementation = context.getInheritedNumberImplementation();
|
||||||
|
if (params[0].signum() == -1) {
|
||||||
|
NumberInterface[] negatedParams = {params[0].negate()};
|
||||||
|
return applyInternal(context, negatedParams).negate();
|
||||||
|
}
|
||||||
|
if (params[0].compareTo(implementation.instanceForString("1")) > 0) {
|
||||||
|
NumberInterface[] reciprocalParams = {implementation.instanceForString("1").divide(params[0])};
|
||||||
|
return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
|
||||||
|
.subtract(applyInternal(context, reciprocalParams));
|
||||||
|
}
|
||||||
|
if (params[0].compareTo(implementation.instanceForString("1")) == 0) {
|
||||||
|
return piFor(params[0].getClass()).divide(implementation.instanceForString("4"));
|
||||||
|
}
|
||||||
|
if (params[0].compareTo(implementation.instanceForString(".9")) >= 0) {
|
||||||
|
NumberInterface[] newParams = {params[0].multiply(implementation.instanceForString("2"))
|
||||||
|
.divide(implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
|
||||||
|
return applyInternal(context, newParams).divide(implementation.instanceForString("2"));
|
||||||
|
}
|
||||||
|
NumberInterface currentPower = params[0], currentTerm = currentPower, sum = currentTerm,
|
||||||
|
maxError = params[0].getMaxError(), multiplier = currentPower.multiply(currentPower).negate();
|
||||||
|
int n = 1;
|
||||||
|
while (FUNCTION_ABS.apply(context, currentTerm).compareTo(maxError) > 0) {
|
||||||
|
n += 2;
|
||||||
|
currentPower = currentPower.multiply(multiplier);
|
||||||
|
currentTerm = currentPower.divide(implementation.instanceForString(Integer.toString(n)));
|
||||||
|
sum = sum.add(currentTerm);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The arccotangent function. Range: (0, pi).
|
||||||
|
*/
|
||||||
|
public final NumberFunction functionArccot = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return piFor(params[0].getClass()).divide(context.getInheritedNumberImplementation().instanceForString("2"))
|
||||||
|
.subtract(functionArctan.apply(context, params));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public StandardPlugin(PluginManager manager) {
|
||||||
|
super(manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factorial function that uses memoization for each number class; it efficiently
|
||||||
|
* computes factorials of non-negative integers.
|
||||||
|
*
|
||||||
|
* @param implementation type of number to return.
|
||||||
|
* @param n non-negative integer.
|
||||||
|
* @return a number of numClass with value n factorial.
|
||||||
|
*/
|
||||||
|
synchronized public static NumberInterface factorial(NumberImplementation implementation, int n) {
|
||||||
|
if (!FACTORIAL_LISTS.containsKey(implementation)) {
|
||||||
|
FACTORIAL_LISTS.put(implementation, new ArrayList<>());
|
||||||
|
FACTORIAL_LISTS.get(implementation).add(implementation.instanceForString("1"));
|
||||||
|
FACTORIAL_LISTS.get(implementation).add(implementation.instanceForString("1"));
|
||||||
|
}
|
||||||
|
ArrayList<NumberInterface> list = FACTORIAL_LISTS.get(implementation);
|
||||||
|
if (n >= list.size()) {
|
||||||
|
while (list.size() < n + 16) {
|
||||||
|
list.add(list.get(list.size() - 1).multiply(implementation.instanceForString(Integer.toString(list.size()))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list.get(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of the Taylor series for sin (centered at 0) at x.
|
||||||
|
*
|
||||||
|
* @param x where the series is evaluated.
|
||||||
|
* @return the value of the series
|
||||||
|
*/
|
||||||
|
private static NumberInterface sinTaylor(PluginEvaluationContext context, NumberInterface x) {
|
||||||
|
NumberInterface power = x, multiplier = x.multiply(x).negate(), currentTerm, sum = x;
|
||||||
|
NumberInterface maxError = x.getMaxError();
|
||||||
|
int n = 1;
|
||||||
|
do {
|
||||||
|
n += 2;
|
||||||
|
power = power.multiply(multiplier);
|
||||||
|
currentTerm = power.divide(factorial(context.getInheritedNumberImplementation(), n));
|
||||||
|
sum = sum.add(currentTerm);
|
||||||
|
} while (FUNCTION_ABS.apply(context, currentTerm).compareTo(maxError) > 0);
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an equivalent angle in the interval [0, 2pi)
|
||||||
|
*
|
||||||
|
* @param phi an angle (in radians).
|
||||||
|
* @return theta in [0, 2pi) that differs from phi by a multiple of 2pi.
|
||||||
|
*/
|
||||||
|
private static NumberInterface getSmallAngle(PluginEvaluationContext context, NumberInterface phi, NumberInterface pi) {
|
||||||
|
NumberInterface twoPi = pi.multiply(context.getInheritedNumberImplementation().instanceForString("2"));
|
||||||
|
NumberInterface theta = FUNCTION_ABS.apply(context, phi).subtract(twoPi
|
||||||
|
.multiply(FUNCTION_ABS.apply(context, phi).divide(twoPi).floor())); //Now theta is in [0, 2pi).
|
||||||
|
if (phi.signum() < 0) {
|
||||||
|
theta = twoPi.subtract(theta);
|
||||||
|
}
|
||||||
|
return theta;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
registerNumberImplementation("naive", IMPLEMENTATION_NAIVE);
|
||||||
|
registerNumberImplementation("precise", IMPLEMENTATION_PRECISE);
|
||||||
|
|
||||||
|
registerOperator("+", OP_ADD);
|
||||||
|
registerOperator("-", OP_SUBTRACT);
|
||||||
|
registerOperator("`", OP_NEGATE);
|
||||||
|
registerOperator("*", OP_MULTIPLY);
|
||||||
|
registerOperator("/", OP_DIVIDE);
|
||||||
|
registerOperator("^", OP_CARET);
|
||||||
|
registerOperator("!", OP_FACTORIAL);
|
||||||
|
|
||||||
|
registerTreeValueOperator("=", OP_SET);
|
||||||
|
registerTreeValueOperator(":=", OP_DEFINE);
|
||||||
|
|
||||||
|
registerOperator("nPr", OP_NPR);
|
||||||
|
registerOperator("nCr", OP_NCR);
|
||||||
|
|
||||||
|
registerFunction("abs", FUNCTION_ABS);
|
||||||
|
registerFunction("exp", FUNCTION_EXP);
|
||||||
|
registerFunction("ln", FUNCTION_LN);
|
||||||
|
registerFunction("sqrt", FUNCTION_SQRT);
|
||||||
|
|
||||||
|
registerFunction("sin", functionSin);
|
||||||
|
registerFunction("cos", functionCos);
|
||||||
|
registerFunction("tan", functionTan);
|
||||||
|
registerFunction("sec", functionSec);
|
||||||
|
registerFunction("csc", functionCsc);
|
||||||
|
registerFunction("cot", functionCot);
|
||||||
|
|
||||||
|
registerFunction("arcsin", functionArcsin);
|
||||||
|
registerFunction("arccos", functionArccos);
|
||||||
|
registerFunction("arctan", functionArctan);
|
||||||
|
registerFunction("arcsec", functionArcsec);
|
||||||
|
registerFunction("arccsc", functionArccsc);
|
||||||
|
registerFunction("arccot", functionArccot);
|
||||||
|
|
||||||
|
registerFunction("random_int", FUNCTION_RAND_INT);
|
||||||
|
|
||||||
|
registerDocumentation(new Documentation("abs", "Absolute Value", "Finds the distance " +
|
||||||
|
"from zero of a number.", "Given a number, this function finds the distance form " +
|
||||||
|
"zero of a number, effectively turning negative numbers into positive ones.\n\n" +
|
||||||
|
"Example: abs(-2) -> 2", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("exp", "Exponentiate", "Brings e to the given power.",
|
||||||
|
"This function evaluates e to the power of the given value, and is the inverse " +
|
||||||
|
"of the natural logarithm.\n\n" +
|
||||||
|
"Example: exp(1) -> 2.718...", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("ln", "Natural Logarithm", "Gets the natural " +
|
||||||
|
"logarithm of the given value.", "The natural logarithm of a number is " +
|
||||||
|
"the power that e has to be brought to to be equal to the number.\n\n" +
|
||||||
|
"Example: ln(2.718) -> 1", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("sqrt", "Square Root", "Finds the square root " +
|
||||||
|
"of the number.", "A square root a of a number is defined as the non-negative a such that a times a is equal " +
|
||||||
|
"to that number.\n\n" +
|
||||||
|
"Example: sqrt(4) -> 2", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("sin", "Sine", "Computes the sine of the given angle, " +
|
||||||
|
"in radians.", "Example: sin(pi/6) -> 0.5", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("cos", "Cosine", "Computes the cosine of the given angle, " +
|
||||||
|
"in radians.", "Example: cos(pi/6) -> 0.866... (the exact result is sqrt(3)/2)", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("tan", "Tangent", "Computes the tangent of the given angle, " +
|
||||||
|
"in radians.", "Example: tan(pi/6) -> 0.577... (the exact result is 1/sqrt(3))", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("sec", "Secant", "Computes the secant of the given angle, " +
|
||||||
|
"in radians.", "Example: sec(pi/6) -> 1.154... (the exact result is 2/sqrt(3))", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("csc", "Cosecant", "Computes the cosecant of the given angle, " +
|
||||||
|
"in radians.", "Example: csc(pi/6) -> 2", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("cot", "Cotangent", "Computes the cotangent of the given angle, " +
|
||||||
|
"in radians.", "Example: cot(pi/6) -> 1.732... (the exact result is sqrt(3))", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("random_int", "Random Integer", "Generates a random integer [0, n].",
|
||||||
|
"Generates a pseudorandom number using the standard JVM random mechanism, keeping it less than or " +
|
||||||
|
"equal to the given number.\n\n" +
|
||||||
|
"Example: random_int(5) -> 4\n" +
|
||||||
|
"random_int(5) -> 3\n" +
|
||||||
|
"random_int(5) -> 3\n", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("arcsin", "Arcsine", "Computes the arcsine of x. (The result is in radians.)",
|
||||||
|
"Example: arcsin(0.5) -> 0.523... (the exact result is pi/6)", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("arccos", "Arccosine", "Computes the arccosine of x. (The result is in radians.)",
|
||||||
|
"Example: arccos(0.5) -> 1.047... (the exact result is pi/3)", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("arctan", "Arctangent", "Computes the arctangent of x. (The result is in radians.)",
|
||||||
|
"Example: arctan(1) -> 0.785... (the exact result is pi/4)", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("arcsec", "Arcsecant", "Computes the arcsecant of x. (The result is in radians.)",
|
||||||
|
"Example: arcsec(2) -> 1.047... (the exact result is pi/3)", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("arccsc", "Arccosecant", "Computes the arcscosecant of x. (The result is in radians.)",
|
||||||
|
"Example: arccsc(2) -> 0.523... (the exact result is pi/6)", DocumentationType.FUNCTION));
|
||||||
|
registerDocumentation(new Documentation("arccot", "Arccotangent", "Computes the arccotangent of x. (The result is in radians," +
|
||||||
|
" in the range (0, pi).)",
|
||||||
|
"Example: arccot(0) -> 1.570... (the exact result is pi/2)", DocumentationType.FUNCTION));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
FACTORIAL_LISTS.clear();
|
||||||
|
}
|
||||||
|
}
|
112
core/src/main/kotlin/org/nwapw/abacus/Abacus.kt
Normal file
112
core/src/main/kotlin/org/nwapw/abacus/Abacus.kt
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package org.nwapw.abacus
|
||||||
|
|
||||||
|
import org.nwapw.abacus.config.Configuration
|
||||||
|
import org.nwapw.abacus.context.EvaluationContext
|
||||||
|
import org.nwapw.abacus.context.MutableEvaluationContext
|
||||||
|
import org.nwapw.abacus.number.promotion.PromotionManager
|
||||||
|
import org.nwapw.abacus.parsing.TreeBuilder
|
||||||
|
import org.nwapw.abacus.parsing.standard.LexerTokenizer
|
||||||
|
import org.nwapw.abacus.parsing.standard.ShuntingYardParser
|
||||||
|
import org.nwapw.abacus.plugin.PluginManager
|
||||||
|
import org.nwapw.abacus.plugin.standard.StandardPlugin
|
||||||
|
import org.nwapw.abacus.tree.nodes.TreeNode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core class to handle all mathematics.
|
||||||
|
*
|
||||||
|
* The main calculator class. This is responsible
|
||||||
|
* for piecing together all of the components, allowing
|
||||||
|
* their interaction with each other.
|
||||||
|
*
|
||||||
|
* @property configuration the configuration to use.
|
||||||
|
*/
|
||||||
|
class Abacus(val configuration: Configuration) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tokenizer used to convert strings into tokens.
|
||||||
|
*/
|
||||||
|
private val tokenizer = LexerTokenizer()
|
||||||
|
/**
|
||||||
|
* Parser the parser used to convert tokens into trees.
|
||||||
|
*/
|
||||||
|
private val parser = ShuntingYardParser()
|
||||||
|
/**
|
||||||
|
* The plugin manager used to handle loading and unloading plugins.
|
||||||
|
*/
|
||||||
|
val pluginManager = PluginManager(this)
|
||||||
|
/**
|
||||||
|
* The tree builder that handles the conversion of strings into trees.
|
||||||
|
*/
|
||||||
|
val treeBuilder = TreeBuilder(tokenizer, parser)
|
||||||
|
/**
|
||||||
|
* The promotion manager used to convert between number implementations.
|
||||||
|
*/
|
||||||
|
val promotionManager = PromotionManager(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hidden, mutable implementation of the context.
|
||||||
|
*/
|
||||||
|
private val mutableContext = MutableEvaluationContext(numberImplementation = StandardPlugin.IMPLEMENTATION_NAIVE, abacus = this)
|
||||||
|
/**
|
||||||
|
* The base context from which calculations are started.
|
||||||
|
*/
|
||||||
|
val context: EvaluationContext
|
||||||
|
get() = mutableContext
|
||||||
|
|
||||||
|
init {
|
||||||
|
pluginManager.addListener(tokenizer)
|
||||||
|
pluginManager.addListener(parser)
|
||||||
|
pluginManager.addListener(promotionManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads the Abacus core.
|
||||||
|
*/
|
||||||
|
fun reload(){
|
||||||
|
with(mutableContext){
|
||||||
|
clearVariables()
|
||||||
|
clearDefinitions()
|
||||||
|
}
|
||||||
|
pluginManager.reload()
|
||||||
|
mutableContext.numberImplementation = pluginManager.numberImplementationFor(configuration.numberImplementation)
|
||||||
|
?: StandardPlugin.IMPLEMENTATION_NAIVE
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Merges the current context with the provided one, updating
|
||||||
|
* variables and the like.
|
||||||
|
* @param context the context to apply.
|
||||||
|
*/
|
||||||
|
fun applyToContext(context: EvaluationContext){
|
||||||
|
mutableContext.apply(context)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Parses a string into a tree structure using the main
|
||||||
|
* tree builder.
|
||||||
|
*
|
||||||
|
* @param input the input string to parse
|
||||||
|
* @return the resulting tree, null if the tree builder or the produced tree are null.
|
||||||
|
*/
|
||||||
|
fun parseString(input: String): TreeNode = treeBuilder.fromString(input)
|
||||||
|
/**
|
||||||
|
* Evaluates the given tree.
|
||||||
|
*
|
||||||
|
* @param tree the tree to reduce, must not be null.
|
||||||
|
* @return the evaluation result.
|
||||||
|
*/
|
||||||
|
fun evaluateTree(tree: TreeNode): EvaluationResult {
|
||||||
|
return evaluateTreeWithContext(tree, context.mutableSubInstance())
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Evaluates the given tree using a different context than
|
||||||
|
* the default one.
|
||||||
|
*
|
||||||
|
* @param tree the tree to reduce, must not be null.
|
||||||
|
* @param context the context to use for the evaluation.
|
||||||
|
* @return the evaluation result.
|
||||||
|
*/
|
||||||
|
fun evaluateTreeWithContext(tree: TreeNode, context: MutableEvaluationContext): EvaluationResult {
|
||||||
|
val evaluationValue = tree.reduce(context)
|
||||||
|
return EvaluationResult(evaluationValue, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package org.nwapw.abacus
|
||||||
|
|
||||||
|
import org.nwapw.abacus.context.MutableEvaluationContext
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
data class EvaluationResult(val value: NumberInterface, val resultingContext: MutableEvaluationContext)
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.nwapw.abacus.config
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that holds information that tells Abacus how to behave.
|
||||||
|
*
|
||||||
|
* Configuration stores information about how Abacus should behave, for
|
||||||
|
* instance, what number implementation it should use and what
|
||||||
|
* plugins should be ignored during loading.
|
||||||
|
*
|
||||||
|
* @property numberImplementation the number implementation Abacus should use for loading.
|
||||||
|
* @param disabledPlugins the plugins that should be disabled and not loaded by the plugin manager.
|
||||||
|
*/
|
||||||
|
open class Configuration(var numberImplementation: String = "<default>", disabledPlugins: Array<String> = emptyArray()) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of disabled plugins that should be ignored by the plugin manager.
|
||||||
|
*/
|
||||||
|
val disabledPlugins = disabledPlugins.toMutableSet()
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.nwapw.abacus.context
|
||||||
|
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A delegate to accumulate a collection of elements in a [EvaluationContext] hierarchy.
|
||||||
|
*
|
||||||
|
* ChainAccumulateDelegate is similar to the [ChainSearchDelegate], however, it operates only on collections.
|
||||||
|
* Instead of returning the most recent collection, it merges them into a [Set].
|
||||||
|
*
|
||||||
|
* @param T the type of element in the collection.
|
||||||
|
* @property valueGetter the getter used to access the collection from the context.
|
||||||
|
*/
|
||||||
|
class ChainAccumulateDelegate<out T>(private val valueGetter: EvaluationContext.() -> Collection<T>) {
|
||||||
|
|
||||||
|
operator fun getValue(selfRef: Any, property: KProperty<*>): Set<T> {
|
||||||
|
val set = mutableSetOf<T>()
|
||||||
|
var currentRef: EvaluationContext = selfRef as? EvaluationContext ?: return set
|
||||||
|
while(true) {
|
||||||
|
set.addAll(currentRef.valueGetter())
|
||||||
|
currentRef = currentRef.parent ?: break
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package org.nwapw.abacus.context
|
||||||
|
|
||||||
|
import org.nwapw.abacus.exception.ContextException
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A delegate to search a hierarchy made up of [EvaluationContext].
|
||||||
|
*
|
||||||
|
* ChainSearchDelegate is a variable delegate written specifically for use in [EvaluationContext], because
|
||||||
|
* of its hierarchical structure. Variables not found in the current context are searched
|
||||||
|
* for in its parent, which continues recursively until the context being examined has no parent.
|
||||||
|
* This class assists that logic, which is commonly re-used with different variable types, by calling
|
||||||
|
* [valueGetter] on the current context, then its parent, etc.
|
||||||
|
*
|
||||||
|
* @param V the type of the property to search recursively.
|
||||||
|
* @property valueGetter the getter lambda to access the value from the context.
|
||||||
|
*/
|
||||||
|
class ChainSearchDelegate<out V>(private val valueGetter: EvaluationContext.() -> V?) {
|
||||||
|
|
||||||
|
operator fun getValue(selfRef: Any, property: KProperty<*>): V {
|
||||||
|
var currentRef = selfRef as EvaluationContext
|
||||||
|
var returnedValue = currentRef.valueGetter()
|
||||||
|
while (returnedValue == null) {
|
||||||
|
currentRef = currentRef.parent ?: break
|
||||||
|
returnedValue = currentRef.valueGetter()
|
||||||
|
}
|
||||||
|
return returnedValue ?: throw ContextException("context chain does not contain value")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package org.nwapw.abacus.context
|
||||||
|
|
||||||
|
import org.nwapw.abacus.Abacus
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
import org.nwapw.abacus.plugin.NumberImplementation
|
||||||
|
import org.nwapw.abacus.tree.Reducer
|
||||||
|
import org.nwapw.abacus.tree.nodes.TreeNode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context for the reduction of a [org.nwapw.abacus.tree.TreeNode] into a number.
|
||||||
|
*
|
||||||
|
* The reduction context is used to carry important state information captured at the beginning
|
||||||
|
* of the reduction of an expression, such as the variables and the implementation in use.
|
||||||
|
*
|
||||||
|
* @property parent the parent of this context.
|
||||||
|
* @property numberImplementation the implementation for numbers of this context.
|
||||||
|
* @property abacus the abacus instance used by this reducer.
|
||||||
|
*/
|
||||||
|
abstract class EvaluationContext(val parent: EvaluationContext? = null,
|
||||||
|
open val numberImplementation: NumberImplementation? = null,
|
||||||
|
open val abacus: Abacus? = null): Reducer<NumberInterface> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The map of variables in this context.
|
||||||
|
*/
|
||||||
|
protected val variableMap = mutableMapOf<String, NumberInterface>()
|
||||||
|
/**
|
||||||
|
* The map of definitions in this context.
|
||||||
|
*/
|
||||||
|
protected val definitionMap = mutableMapOf<String, TreeNode>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of all variable names defined in this context.
|
||||||
|
*/
|
||||||
|
val variables: Set<String>
|
||||||
|
get() = variableMap.keys
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of all definition names defined in this context.
|
||||||
|
*/
|
||||||
|
val definitions: Set<String>
|
||||||
|
get() = definitionMap.keys
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The implementation inherited from this context's parent.
|
||||||
|
*/
|
||||||
|
val inheritedNumberImplementation: NumberImplementation
|
||||||
|
by ChainSearchDelegate { numberImplementation }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Abacus instance inherited from this context's parent.
|
||||||
|
*/
|
||||||
|
val inheritedAbacus: Abacus
|
||||||
|
by ChainSearchDelegate { abacus }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of all variables in this context and its parents.
|
||||||
|
*/
|
||||||
|
val inheritedVariables: Set<String> by ChainAccumulateDelegate { variables }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of all definition in this context and its parents.
|
||||||
|
*/
|
||||||
|
val inheritedDefinitions: Set<String> by ChainAccumulateDelegate { definitions }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new child instance of this context that is mutable.
|
||||||
|
* @return the new child instance.
|
||||||
|
*/
|
||||||
|
fun mutableSubInstance(): MutableEvaluationContext = MutableEvaluationContext(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a variable stored in this context.
|
||||||
|
*/
|
||||||
|
fun getVariable(name: String): NumberInterface? {
|
||||||
|
return variableMap[name] ?: parent?.getVariable(name)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Gets the definition stored in this context.
|
||||||
|
*/
|
||||||
|
fun getDefinition(name: String): TreeNode? {
|
||||||
|
return definitionMap[name] ?: parent?.getDefinition(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package org.nwapw.abacus.context
|
||||||
|
|
||||||
|
import org.nwapw.abacus.Abacus
|
||||||
|
import org.nwapw.abacus.exception.NumberReducerException
|
||||||
|
import org.nwapw.abacus.exception.ReductionException
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
import org.nwapw.abacus.plugin.NumberImplementation
|
||||||
|
import org.nwapw.abacus.tree.nodes.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reduction context that is mutable.
|
||||||
|
*
|
||||||
|
* @param parent the parent of this context.
|
||||||
|
* @param numberImplementation the number implementation used in this context.
|
||||||
|
* @param abacus the abacus instance used.
|
||||||
|
*/
|
||||||
|
class MutableEvaluationContext(parent: EvaluationContext? = null,
|
||||||
|
numberImplementation: NumberImplementation? = null,
|
||||||
|
abacus: Abacus? = null) :
|
||||||
|
PluginEvaluationContext(parent, numberImplementation, abacus) {
|
||||||
|
|
||||||
|
override var numberImplementation: NumberImplementation? = super.numberImplementation
|
||||||
|
override var abacus: Abacus? = super.abacus
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes data stored in the [other] context over data stored in this one.
|
||||||
|
* @param other the context from which to copy data.
|
||||||
|
*/
|
||||||
|
fun apply(other: EvaluationContext) {
|
||||||
|
if(other.numberImplementation != null) numberImplementation = other.numberImplementation
|
||||||
|
for(name in other.variables) {
|
||||||
|
setVariable(name, other.getVariable(name) ?: continue)
|
||||||
|
}
|
||||||
|
for(name in other.definitions) {
|
||||||
|
setDefinition(name, other.getDefinition(name) ?: continue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reduceNode(treeNode: TreeNode, vararg children: Any): NumberInterface {
|
||||||
|
val oldNumberImplementation = numberImplementation
|
||||||
|
val abacus = inheritedAbacus
|
||||||
|
val promotionManager = abacus.promotionManager
|
||||||
|
val toReturn = when(treeNode){
|
||||||
|
is NumberNode -> {
|
||||||
|
inheritedNumberImplementation.instanceForString(treeNode.number)
|
||||||
|
}
|
||||||
|
is VariableNode -> {
|
||||||
|
val variable = getVariable(treeNode.variable)
|
||||||
|
if(variable != null) return variable
|
||||||
|
val definition = getDefinition(treeNode.variable)
|
||||||
|
if(definition != null) return definition.reduce(this)
|
||||||
|
throw NumberReducerException("variable is not defined.")
|
||||||
|
}
|
||||||
|
is NumberUnaryNode -> {
|
||||||
|
val child = children[0] as NumberInterface
|
||||||
|
numberImplementation = abacus.pluginManager.interfaceImplementationFor(child.javaClass)
|
||||||
|
abacus.pluginManager.operatorFor(treeNode.operation)
|
||||||
|
.apply(this, child)
|
||||||
|
}
|
||||||
|
is NumberBinaryNode -> {
|
||||||
|
val left = children[0] as NumberInterface
|
||||||
|
val right = children[1] as NumberInterface
|
||||||
|
val promotionResult = promotionManager.promote(left, right)
|
||||||
|
numberImplementation = promotionResult.promotedTo
|
||||||
|
abacus.pluginManager.operatorFor(treeNode.operation).apply(this, *promotionResult.items)
|
||||||
|
}
|
||||||
|
is NumberFunctionNode -> {
|
||||||
|
val promotionResult = promotionManager
|
||||||
|
.promote(*children.map { it as NumberInterface }.toTypedArray())
|
||||||
|
numberImplementation = promotionResult.promotedTo
|
||||||
|
abacus.pluginManager.functionFor(treeNode.callTo).apply(this, *promotionResult.items)
|
||||||
|
}
|
||||||
|
is TreeValueUnaryNode -> {
|
||||||
|
abacus.pluginManager.treeValueOperatorFor(treeNode.operation)
|
||||||
|
.apply(this, treeNode.applyTo)
|
||||||
|
}
|
||||||
|
is TreeValueBinaryNode -> {
|
||||||
|
abacus.pluginManager.treeValueOperatorFor(treeNode.operation)
|
||||||
|
.apply(this, treeNode.left, treeNode.right)
|
||||||
|
}
|
||||||
|
is TreeValueFunctionNode -> {
|
||||||
|
abacus.pluginManager.treeValueFunctionFor(treeNode.callTo)
|
||||||
|
.apply(this, *treeNode.children.toTypedArray())
|
||||||
|
}
|
||||||
|
else -> throw ReductionException("unrecognized tree node.")
|
||||||
|
}
|
||||||
|
numberImplementation = oldNumberImplementation
|
||||||
|
return toReturn
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.nwapw.abacus.context
|
||||||
|
|
||||||
|
import org.nwapw.abacus.Abacus
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
import org.nwapw.abacus.plugin.NumberImplementation
|
||||||
|
import org.nwapw.abacus.tree.nodes.TreeNode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An evaluation context with limited mutability.
|
||||||
|
*
|
||||||
|
* An evaluation context that is mutable but in a limited way, that is, not allowing the modifications
|
||||||
|
* of variables whose changes might cause issues outside of the function. An example of this would be
|
||||||
|
* the modification of the [numberImplementation], which would cause code paths such as the parsing
|
||||||
|
* of NumberNodes to produce a different type of number than if the function did not run, whcih is unacceptable.
|
||||||
|
*
|
||||||
|
* @param parent the parent of this context.
|
||||||
|
* @param numberImplementation the number implementation used in this context.
|
||||||
|
* @param abacus the abacus instance used.
|
||||||
|
*/
|
||||||
|
abstract class PluginEvaluationContext(parent: EvaluationContext? = null,
|
||||||
|
numberImplementation: NumberImplementation? = null,
|
||||||
|
abacus: Abacus? = null) :
|
||||||
|
EvaluationContext(parent, numberImplementation, abacus) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a variable to a certain [value].
|
||||||
|
* @param name the name of the variable.
|
||||||
|
* @param value the value of the variable.
|
||||||
|
*/
|
||||||
|
fun setVariable(name: String, value: NumberInterface) {
|
||||||
|
variableMap[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a definition to a certain [value].
|
||||||
|
* @param name the name of the definition.
|
||||||
|
* @param value the value of the definition.
|
||||||
|
*/
|
||||||
|
fun setDefinition(name: String, value: TreeNode) {
|
||||||
|
definitionMap[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the variables defined in this context.
|
||||||
|
*/
|
||||||
|
fun clearVariables(){
|
||||||
|
variableMap.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the definitions defined in this context.
|
||||||
|
*/
|
||||||
|
fun clearDefinitions(){
|
||||||
|
definitionMap.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
45
core/src/main/kotlin/org/nwapw/abacus/function/Applicable.kt
Normal file
45
core/src/main/kotlin/org/nwapw/abacus/function/Applicable.kt
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package org.nwapw.abacus.function
|
||||||
|
|
||||||
|
import org.nwapw.abacus.context.MutableEvaluationContext
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext
|
||||||
|
import org.nwapw.abacus.exception.DomainException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that can be applied to arguments.
|
||||||
|
*
|
||||||
|
* Applicable is a class that represents something that can be applied to one or more
|
||||||
|
* arguments of the same type, and returns a single value from that application.
|
||||||
|
* @param <T> the type of the parameters passed to this applicable.
|
||||||
|
* @param <O> the return type of the applicable.
|
||||||
|
*/
|
||||||
|
interface Applicable<in T : Any, out O : Any> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given applicable can be used with the given parameters.
|
||||||
|
* @param params the parameter array to verify for compatibility.
|
||||||
|
* @return whether the array can be used with applyInternal.
|
||||||
|
*/
|
||||||
|
fun matchesParams(context: PluginEvaluationContext, params: Array<out T>): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the applicable object to the given parameters,
|
||||||
|
* without checking for compatibility.
|
||||||
|
* @param params the parameters to apply to.
|
||||||
|
* @return the result of the application.
|
||||||
|
*/
|
||||||
|
fun applyInternal(context: PluginEvaluationContext, params: Array<out T>): O
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the parameters can be used with this applicable, returns
|
||||||
|
* the result of the application of the applicable to the parameters.
|
||||||
|
* Otherwise, returns null.
|
||||||
|
* @param params the parameters to apply to.
|
||||||
|
* @return the result of the operation, or null if parameters do not match.
|
||||||
|
*/
|
||||||
|
fun apply(context: PluginEvaluationContext, vararg params: T): O {
|
||||||
|
if (!matchesParams(context, params))
|
||||||
|
throw DomainException("parameters do not match function requirements.")
|
||||||
|
return applyInternal(context, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.nwapw.abacus.function
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data class used for storing information about a function.
|
||||||
|
*
|
||||||
|
* The Documentation class holds the information necessary to display the information
|
||||||
|
* about a function to the user.
|
||||||
|
*
|
||||||
|
* @param codeName the name of the function as it occurs in code.
|
||||||
|
* @param name the name of the function in English.
|
||||||
|
* @param description the short description of this function.
|
||||||
|
* @param longDescription the full description of this function.
|
||||||
|
* @param type the things this documentation maps to.
|
||||||
|
*/
|
||||||
|
data class Documentation(val codeName: String, val name: String,
|
||||||
|
val description: String, val longDescription: String,
|
||||||
|
val type: DocumentationType) {
|
||||||
|
|
||||||
|
fun matches(other: String): Boolean {
|
||||||
|
return codeName.toLowerCase().contains(other.toLowerCase()) ||
|
||||||
|
name.toLowerCase().contains(other.toLowerCase()) ||
|
||||||
|
description.toLowerCase().contains(other.toLowerCase()) ||
|
||||||
|
longDescription.toLowerCase().contains(other.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
13
core/src/main/kotlin/org/nwapw/abacus/function/Operator.kt
Normal file
13
core/src/main/kotlin/org/nwapw/abacus/function/Operator.kt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package org.nwapw.abacus.function
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single operator that can be used by Abacus.
|
||||||
|
*
|
||||||
|
* This is a class that holds the information about a single operator, such as a plus or minus.
|
||||||
|
*
|
||||||
|
* @param associativity the associativity of this operator, used for order of operations;.
|
||||||
|
* @param type the type of this operator, used for parsing (infix / prefix / postfix and binary / unary)
|
||||||
|
* @param precedence the precedence of this operator, used for order of operations.
|
||||||
|
*/
|
||||||
|
open class Operator(val associativity: OperatorAssociativity, val type: OperatorType,
|
||||||
|
val precedence: Int)
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.nwapw.abacus.function.interfaces
|
||||||
|
|
||||||
|
import org.nwapw.abacus.function.Applicable
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that operates on numbers.
|
||||||
|
*
|
||||||
|
* This function takes some number of input NumberInterfaces and returns
|
||||||
|
* another NumberInterface as a result.
|
||||||
|
*/
|
||||||
|
abstract class NumberFunction : Applicable<NumberInterface, NumberInterface>
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.nwapw.abacus.function.interfaces
|
||||||
|
|
||||||
|
import org.nwapw.abacus.function.Applicable
|
||||||
|
import org.nwapw.abacus.function.Operator
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity
|
||||||
|
import org.nwapw.abacus.function.OperatorType
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An operator that operates on NumberImplementations.
|
||||||
|
*
|
||||||
|
* This is simply an alias for Operator<NumberInterface, NumberInterface>.
|
||||||
|
* @param associativity the associativity of the operator.
|
||||||
|
* @param type the type of the operator (binary, unary, etc)
|
||||||
|
* @param precedence the precedence of the operator.
|
||||||
|
*/
|
||||||
|
abstract class NumberOperator(associativity: OperatorAssociativity, type: OperatorType,
|
||||||
|
precedence: Int) :
|
||||||
|
Operator(associativity, type, precedence),
|
||||||
|
Applicable<NumberInterface, NumberInterface>
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.nwapw.abacus.function.interfaces
|
||||||
|
|
||||||
|
import org.nwapw.abacus.function.Applicable
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
import org.nwapw.abacus.tree.nodes.TreeNode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that operates on trees.
|
||||||
|
*
|
||||||
|
* A function that operates on parse tree nodes instead of on already simplified numbers.
|
||||||
|
* Despite this, it returns a number, not a tree.
|
||||||
|
*/
|
||||||
|
abstract class TreeValueFunction : Applicable<TreeNode, NumberInterface>
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.nwapw.abacus.function.interfaces
|
||||||
|
|
||||||
|
import org.nwapw.abacus.function.Applicable
|
||||||
|
import org.nwapw.abacus.function.Operator
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity
|
||||||
|
import org.nwapw.abacus.function.OperatorType
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
import org.nwapw.abacus.tree.nodes.TreeNode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An operator that operates on trees.
|
||||||
|
*
|
||||||
|
* This operator operates on parse trees, returning, however a number.
|
||||||
|
* @param associativity the associativity of the operator.
|
||||||
|
* @param type the type of the operator (infix, postfix, etc)
|
||||||
|
* @param precedence the precedence of the operator.
|
||||||
|
*/
|
||||||
|
abstract class TreeValueOperator(associativity: OperatorAssociativity, type: OperatorType,
|
||||||
|
precedence: Int) :
|
||||||
|
Operator(associativity, type, precedence),
|
||||||
|
Applicable<TreeNode, NumberInterface>
|
276
core/src/main/kotlin/org/nwapw/abacus/number/NumberInterface.kt
Normal file
276
core/src/main/kotlin/org/nwapw/abacus/number/NumberInterface.kt
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
package org.nwapw.abacus.number
|
||||||
|
|
||||||
|
import org.nwapw.abacus.exception.ComputationInterruptedException
|
||||||
|
import org.nwapw.abacus.number.range.NumberRangeBuilder
|
||||||
|
|
||||||
|
abstract class NumberInterface: Comparable<NumberInterface> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the thread was interrupted and
|
||||||
|
* throw an exception to end the computation.
|
||||||
|
*/
|
||||||
|
private fun checkInterrupted(){
|
||||||
|
if(Thread.currentThread().isInterrupted)
|
||||||
|
throw ComputationInterruptedException()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the integer representation of this number, discarding any fractional part,
|
||||||
|
* if int can hold the value.
|
||||||
|
*
|
||||||
|
* @return the integer value of this number.
|
||||||
|
*/
|
||||||
|
abstract fun intValue(): Int
|
||||||
|
/**
|
||||||
|
* Same as Math.signum().
|
||||||
|
*
|
||||||
|
* @return 1 if this number is positive, -1 if this number is negative, 0 if this number is 0.
|
||||||
|
*/
|
||||||
|
abstract fun signum(): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum precision to which this number operates.
|
||||||
|
*/
|
||||||
|
abstract val maxPrecision: Int
|
||||||
|
/**
|
||||||
|
* Returns the smallest error this instance can tolerate depending
|
||||||
|
* on its precision and value.
|
||||||
|
*/
|
||||||
|
abstract val maxError: NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds this number to another, returning
|
||||||
|
* a new number instance.
|
||||||
|
*
|
||||||
|
* @param summand the summand
|
||||||
|
* @return the result of the summation.
|
||||||
|
*/
|
||||||
|
abstract fun addInternal(summand: NumberInterface): NumberInterface
|
||||||
|
/**
|
||||||
|
* Subtracts another number from this number,
|
||||||
|
* a new number instance.
|
||||||
|
*
|
||||||
|
* @param subtrahend the subtrahend.
|
||||||
|
* @return the result of the subtraction.
|
||||||
|
*/
|
||||||
|
abstract fun subtractInternal(subtrahend: NumberInterface): NumberInterface
|
||||||
|
/**
|
||||||
|
* Multiplies this number by another, returning
|
||||||
|
* a new number instance.
|
||||||
|
*
|
||||||
|
* @param multiplier the multiplier
|
||||||
|
* @return the result of the multiplication.
|
||||||
|
*/
|
||||||
|
abstract fun multiplyInternal(multiplier: NumberInterface): NumberInterface
|
||||||
|
/**
|
||||||
|
* Divides this number by another, returning
|
||||||
|
* a new number instance.
|
||||||
|
*
|
||||||
|
* @param divisor the divisor
|
||||||
|
* @return the result of the division.
|
||||||
|
*/
|
||||||
|
abstract fun divideInternal(divisor: NumberInterface): NumberInterface
|
||||||
|
/**
|
||||||
|
* Returns a new instance of this number with
|
||||||
|
* the sign flipped.
|
||||||
|
*
|
||||||
|
* @return the new instance.
|
||||||
|
*/
|
||||||
|
abstract fun negateInternal(): NumberInterface
|
||||||
|
/**
|
||||||
|
* Raises this number to an integer power.
|
||||||
|
*
|
||||||
|
* @param exponent the exponent to which to take the number.
|
||||||
|
* @return the resulting value.
|
||||||
|
*/
|
||||||
|
abstract fun intPowInternal(pow: Int): NumberInterface
|
||||||
|
/**
|
||||||
|
* Returns the least integer greater than or equal to the number.
|
||||||
|
*
|
||||||
|
* @return the least integer greater or equal to the number, if int can hold the value.
|
||||||
|
*/
|
||||||
|
abstract fun ceilingInternal(): NumberInterface
|
||||||
|
/**
|
||||||
|
* Return the greatest integer less than or equal to the number.
|
||||||
|
*
|
||||||
|
* @return the greatest integer smaller or equal the number.
|
||||||
|
*/
|
||||||
|
abstract fun floorInternal(): NumberInterface
|
||||||
|
/**
|
||||||
|
* Returns the fractional part of the number.
|
||||||
|
*
|
||||||
|
* @return the fractional part of the number.
|
||||||
|
*/
|
||||||
|
abstract fun fractionalPartInternal(): NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
fun add(summand: NumberInterface): NumberInterface {
|
||||||
|
checkInterrupted()
|
||||||
|
return addInternal(summand)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
fun subtract(subtrahend: NumberInterface): NumberInterface {
|
||||||
|
checkInterrupted()
|
||||||
|
return subtractInternal(subtrahend)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
fun multiply(multiplier: NumberInterface): NumberInterface {
|
||||||
|
checkInterrupted()
|
||||||
|
return multiplyInternal(multiplier)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
fun divide(divisor: NumberInterface): NumberInterface {
|
||||||
|
checkInterrupted()
|
||||||
|
return divideInternal(divisor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
fun negate(): NumberInterface {
|
||||||
|
checkInterrupted()
|
||||||
|
return negateInternal()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
fun intPow(exponent: Int): NumberInterface {
|
||||||
|
checkInterrupted()
|
||||||
|
return intPowInternal(exponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
fun ceiling(): NumberInterface {
|
||||||
|
checkInterrupted()
|
||||||
|
return ceilingInternal()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the greatest integer less than or equal to the number.
|
||||||
|
* Also, checks if the thread has been interrupted, and if so, throws
|
||||||
|
* an exception.
|
||||||
|
*
|
||||||
|
* @return the greatest int smaller than or equal to the number.
|
||||||
|
*/
|
||||||
|
fun floor(): NumberInterface {
|
||||||
|
checkInterrupted()
|
||||||
|
return floorInternal()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fractional part of the number, specifically x - floor(x).
|
||||||
|
* Also, checks if the thread has been interrupted,
|
||||||
|
* and if so, throws an exception.
|
||||||
|
*
|
||||||
|
* @return the fractional part of the number.
|
||||||
|
*/
|
||||||
|
fun fractionalPart(): NumberInterface {
|
||||||
|
checkInterrupted()
|
||||||
|
return fractionalPartInternal()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the given number is an integer or not.
|
||||||
|
*
|
||||||
|
* @return whether the number is an integer or not.
|
||||||
|
*/
|
||||||
|
fun isInteger() = fractionalPart().signum() == 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a NumberRangeBuilder object, which is used to create a range.
|
||||||
|
* The reason that this returns a builder and not an actual range is that
|
||||||
|
* the NumberRange needs to promote values passed to it, which
|
||||||
|
* requires an abacus instance.
|
||||||
|
* @param other the value at the bottom of the range.
|
||||||
|
* @return the resulting range builder.
|
||||||
|
*/
|
||||||
|
operator fun rangeTo(other: NumberInterface) = NumberRangeBuilder(this, other)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plus operator overloaded to allow "nice" looking math.
|
||||||
|
* @param other the value to add to this number.
|
||||||
|
* @return the result of the addition.
|
||||||
|
*/
|
||||||
|
operator fun plus(other: NumberInterface) = add(other)
|
||||||
|
/**
|
||||||
|
* Minus operator overloaded to allow "nice" looking math.
|
||||||
|
* @param other the value to subtract to this number.
|
||||||
|
* @return the result of the subtraction.
|
||||||
|
*/
|
||||||
|
operator fun minus(other: NumberInterface) = subtract(other)
|
||||||
|
/**
|
||||||
|
* Times operator overloaded to allow "nice" looking math.
|
||||||
|
* @param other the value to multiply this number by.
|
||||||
|
* @return the result of the multiplication.
|
||||||
|
*/
|
||||||
|
operator fun times(other: NumberInterface) = multiply(other)
|
||||||
|
/**
|
||||||
|
* Divide operator overloaded to allow "nice" looking math.
|
||||||
|
* @param other the value to divide this number by.
|
||||||
|
* @return the result of the division.
|
||||||
|
*/
|
||||||
|
operator fun div(other: NumberInterface) = divide(other)
|
||||||
|
/**
|
||||||
|
* The plus operator.
|
||||||
|
* @return this number.
|
||||||
|
*/
|
||||||
|
operator fun unaryPlus() = this
|
||||||
|
/**
|
||||||
|
* The minus operator.
|
||||||
|
* @return the negative of this number.
|
||||||
|
*/
|
||||||
|
operator fun unaryMinus() = negate()
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
@file:JvmName("NumberUtils")
|
||||||
|
package org.nwapw.abacus.number.promotion
|
||||||
|
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
typealias PromotionPath = List<PromotionFunction>
|
||||||
|
typealias NumberClass = Class<NumberInterface>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promote a number through this path. The functions in this path
|
||||||
|
* are applied in order to the number, and the final result is returned.
|
||||||
|
*
|
||||||
|
* @param from the number to start from.
|
||||||
|
*/
|
||||||
|
fun PromotionPath.promote(from: NumberInterface): NumberInterface {
|
||||||
|
return fold(from, { current, function -> function.promote(current) })
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.nwapw.abacus.number.promotion
|
||||||
|
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function that is used to promote a number from one type to another.
|
||||||
|
*
|
||||||
|
* A promotion function is used in the promotion system as a mean to
|
||||||
|
* actually "travel" down the promotion path.
|
||||||
|
*/
|
||||||
|
interface PromotionFunction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promotes the given [number] into another type.
|
||||||
|
* @param number the number to promote from.
|
||||||
|
* @return the new number with the same value.
|
||||||
|
*/
|
||||||
|
fun promote(number: NumberInterface): NumberInterface
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package org.nwapw.abacus.number.promotion
|
||||||
|
|
||||||
|
import org.nwapw.abacus.Abacus
|
||||||
|
import org.nwapw.abacus.exception.PromotionException
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
import org.nwapw.abacus.plugin.NumberImplementation
|
||||||
|
import org.nwapw.abacus.plugin.PluginListener
|
||||||
|
import org.nwapw.abacus.plugin.PluginManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that handles promotions based on priority and the
|
||||||
|
* transition paths each implementation provides.
|
||||||
|
*
|
||||||
|
* @property abacus the Abacus instance to use to access other components.
|
||||||
|
*/
|
||||||
|
class PromotionManager(val abacus: Abacus) : PluginListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The already computed paths
|
||||||
|
*/
|
||||||
|
val computePaths = mutableMapOf<Pair<NumberImplementation, NumberImplementation>, PromotionPath?>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes a path between a starting and an ending implementation.
|
||||||
|
*
|
||||||
|
* @param from the implementation to start from.
|
||||||
|
* @param to the implementation to get to.
|
||||||
|
* @return the resulting promotion path, or null if it is not found
|
||||||
|
*/
|
||||||
|
fun computePathBetween(from: NumberImplementation, to: NumberImplementation): PromotionPath? {
|
||||||
|
val fromName = abacus.pluginManager.interfaceImplementationNameFor(from.implementation)
|
||||||
|
val toName = abacus.pluginManager.interfaceImplementationNameFor(to.implementation)
|
||||||
|
|
||||||
|
if(fromName == toName) return listOf(object : PromotionFunction {
|
||||||
|
override fun promote(number: NumberInterface): NumberInterface {
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if(from.promotionPaths.containsKey(toName))
|
||||||
|
return listOf(from.promotionPaths[toName] ?: return null)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promote all the numbers in the list to the same number implementation, to ensure
|
||||||
|
* they can be used with each other. Finds the highest priority implementation
|
||||||
|
* in the list, and promotes all other numbers to it.
|
||||||
|
*
|
||||||
|
* @param numbers the numbers to promote.
|
||||||
|
* @return the resulting promotion result.
|
||||||
|
*/
|
||||||
|
fun promote(vararg numbers: NumberInterface): PromotionResult {
|
||||||
|
val pluginManager = abacus.pluginManager
|
||||||
|
val implementations = numbers.map { pluginManager.interfaceImplementationFor(it.javaClass) }
|
||||||
|
val highestPriority = implementations.sortedBy { it.priority }.last()
|
||||||
|
return PromotionResult(items = numbers.map {
|
||||||
|
if (it.javaClass == highestPriority.implementation) it
|
||||||
|
else computePaths[pluginManager.interfaceImplementationFor(it.javaClass) to highestPriority]
|
||||||
|
?.promote(it) ?: throw PromotionException()
|
||||||
|
}.toTypedArray(), promotedTo = highestPriority)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoad(manager: PluginManager) {
|
||||||
|
val implementations = manager.allNumberImplementations.map { manager.numberImplementationFor(it) }
|
||||||
|
for(first in implementations) {
|
||||||
|
for(second in implementations) {
|
||||||
|
val promoteFrom = if(second.priority > first.priority) first else second
|
||||||
|
val promoteTo = if(second.priority > first.priority) second else first
|
||||||
|
val path = computePathBetween(promoteFrom, promoteTo)
|
||||||
|
computePaths[promoteFrom to promoteTo] = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUnload(manager: PluginManager) {
|
||||||
|
computePaths.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.nwapw.abacus.number.promotion
|
||||||
|
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
import org.nwapw.abacus.plugin.NumberImplementation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of promoting an array of NumberInterfaces.
|
||||||
|
*
|
||||||
|
* @param promotedTo the implementation to which the numbers were promoted.
|
||||||
|
* @param items the items the items resulting from the promotion.
|
||||||
|
*/
|
||||||
|
data class PromotionResult(val promotedTo: NumberImplementation, val items: Array<NumberInterface>)
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.nwapw.abacus.number.range
|
||||||
|
|
||||||
|
import org.nwapw.abacus.Abacus
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A closed range designed specifically for [NumberInterface]
|
||||||
|
*
|
||||||
|
* Besides providing the usual functionality of a [ClosedRange], this range
|
||||||
|
* also handles promotion - that is, it's safe to use it with numbers of different
|
||||||
|
* implementations, even as starting and ending points.
|
||||||
|
*
|
||||||
|
* @property abacus the abacus instance used for promotion.
|
||||||
|
* @property start the starting point of the range.
|
||||||
|
* @property endInclusive the ending point of the range.
|
||||||
|
*/
|
||||||
|
class NumberRange(val abacus: Abacus,
|
||||||
|
override val start: NumberInterface,
|
||||||
|
override val endInclusive: NumberInterface): ClosedRange<NumberInterface> {
|
||||||
|
|
||||||
|
override operator fun contains(value: NumberInterface): Boolean {
|
||||||
|
val promotionResult = abacus.promotionManager.promote(start, endInclusive, value)
|
||||||
|
val newStart = promotionResult.items[0]
|
||||||
|
val newEnd = promotionResult.items[1]
|
||||||
|
val newValue = promotionResult.items[2]
|
||||||
|
return newValue >= newStart && newValue <= newEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.nwapw.abacus.number.range
|
||||||
|
|
||||||
|
import org.nwapw.abacus.Abacus
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility class for creating [NumberRange] instances.
|
||||||
|
*
|
||||||
|
* Unlike a regular [ClosedRange], a NumberRange must have a third parameter,
|
||||||
|
* which is the [Abacus] instance that is used for promotion. However, the ".." operator
|
||||||
|
* is infix, and can only take two parameters. The solution is, instead of returning instances
|
||||||
|
* of NumberRange directly, to return a builder, which then provides a [with] infix function
|
||||||
|
* to attach it to an instance of Abacus.
|
||||||
|
* @property start the beginning of the range.
|
||||||
|
* @property endInclusive the end of the range.
|
||||||
|
*/
|
||||||
|
class NumberRangeBuilder(private val start: NumberInterface, private val endInclusive: NumberInterface) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a [NumberRange] with the given instance of [abacus].
|
||||||
|
* @return a new range with the given instance of Abacus.
|
||||||
|
*/
|
||||||
|
infix fun with(abacus: Abacus) = NumberRange(abacus, start, endInclusive)
|
||||||
|
|
||||||
|
}
|
24
core/src/main/kotlin/org/nwapw/abacus/parsing/Parser.kt
Normal file
24
core/src/main/kotlin/org/nwapw/abacus/parsing/Parser.kt
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package org.nwapw.abacus.parsing
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.nodes.TreeNode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converter from tokens into a parse tree.
|
||||||
|
*
|
||||||
|
* An interface that provides the ability to convert a list of tokens
|
||||||
|
* into a parse tree.
|
||||||
|
*
|
||||||
|
* @param <T> the type of tokens accepted by this parser.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface Parser<in 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.
|
||||||
|
*/
|
||||||
|
fun constructTree(tokens: List<T>): TreeNode
|
||||||
|
|
||||||
|
}
|
21
core/src/main/kotlin/org/nwapw/abacus/parsing/Tokenizer.kt
Normal file
21
core/src/main/kotlin/org/nwapw/abacus/parsing/Tokenizer.kt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package org.nwapw.abacus.parsing
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converter from string to tokens.
|
||||||
|
*
|
||||||
|
* Interface that converts a string into a list
|
||||||
|
* of tokens of a certain type.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the tokens produced.
|
||||||
|
*/
|
||||||
|
interface Tokenizer<out T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string into tokens.
|
||||||
|
*
|
||||||
|
* @param string the string to convert.
|
||||||
|
* @return the list of tokens, or null on error.
|
||||||
|
*/
|
||||||
|
fun tokenizeString(string: String): List<T>
|
||||||
|
|
||||||
|
}
|
26
core/src/main/kotlin/org/nwapw/abacus/parsing/TreeBuilder.kt
Normal file
26
core/src/main/kotlin/org/nwapw/abacus/parsing/TreeBuilder.kt
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package org.nwapw.abacus.parsing
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.nodes.TreeNode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to combine a [Tokenizer] and a [Parser]
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
class TreeBuilder<T>(private val tokenizer: Tokenizer<T>, private val parser: Parser<T>) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the given [string] into a tree.
|
||||||
|
*
|
||||||
|
* @param string the string to parse into a tree.
|
||||||
|
* @return the resulting tree.
|
||||||
|
*/
|
||||||
|
fun fromString(string: String): TreeNode = parser.constructTree(tokenizer.tokenizeString(string))
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.nwapw.abacus.plugin.standard.operator
|
||||||
|
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity
|
||||||
|
import org.nwapw.abacus.function.OperatorType
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberOperator
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The addition operator.
|
||||||
|
*
|
||||||
|
* This is a standard operator that simply performs addition.
|
||||||
|
*/
|
||||||
|
class OperatorAdd: NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
|
||||||
|
|
||||||
|
override fun matchesParams(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
params.size == 2
|
||||||
|
override fun applyInternal(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
params[0] + params[1]
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.nwapw.abacus.plugin.standard.operator
|
||||||
|
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity
|
||||||
|
import org.nwapw.abacus.function.OperatorType
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberOperator
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
import org.nwapw.abacus.plugin.standard.StandardPlugin.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The power operator.
|
||||||
|
*
|
||||||
|
* This is a standard operator that brings one number to the power of the other.
|
||||||
|
*/
|
||||||
|
class OperatorCaret: NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 3) {
|
||||||
|
|
||||||
|
override fun matchesParams(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
params.size == 2
|
||||||
|
&& !(params[0].signum() == 0 && params[1].signum() == 0)
|
||||||
|
&& !(params[0].signum() == -1 && !params[1].isInteger())
|
||||||
|
|
||||||
|
override fun applyInternal(context: PluginEvaluationContext, params: Array<out NumberInterface>): NumberInterface {
|
||||||
|
val implementation = context.inheritedNumberImplementation
|
||||||
|
if (params[0].signum() == 0)
|
||||||
|
return implementation.instanceForString("0")
|
||||||
|
else if (params[1].signum() == 0)
|
||||||
|
return implementation.instanceForString("1")
|
||||||
|
//Detect integer bases:
|
||||||
|
if (params[0].isInteger()
|
||||||
|
&& FUNCTION_ABS.apply(context, params[1]) < implementation.instanceForString(Integer.toString(Integer.MAX_VALUE))
|
||||||
|
&& FUNCTION_ABS.apply(context, params[1]) >= implementation.instanceForString("1")) {
|
||||||
|
val newParams = arrayOf(params[0], params[1].fractionalPart())
|
||||||
|
return params[0].intPow(params[1].floor().intValue()) * applyInternal(context, newParams)
|
||||||
|
}
|
||||||
|
return FUNCTION_EXP.apply(context, FUNCTION_LN.apply(context, FUNCTION_ABS.apply(context, params[0])) * params[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.nwapw.abacus.plugin.standard.operator
|
||||||
|
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity
|
||||||
|
import org.nwapw.abacus.function.OperatorType
|
||||||
|
import org.nwapw.abacus.function.interfaces.TreeValueOperator
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
import org.nwapw.abacus.tree.nodes.TreeNode
|
||||||
|
import org.nwapw.abacus.tree.nodes.VariableNode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The definition operator.
|
||||||
|
*
|
||||||
|
* This is a standard operator that creates a definition - something that doesn't capture variable values
|
||||||
|
* when it's created, but rather the variables themselves, and changes when the variables it uses change.
|
||||||
|
*/
|
||||||
|
class OperatorDefine: TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
|
||||||
|
|
||||||
|
override fun matchesParams(context: PluginEvaluationContext, params: Array<out TreeNode>) =
|
||||||
|
params.size == 2 && params[0] is VariableNode
|
||||||
|
|
||||||
|
override fun applyInternal(context: PluginEvaluationContext, params: Array<out TreeNode>): NumberInterface {
|
||||||
|
val assignTo = (params[0] as VariableNode).variable
|
||||||
|
context.setDefinition(assignTo, params[1])
|
||||||
|
return params[1].reduce(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.nwapw.abacus.plugin.standard.operator
|
||||||
|
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity
|
||||||
|
import org.nwapw.abacus.function.OperatorType
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberOperator
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The division operator.
|
||||||
|
*
|
||||||
|
* This is a standard operator that simply performs division.
|
||||||
|
*/
|
||||||
|
class OperatorDivide: NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 2) {
|
||||||
|
|
||||||
|
override fun matchesParams(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
params.size == 2
|
||||||
|
override fun applyInternal(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
params[0] / params[1]
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.nwapw.abacus.plugin.standard.operator
|
||||||
|
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity
|
||||||
|
import org.nwapw.abacus.function.OperatorType
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberOperator
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The factorial operator.
|
||||||
|
*
|
||||||
|
* This is a standard operator that simply evaluates the factorial of a number.
|
||||||
|
*/
|
||||||
|
class OperatorFactorial: NumberOperator(OperatorAssociativity.LEFT, OperatorType.UNARY_POSTFIX, 0) {
|
||||||
|
|
||||||
|
override fun matchesParams(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
params.size == 1
|
||||||
|
&& params[0].isInteger()
|
||||||
|
&& params[0].signum() >= 0
|
||||||
|
|
||||||
|
override fun applyInternal(context: PluginEvaluationContext, params: Array<out NumberInterface>): NumberInterface {
|
||||||
|
val implementation = context.inheritedNumberImplementation
|
||||||
|
val one = implementation.instanceForString("1")
|
||||||
|
if (params[0].signum() == 0) {
|
||||||
|
return one
|
||||||
|
}
|
||||||
|
var factorial = params[0]
|
||||||
|
var multiplier = params[0] - one
|
||||||
|
//It is necessary to later prevent calls of factorial on anything but non-negative integers.
|
||||||
|
while (multiplier.signum() == 1) {
|
||||||
|
factorial *= multiplier
|
||||||
|
multiplier -= one
|
||||||
|
}
|
||||||
|
return factorial
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.nwapw.abacus.plugin.standard.operator
|
||||||
|
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity
|
||||||
|
import org.nwapw.abacus.function.OperatorType
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberOperator
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The multiplication operator.
|
||||||
|
*
|
||||||
|
* This is a standard operator that simply performs multiplication.
|
||||||
|
*/
|
||||||
|
class OperatorMultiply: NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 2) {
|
||||||
|
|
||||||
|
override fun matchesParams(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
params.size == 2
|
||||||
|
override fun applyInternal(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
params[0] * params[1]
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.nwapw.abacus.plugin.standard.operator
|
||||||
|
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity
|
||||||
|
import org.nwapw.abacus.function.OperatorType
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberOperator
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
import org.nwapw.abacus.plugin.standard.StandardPlugin.OP_FACTORIAL
|
||||||
|
import org.nwapw.abacus.plugin.standard.StandardPlugin.OP_NPR
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "N choose R" operator.
|
||||||
|
*
|
||||||
|
* This is a standard operator that returns the number of possible combinations, regardless of order,
|
||||||
|
* of a certain size can be taken out of a pool of a bigger size.
|
||||||
|
*/
|
||||||
|
class OperatorNcr: NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 1) {
|
||||||
|
|
||||||
|
override fun matchesParams(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
params.size == 2 && params[0].isInteger()
|
||||||
|
&& params[1].isInteger()
|
||||||
|
|
||||||
|
override fun applyInternal(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
OP_NPR.apply(context, *params) / OP_FACTORIAL.apply(context, params[1])
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.nwapw.abacus.plugin.standard.operator
|
||||||
|
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity
|
||||||
|
import org.nwapw.abacus.function.OperatorType
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberOperator
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The negation operator.
|
||||||
|
*
|
||||||
|
* This is a standard operator that negates a number.
|
||||||
|
*/
|
||||||
|
class OperatorNegate: NumberOperator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 1) {
|
||||||
|
|
||||||
|
override fun matchesParams(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
params.size == 1
|
||||||
|
|
||||||
|
override fun applyInternal(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
-params[0]
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.nwapw.abacus.plugin.standard.operator
|
||||||
|
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity
|
||||||
|
import org.nwapw.abacus.function.OperatorType
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberOperator
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "N pick R" operator.
|
||||||
|
*
|
||||||
|
* his is a standard operator that returns the number of possible combinations
|
||||||
|
* of a certain size can be taken out of a pool of a bigger size.
|
||||||
|
*/
|
||||||
|
class OperatorNpr: NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 1) {
|
||||||
|
|
||||||
|
override fun matchesParams(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
params.size == 2 && params[0].isInteger()
|
||||||
|
&& params[1].isInteger()
|
||||||
|
|
||||||
|
override fun applyInternal(context: PluginEvaluationContext, params: Array<out NumberInterface>): NumberInterface {
|
||||||
|
val implementation = context.inheritedNumberImplementation
|
||||||
|
if (params[0] < params[1] ||
|
||||||
|
params[0].signum() < 0 ||
|
||||||
|
params[0].signum() == 0 && params[1].signum() != 0)
|
||||||
|
return implementation.instanceForString("0")
|
||||||
|
var total = implementation.instanceForString("1")
|
||||||
|
var multiplyBy = params[0]
|
||||||
|
var remainingMultiplications = params[1]
|
||||||
|
val halfway = params[0] / implementation.instanceForString("2")
|
||||||
|
if (remainingMultiplications > halfway) {
|
||||||
|
remainingMultiplications = params[0] - remainingMultiplications
|
||||||
|
}
|
||||||
|
while (remainingMultiplications.signum() > 0) {
|
||||||
|
total *= multiplyBy
|
||||||
|
remainingMultiplications -= implementation.instanceForString("1")
|
||||||
|
multiplyBy -= implementation.instanceForString("1")
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.nwapw.abacus.plugin.standard.operator
|
||||||
|
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity
|
||||||
|
import org.nwapw.abacus.function.OperatorType
|
||||||
|
import org.nwapw.abacus.function.interfaces.TreeValueOperator
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
import org.nwapw.abacus.tree.nodes.TreeNode
|
||||||
|
import org.nwapw.abacus.tree.nodes.VariableNode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set operator.
|
||||||
|
*
|
||||||
|
* This is a standard operator that assigns a value to a variable name.
|
||||||
|
*/
|
||||||
|
class OperatorSet: TreeValueOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
|
||||||
|
|
||||||
|
override fun matchesParams(context: PluginEvaluationContext, params: Array<out TreeNode>) =
|
||||||
|
params.size == 2 && params[0] is VariableNode
|
||||||
|
|
||||||
|
override fun applyInternal(context: PluginEvaluationContext, params: Array<out TreeNode>): NumberInterface {
|
||||||
|
val assignTo = (params[0] as VariableNode).variable
|
||||||
|
val value = params[1].reduce(context)
|
||||||
|
context.setVariable(assignTo, value)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.nwapw.abacus.plugin.standard.operator
|
||||||
|
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity
|
||||||
|
import org.nwapw.abacus.function.OperatorType
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberOperator
|
||||||
|
import org.nwapw.abacus.number.NumberInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The subtraction operator.
|
||||||
|
*
|
||||||
|
* This is a standard operator that performs subtraction.
|
||||||
|
*/
|
||||||
|
class OperatorSubtract: NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
|
||||||
|
|
||||||
|
override fun matchesParams(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
params.size == 2
|
||||||
|
override fun applyInternal(context: PluginEvaluationContext, params: Array<out NumberInterface>) =
|
||||||
|
params[0] - params[1]
|
||||||
|
|
||||||
|
}
|
21
core/src/main/kotlin/org/nwapw/abacus/tree/Reducer.kt
Normal file
21
core/src/main/kotlin/org/nwapw/abacus/tree/Reducer.kt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package org.nwapw.abacus.tree
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.nodes.TreeNode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reducer interface that takes a tree and returns a single value.
|
||||||
|
*
|
||||||
|
* The reducer walks the tree, visiting the children first, converting them into
|
||||||
|
* a value, and then attempts to reduce the parent. Eventually, the single final value is returned.
|
||||||
|
*/
|
||||||
|
interface Reducer<out T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces the given tree node, given its already reduced children.
|
||||||
|
*
|
||||||
|
* @param treeNode the tree node to reduce.
|
||||||
|
* @param children the list of children, of type T.
|
||||||
|
*/
|
||||||
|
fun reduceNode(treeNode: TreeNode, vararg children: Any): T
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.nwapw.abacus.tree.nodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tree node that holds a binary operation.
|
||||||
|
*
|
||||||
|
* This node represents any binary operation, such as binary infix or binary postfix. The only
|
||||||
|
* currently implemented into Abacus is binary infix, but that has more to do with the parser than
|
||||||
|
* this class, which doesn't care about the order that its operation and nodes were found in text.
|
||||||
|
*
|
||||||
|
* @param operation the operation this node performs on its children.
|
||||||
|
* @param left the left node.
|
||||||
|
* @param right the right node.
|
||||||
|
*/
|
||||||
|
abstract class BinaryNode(val operation: String, val left: TreeNode, val right: TreeNode) : TreeNode() {
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "(" + left.toString() + operation + right.toString() + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
core/src/main/kotlin/org/nwapw/abacus/tree/nodes/CallNode.kt
Normal file
25
core/src/main/kotlin/org/nwapw/abacus/tree/nodes/CallNode.kt
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package org.nwapw.abacus.tree.nodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a more generic function call.
|
||||||
|
*
|
||||||
|
* This class does not specify how it should be reduced, allowing other classes
|
||||||
|
* to extend this functionality.
|
||||||
|
*
|
||||||
|
* @param callTo the name of the things being called.
|
||||||
|
* @param children the children of this node.
|
||||||
|
*/
|
||||||
|
abstract class CallNode(val callTo: String, val children: List<TreeNode>) : TreeNode() {
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val buffer = StringBuffer()
|
||||||
|
buffer.append(callTo)
|
||||||
|
buffer.append("(")
|
||||||
|
for (i in 0 until children.size) {
|
||||||
|
buffer.append(children[i].toString())
|
||||||
|
buffer.append(if (i != children.size - 1) ", " else ")")
|
||||||
|
}
|
||||||
|
return buffer.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.nwapw.abacus.tree.nodes
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.Reducer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A binary operator node that reduces its children.
|
||||||
|
*
|
||||||
|
* NumberBinaryNode operates by simply reducing its children and
|
||||||
|
* then using the result of that reduction to reduce itself.
|
||||||
|
*
|
||||||
|
* @param operation the operation this node performs.
|
||||||
|
* @param left the left child of this node.
|
||||||
|
* @param right the right child of this node.
|
||||||
|
*/
|
||||||
|
class NumberBinaryNode(operation: String, left: TreeNode, right: TreeNode)
|
||||||
|
: BinaryNode(operation, left, right) {
|
||||||
|
|
||||||
|
override fun <T : Any> reduce(reducer: Reducer<T>): T {
|
||||||
|
val left = left.reduce(reducer)
|
||||||
|
val right = right.reduce(reducer)
|
||||||
|
return reducer.reduceNode(this, left, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.nwapw.abacus.tree.nodes
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.Reducer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tree node that holds a function call.
|
||||||
|
*
|
||||||
|
* The function call node can hold any number of children, and passes the to the appropriate reducer,
|
||||||
|
* but that is its sole purpose.
|
||||||
|
*
|
||||||
|
* @param function the function string.
|
||||||
|
*/
|
||||||
|
class NumberFunctionNode(function: String, children: List<TreeNode>) : CallNode(function, children) {
|
||||||
|
|
||||||
|
override fun <T : Any> reduce(reducer: Reducer<T>): T {
|
||||||
|
val children = Array<Any>(children.size, { children[it].reduce(reducer) })
|
||||||
|
return reducer.reduceNode(this, *children)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.nwapw.abacus.tree.nodes
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.Reducer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tree node that holds a single number value.
|
||||||
|
*
|
||||||
|
* This is a tree node that holds a single NumberInterface, which represents any number,
|
||||||
|
* and is not defined during compile time.
|
||||||
|
*
|
||||||
|
* @number the number value of this node.
|
||||||
|
*/
|
||||||
|
class NumberNode(val number: String) : TreeNode() {
|
||||||
|
|
||||||
|
override fun <T : Any> reduce(reducer: Reducer<T>): T {
|
||||||
|
return reducer.reduceNode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.nwapw.abacus.tree.nodes
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.Reducer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A unary operator node that reduces its children.
|
||||||
|
*
|
||||||
|
* NumberUnaryNode operates by simply reducing its child,
|
||||||
|
* and using the result of that reduction to reduce itself.
|
||||||
|
* @param operation the operation this node performs.
|
||||||
|
* @param child the child this node should be applied to.
|
||||||
|
*/
|
||||||
|
class NumberUnaryNode(operation: String, child: TreeNode)
|
||||||
|
: UnaryNode(operation, child) {
|
||||||
|
|
||||||
|
override fun <T : Any> reduce(reducer: Reducer<T>): T {
|
||||||
|
val child = applyTo.reduce(reducer)
|
||||||
|
return reducer.reduceNode(this, child)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
12
core/src/main/kotlin/org/nwapw/abacus/tree/nodes/TreeNode.kt
Normal file
12
core/src/main/kotlin/org/nwapw/abacus/tree/nodes/TreeNode.kt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package org.nwapw.abacus.tree.nodes
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.Reducer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tree node.
|
||||||
|
*/
|
||||||
|
abstract class TreeNode {
|
||||||
|
|
||||||
|
abstract fun <T : Any> reduce(reducer: Reducer<T>): T
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.nwapw.abacus.tree.nodes
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.Reducer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tree node that represents a binary tree value operator.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The tree value operators operate on trees, and so this
|
||||||
|
* node does not reduce its children. It is up to the implementation to handle
|
||||||
|
* reduction.
|
||||||
|
* @param operation the operation this node performs.
|
||||||
|
* @param left the left child of this node.
|
||||||
|
* @param right the right child of this node.
|
||||||
|
*/
|
||||||
|
class TreeValueBinaryNode(operation: String, left: TreeNode, right: TreeNode)
|
||||||
|
: BinaryNode(operation, left, right) {
|
||||||
|
|
||||||
|
override fun <T : Any> reduce(reducer: Reducer<T>): T {
|
||||||
|
return reducer.reduceNode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.nwapw.abacus.tree.nodes
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.Reducer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tree node that represents a tree value function call.
|
||||||
|
*
|
||||||
|
* This is in many ways similar to a simple FunctionNode, and the distinction
|
||||||
|
* is mostly to help the reducer. Besides that, this class also does not
|
||||||
|
* even attempt to reduce its children.
|
||||||
|
*/
|
||||||
|
class TreeValueFunctionNode(name: String, children: List<TreeNode>) : CallNode(name, children) {
|
||||||
|
|
||||||
|
override fun <T : Any> reduce(reducer: Reducer<T>): T {
|
||||||
|
return reducer.reduceNode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.nwapw.abacus.tree.nodes
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.Reducer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tree node that represents a unary tree value operator.
|
||||||
|
*
|
||||||
|
* The tree value operators operate on trees, and so this
|
||||||
|
* node does not reduce its children. It is up to the implementation to handle
|
||||||
|
* reduction.
|
||||||
|
* @param operation the operation this node performs.
|
||||||
|
* @param child the node the operation should be applied to.
|
||||||
|
*/
|
||||||
|
class TreeValueUnaryNode(operation: String, child: TreeNode)
|
||||||
|
: UnaryNode(operation, child) {
|
||||||
|
|
||||||
|
override fun <T : Any> reduce(reducer: Reducer<T>): T {
|
||||||
|
return reducer.reduceNode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.nwapw.abacus.tree.nodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tree node that holds a unary operation.
|
||||||
|
*
|
||||||
|
* This node holds a single operator applied to a single parameter, and does not care
|
||||||
|
* whether the operation was found before or after the parameter in the text.
|
||||||
|
*
|
||||||
|
* @param operation the operation applied to the given node.
|
||||||
|
* @param applyTo the node to which the operation will be applied.
|
||||||
|
*/
|
||||||
|
abstract class UnaryNode(val operation: String, val applyTo: TreeNode) : TreeNode() {
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "(" + applyTo.toString() + ")" + operation
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.nwapw.abacus.tree.nodes
|
||||||
|
|
||||||
|
import org.nwapw.abacus.tree.Reducer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tree node that holds a placeholder variable.
|
||||||
|
*
|
||||||
|
* This node holds a variable string, and acts similarly to a number,
|
||||||
|
* with the key difference of not actually holding a value at runtime.
|
||||||
|
*
|
||||||
|
* @param variable the actual variable name that this node represents.
|
||||||
|
*/
|
||||||
|
class VariableNode(val variable: String) : TreeNode() {
|
||||||
|
|
||||||
|
override fun <T : Any> reduce(reducer: Reducer<T>): T {
|
||||||
|
return reducer.reduceNode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return variable
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
109
core/src/test/java/org/nwapw/abacus/tests/CalculationTests.java
Executable file
109
core/src/test/java/org/nwapw/abacus/tests/CalculationTests.java
Executable file
|
@ -0,0 +1,109 @@
|
||||||
|
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.exception.DomainException;
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
import org.nwapw.abacus.plugin.standard.StandardPlugin;
|
||||||
|
import org.nwapw.abacus.tree.nodes.TreeNode;
|
||||||
|
|
||||||
|
public class CalculationTests {
|
||||||
|
|
||||||
|
private static Abacus abacus = new Abacus(new Configuration( "precise", new String[]{}));
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void prepareTests() {
|
||||||
|
abacus.getPluginManager().addInstantiated(new StandardPlugin(abacus.getPluginManager()));
|
||||||
|
abacus.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testOutput(String input, String parseOutput, String output) {
|
||||||
|
TreeNode parsedTree = abacus.parseString(input);
|
||||||
|
Assert.assertEquals(parsedTree.toString(), parseOutput);
|
||||||
|
NumberInterface result = abacus.evaluateTree(parsedTree).getValue();
|
||||||
|
Assert.assertNotNull(result);
|
||||||
|
Assert.assertTrue(result.toString().startsWith(output));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testDomainException(String input, String parseOutput) {
|
||||||
|
TreeNode parsedTree = abacus.parseString(input);
|
||||||
|
Assert.assertEquals(parsedTree.toString(), parseOutput);
|
||||||
|
try {
|
||||||
|
abacus.evaluateTree(parsedTree);
|
||||||
|
Assert.fail("Function did not throw DomainException.");
|
||||||
|
} catch (DomainException e){ }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddition() {
|
||||||
|
testOutput("9.5+10", "(9.5+10)", "19.5");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSubtraction() {
|
||||||
|
testOutput("9.5-10", "(9.5-10)", "-0.5");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultiplication() {
|
||||||
|
testOutput("9.5*10", "(9.5*10)", "95");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDivision() {
|
||||||
|
testOutput("9.5/2", "(9.5/2)", "4.75");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNegation() {
|
||||||
|
testOutput("-9.5", "(9.5)`", "-9.5");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFactorial() {
|
||||||
|
testOutput("7!", "(7)!", "5040");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAbs() {
|
||||||
|
testOutput("abs(-1)", "abs((1)`)", "1");
|
||||||
|
testOutput("abs(1)", "abs(1)", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLn() {
|
||||||
|
testDomainException("ln(-1)", "ln((1)`)");
|
||||||
|
testOutput("ln2", "ln(2)", "0.6931471805599453094172321214581765680755");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSqrt() {
|
||||||
|
testOutput("sqrt0", "sqrt(0)", "0");
|
||||||
|
testOutput("sqrt4", "sqrt(4)", "2");
|
||||||
|
testOutput("sqrt2", "sqrt(2)", "1.4142135623730950488016887242096980785696");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExp() {
|
||||||
|
testOutput("exp0", "exp(0)", "1");
|
||||||
|
testOutput("exp1", "exp(1)", "2.718281828459045235360287471352662497757247");
|
||||||
|
testOutput("exp300", "exp(300)", "1.9424263952412559365842088360176992193662086");
|
||||||
|
testOutput("exp(-500)", "exp((500)`)", "7.1245764067412855315491573771227552469277568");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPow() {
|
||||||
|
testOutput("0^2", "(0^2)", "0");
|
||||||
|
testOutput("2^0", "(2^0)", "1");
|
||||||
|
testOutput("2^1", "(2^1)", "2");
|
||||||
|
testOutput("2^-1", "(2^(1)`)", "0.5");
|
||||||
|
testOutput("2^50", "(2^50)", "112589990684262");
|
||||||
|
testOutput("7^(-sqrt2*17)", "(7^((sqrt(2)*17))`)", "4.81354609155297814551845300063563");
|
||||||
|
testDomainException("0^0", "(0^0)");
|
||||||
|
testDomainException("(-13)^.9999", "((13)`^.9999)");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
133
core/src/test/java/org/nwapw/abacus/tests/LexerTests.java
Normal file
133
core/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.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
95
core/src/test/java/org/nwapw/abacus/tests/RangeTests.java
Normal file
95
core/src/test/java/org/nwapw/abacus/tests/RangeTests.java
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
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.promotion.PromotionFunction;
|
||||||
|
import org.nwapw.abacus.number.range.NumberRange;
|
||||||
|
import org.nwapw.abacus.number.standard.NaiveNumber;
|
||||||
|
import org.nwapw.abacus.number.standard.PreciseNumber;
|
||||||
|
import org.nwapw.abacus.plugin.standard.StandardPlugin;
|
||||||
|
|
||||||
|
public class RangeTests {
|
||||||
|
|
||||||
|
private static Abacus abacus = new Abacus(new Configuration( "precise", new String[]{}));
|
||||||
|
private static PromotionFunction naivePromotion = i -> new NaiveNumber((i.toString()));
|
||||||
|
private static PromotionFunction precisePromotion = i -> new PreciseNumber((i.toString()));
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void prepareTests() {
|
||||||
|
abacus.getPluginManager().addInstantiated(new StandardPlugin(abacus.getPluginManager()));
|
||||||
|
abacus.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NumberRange naiveRange(String bottom, String top) {
|
||||||
|
return new NaiveNumber(bottom).rangeTo(new NaiveNumber(top)).with(abacus);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNaiveRange(){
|
||||||
|
NumberRange range = naiveRange("0", "10");
|
||||||
|
Assert.assertTrue(range.getStart().toString().startsWith("0"));
|
||||||
|
Assert.assertTrue(range.getEndInclusive().toString().startsWith("10"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNaiveRangeBelow() {
|
||||||
|
NumberRange range = naiveRange("0", "10");
|
||||||
|
Assert.assertFalse(range.contains(new NaiveNumber("-10")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNaiveRangeAbove() {
|
||||||
|
NumberRange range = naiveRange("0", "10");
|
||||||
|
Assert.assertFalse(range.contains(new NaiveNumber("20")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNaiveRangeJustWithinBottom() {
|
||||||
|
NumberRange range = naiveRange("0", "10");
|
||||||
|
Assert.assertTrue(range.contains(new NaiveNumber("0")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNaiveRangeJustWithinTop() {
|
||||||
|
NumberRange range = naiveRange("0", "10");
|
||||||
|
Assert.assertTrue(range.contains(new NaiveNumber("10")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNaiveRangeWithin() {
|
||||||
|
NumberRange range = naiveRange("0", "10");
|
||||||
|
Assert.assertTrue(range.contains(new NaiveNumber("5")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addTestPromotionPaths() {
|
||||||
|
StandardPlugin.IMPLEMENTATION_NAIVE.getPromotionPaths().put("precise", precisePromotion);
|
||||||
|
StandardPlugin.IMPLEMENTATION_PRECISE.getPromotionPaths().put("naive", naivePromotion);
|
||||||
|
abacus.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeTestPromotionPaths() {
|
||||||
|
StandardPlugin.IMPLEMENTATION_NAIVE.getPromotionPaths().remove("precise");
|
||||||
|
StandardPlugin.IMPLEMENTATION_NAIVE.getPromotionPaths().remove("naive");
|
||||||
|
abacus.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPromotionWithin() {
|
||||||
|
addTestPromotionPaths();
|
||||||
|
NumberRange range = naiveRange("0", "10");
|
||||||
|
Assert.assertTrue(range.contains(new PreciseNumber("5")));
|
||||||
|
removeTestPromotionPaths();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPromotionOutside(){
|
||||||
|
addTestPromotionPaths();
|
||||||
|
NumberRange range = naiveRange("0","10");
|
||||||
|
Assert.assertFalse(range.contains(new PreciseNumber("20")));
|
||||||
|
removeTestPromotionPaths();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
150
core/src/test/java/org/nwapw/abacus/tests/TokenizerTests.java
Normal file
150
core/src/test/java/org/nwapw/abacus/tests/TokenizerTests.java
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
package org.nwapw.abacus.tests;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
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.context.MutableEvaluationContext;
|
||||||
|
import org.nwapw.abacus.context.PluginEvaluationContext;
|
||||||
|
import org.nwapw.abacus.function.OperatorAssociativity;
|
||||||
|
import org.nwapw.abacus.function.OperatorType;
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberFunction;
|
||||||
|
import org.nwapw.abacus.function.interfaces.NumberOperator;
|
||||||
|
import org.nwapw.abacus.lexing.Match;
|
||||||
|
import org.nwapw.abacus.number.NumberInterface;
|
||||||
|
import org.nwapw.abacus.parsing.standard.LexerTokenizer;
|
||||||
|
import org.nwapw.abacus.parsing.standard.TokenType;
|
||||||
|
import org.nwapw.abacus.plugin.Plugin;
|
||||||
|
|
||||||
|
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 NumberFunction subtractFunction = new NumberFunction() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params.length == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return params[0].subtract(params[1]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static Plugin testPlugin = new Plugin(abacus.getPluginManager()) {
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
registerOperator("+", new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX,
|
||||||
|
0) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return subtractFunction.apply(context, params);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
registerOperator("-", new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX,
|
||||||
|
0) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matchesParams(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NumberInterface applyInternal(PluginEvaluationContext context, NumberInterface[] params) {
|
||||||
|
return subtractFunction.apply(context, params);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
7
docs/404.html
Normal file
7
docs/404.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
layout: base
|
||||||
|
---
|
||||||
|
<h1>404</h1>
|
||||||
|
|
||||||
|
<p><strong>Page not found :(</strong></p>
|
||||||
|
<p>The requested page could not be found.</p>
|
27
docs/Gemfile
Normal file
27
docs/Gemfile
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
# Hello! This is where you manage which Jekyll version is used to run.
|
||||||
|
# When you want to use a different version, change it below, save the
|
||||||
|
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
|
||||||
|
#
|
||||||
|
# bundle exec jekyll serve
|
||||||
|
#
|
||||||
|
# This will help ensure the proper Jekyll version is running.
|
||||||
|
# Happy Jekylling!
|
||||||
|
gem "jekyll", "3.5.2"
|
||||||
|
|
||||||
|
# This is the default theme for new Jekyll sites. You may change this to anything you like.
|
||||||
|
gem "minima", "~> 2.0"
|
||||||
|
|
||||||
|
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
|
||||||
|
# uncomment the line below. To upgrade, run `bundle update github-pages`.
|
||||||
|
# gem "github-pages", group: :jekyll_plugins
|
||||||
|
|
||||||
|
# If you have any plugins, put them here!
|
||||||
|
group :jekyll_plugins do
|
||||||
|
gem "jekyll-feed", "~> 0.6"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||||
|
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
||||||
|
|
58
docs/Gemfile.lock
Normal file
58
docs/Gemfile.lock
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
addressable (2.5.1)
|
||||||
|
public_suffix (~> 2.0, >= 2.0.2)
|
||||||
|
colorator (1.1.0)
|
||||||
|
ffi (1.9.18)
|
||||||
|
forwardable-extended (2.6.0)
|
||||||
|
jekyll (3.5.2)
|
||||||
|
addressable (~> 2.4)
|
||||||
|
colorator (~> 1.0)
|
||||||
|
jekyll-sass-converter (~> 1.0)
|
||||||
|
jekyll-watch (~> 1.1)
|
||||||
|
kramdown (~> 1.3)
|
||||||
|
liquid (~> 4.0)
|
||||||
|
mercenary (~> 0.3.3)
|
||||||
|
pathutil (~> 0.9)
|
||||||
|
rouge (~> 1.7)
|
||||||
|
safe_yaml (~> 1.0)
|
||||||
|
jekyll-feed (0.9.2)
|
||||||
|
jekyll (~> 3.3)
|
||||||
|
jekyll-sass-converter (1.5.0)
|
||||||
|
sass (~> 3.4)
|
||||||
|
jekyll-watch (1.5.0)
|
||||||
|
listen (~> 3.0, < 3.1)
|
||||||
|
kramdown (1.14.0)
|
||||||
|
liquid (4.0.0)
|
||||||
|
listen (3.0.8)
|
||||||
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||||
|
rb-inotify (~> 0.9, >= 0.9.7)
|
||||||
|
mercenary (0.3.6)
|
||||||
|
minima (2.1.1)
|
||||||
|
jekyll (~> 3.3)
|
||||||
|
pathutil (0.14.0)
|
||||||
|
forwardable-extended (~> 2.6)
|
||||||
|
public_suffix (2.0.5)
|
||||||
|
rb-fsevent (0.10.2)
|
||||||
|
rb-inotify (0.9.10)
|
||||||
|
ffi (>= 0.5.0, < 2)
|
||||||
|
rouge (1.11.1)
|
||||||
|
safe_yaml (1.0.4)
|
||||||
|
sass (3.5.1)
|
||||||
|
sass-listen (~> 4.0.0)
|
||||||
|
sass-listen (4.0.0)
|
||||||
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||||
|
rb-inotify (~> 0.9, >= 0.9.7)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
jekyll (= 3.5.2)
|
||||||
|
jekyll-feed (~> 0.6)
|
||||||
|
minima (~> 2.0)
|
||||||
|
tzinfo-data
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
1.15.3
|
43
docs/_config.yml
Normal file
43
docs/_config.yml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Welcome to Jekyll!
|
||||||
|
#
|
||||||
|
# This config file is meant for settings that affect your whole blog, values
|
||||||
|
# which you are expected to set up once and rarely edit after that. If you find
|
||||||
|
# yourself editing this file very often, consider using Jekyll's data files
|
||||||
|
# feature for the data you need to update frequently.
|
||||||
|
#
|
||||||
|
# For technical reasons, this file is *NOT* reloaded automatically when you use
|
||||||
|
# 'bundle exec jekyll serve'. If you change this file, please restart the server process.
|
||||||
|
|
||||||
|
# Site settings
|
||||||
|
# These are used to personalize your new site. If you look in the HTML files,
|
||||||
|
# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
|
||||||
|
# You can create any custom variable you would like, and they will be accessible
|
||||||
|
# in the templates via {{ site.myvariable }}.
|
||||||
|
title: Abacus
|
||||||
|
email: danila.fedorin@gmail.com
|
||||||
|
description: > # this means to ignore newlines until "baseurl:"
|
||||||
|
This is the home page of Abacus,
|
||||||
|
a calculator developed during
|
||||||
|
the summer of 2017 as a tool
|
||||||
|
for the more tech-savvy users.
|
||||||
|
baseurl: "/abacus" # the subpath of your site, e.g. /blog
|
||||||
|
url: "htts://danilafe.github.io" # the base hostname & protocol for your site, e.g. http://example.com
|
||||||
|
github_username: DanilaFe
|
||||||
|
include: ['_pages']
|
||||||
|
|
||||||
|
# Build settings
|
||||||
|
markdown: kramdown
|
||||||
|
plugins:
|
||||||
|
- jekyll-feed
|
||||||
|
|
||||||
|
# Exclude from processing.
|
||||||
|
# The following items will not be processed, by default. Create a custom list
|
||||||
|
# to override the default setting.
|
||||||
|
# exclude:
|
||||||
|
# - Gemfile
|
||||||
|
# - Gemfile.lock
|
||||||
|
# - node_modules
|
||||||
|
# - vendor/bundle/
|
||||||
|
# - vendor/cache/
|
||||||
|
# - vendor/gems/
|
||||||
|
# - vendor/ruby/
|
0
docs/_includes/footer.html
Normal file
0
docs/_includes/footer.html
Normal file
17
docs/_includes/head.html
Normal file
17
docs/_includes/head.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<title>{% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}</title>
|
||||||
|
<meta name="description" content="{{ page.excerpt | default: site.description | strip_html | normalize_whitespace | truncate: 160 | escape }}">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{ "assets/css/main.css" | relative_url }}">
|
||||||
|
<link rel="canonical" href="{{ page.url | replace:'index.html','' | absolute_url }}">
|
||||||
|
<link rel="alternate" type="application/rss+xml" title="{{ site.title | escape }}" href="{{ "/feed.xml" | relative_url }}">
|
||||||
|
|
||||||
|
{% if jekyll.environment == 'production' and site.google_analytics %}
|
||||||
|
{% include google-analytics.html %}
|
||||||
|
{% endif %}
|
||||||
|
</head>
|
||||||
|
|
10
docs/_includes/header.html
Normal file
10
docs/_includes/header.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<nav>
|
||||||
|
<div class="center">
|
||||||
|
<a href="{{ "/" | relative_url }}" class="primary-link">{{ site.title }}</a>
|
||||||
|
{% for page in site.pages %}
|
||||||
|
{% if page.in_header %}
|
||||||
|
<a href="{{ page.url | relative_url }}">{{ page.title }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</nav>
|
17
docs/_layouts/base.html
Normal file
17
docs/_layouts/base.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
{% include head.html %}
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{% include header.html %}
|
||||||
|
|
||||||
|
<div class="content center">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include footer.html %}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
159
docs/_layouts/home.html
Normal file
159
docs/_layouts/home.html
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
{% include head.html %}
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
margin-top: 50px;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: "Source Code Pro"
|
||||||
|
}
|
||||||
|
|
||||||
|
img#logo {
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img#image_preview {
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 432px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#buttons {
|
||||||
|
margin-top: 40px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
background-color: white;
|
||||||
|
color: #06e8a4;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
background-color: #06e8a4;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.fullwidth {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.fullwidth img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 450px;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: block;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.white {
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.green {
|
||||||
|
background-color: #06e8a4;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.fullwidth div.double {
|
||||||
|
height: 100%;
|
||||||
|
text-align: left;
|
||||||
|
width: 50%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 40px;
|
||||||
|
float: left;
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 750px) {
|
||||||
|
div.fullwidth div.double {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
div.fullwidth img {
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.fullwidth div.double h1, h2, h3, h4, h5, h6 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<img src="https://raw.githubusercontent.com/DanilaFe/abacus/master/image/logo.png" id="logo">
|
||||||
|
<h1>Abacus</h1>
|
||||||
|
<h2>The programmer's calculator</h2>
|
||||||
|
<div id="buttons">
|
||||||
|
<a class="button inverted" href="{{ "/download" | relative_url }}">Download</a>
|
||||||
|
<a class="button inverted" href="{{ "/about" | relative_url }}">About</a>
|
||||||
|
<a class="button inverted" href="https://github.com/DanilaFe/abacus">Contribute</a>
|
||||||
|
<a class="button inverted" href="https://github.com/DanilaFe/abacus/wiki">Wiki</a>
|
||||||
|
</div>
|
||||||
|
<img src="http://i.imgur.com/Min70QY.png" title="source: imgur.com" id="image_preview"/>
|
||||||
|
<h2>Features</h2>
|
||||||
|
<div class="fullwidth white">
|
||||||
|
<div class="double">
|
||||||
|
<img src="https://i.imgur.com/gmGJBBK.png">
|
||||||
|
</div>
|
||||||
|
<div class="double">
|
||||||
|
<h2>Precision</h2>
|
||||||
|
Abacus uses a mathematical tool called Taylor Series to determine values
|
||||||
|
as accurate as the user desires. Of course, this comes with some
|
||||||
|
performance issues with larger numbers. However, Abacus has been
|
||||||
|
tested to generate the value of e correctly to a thousand digits.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fullwidth green">
|
||||||
|
<div class="double">
|
||||||
|
<h2>Configurable and Customizable</h2>
|
||||||
|
The very first idea for Abacus was inspired by how difficult it was
|
||||||
|
to program a TI-84 calculator. Only two languages were available, TI-BASIC
|
||||||
|
and Assembly, the latter having virtually no documentation. Determined
|
||||||
|
to be better than a TI-84, Abacus implemented a plugin system that allows
|
||||||
|
users to easily create and add plugins written in the same programming
|
||||||
|
language as Abacus itself - Java. These plugins can access the full
|
||||||
|
power of the language, and implement their own ways of handling numbers,
|
||||||
|
as well as their own functions and even operators.<br><br>
|
||||||
|
Besides the ability to add plugins, Abacus also adds some general
|
||||||
|
options that can be used to make the user's experience more pleasant.
|
||||||
|
For instance, it allows for a computation limit to be set in order
|
||||||
|
to prevent excessively long evaluation: 8!!! is, for example, an expression
|
||||||
|
that even Wolfram Alpha doesn't compute accurately, and will never finish
|
||||||
|
on Abacus (it's simply too large). The computation limit will allow Abacus
|
||||||
|
to kill a computation if it takes too long. Support for user-definable
|
||||||
|
precision is also planned.
|
||||||
|
</div>
|
||||||
|
<div class="double">
|
||||||
|
<img src="https://i.imgur.com/JzenWPV.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fullwidth white">
|
||||||
|
<div class="double">
|
||||||
|
<img src="https://i.imgur.com/jY17I3A.png">
|
||||||
|
</div>
|
||||||
|
<div class="double">
|
||||||
|
<h2>Built-in Documentation</h2>
|
||||||
|
Abacus plugins are given a mechanism to register documentation for
|
||||||
|
the functions that they provide. The Abacus GUI displays these
|
||||||
|
functions in a searchable list, allowing the user to read the parameters
|
||||||
|
that have to be supplied to each function, as well as learn about
|
||||||
|
its return value.<br><br>
|
||||||
|
The search finds functions not only by their names, but also by relevant
|
||||||
|
terms mentioned in the function's description, thus allowing related
|
||||||
|
functions to be displayed together.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user