mirror of
https://github.com/DanilaFe/abacus
synced 2024-12-22 15:30:09 -08:00
Write up some of the implementation details.
parent
c27ade3558
commit
3d3d0d5554
17
Internals.md
Normal file
17
Internals.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Number Implementation
|
||||||
|
Abacus, being design with future extensions in mind, does not simply operate on doubles. As other approaches to storing numbers are possible, and it is also possible for the "number" to carry more or less information depending on context, abacus attempts to stay away from a hard-coded implementation. As such, it provides an interface, `NumberInterface`, which is then implemented by an implementation of a number. This interface provides several primitive methods, such as add, subtract, multiply, divide, take to an integer power, etc. The rest of the functions are built on top of these methods, thus being completely implementation-agnostic. This sacrifices speed and prevents abacus from using standard Java library functions like Math.sin, but in exchange allows for the ability to implement the number in many different ways.
|
||||||
|
|
||||||
|
## Immutability
|
||||||
|
All the `NumberInterface` implementations are immutable - any operation, such as addition, creates a new copy of the output. This prevents accidental side effects in functions, and removes the need for duplication when returning instances of `NumberInterface` from functions, or storing them as variables.
|
||||||
|
|
||||||
|
## Promotion System
|
||||||
|
Abacus allows different `NumberInterface` implementations to occur within the same expression. In order to ensure the functionality of the primitive methods of the `NumberInterface` for two different implementations, it's necessary for the these implementations to be able to interact correctly. One option for this would be to use `instanceof` checks, and apply the operations accordingly. However, this would mean a number of hardcoded `instanceof` calls, and potential duplication between implementation. This is undesirable. To work around this, a promotion system was implemented. It is based on the assumption that it's always possible to convert one of the implementations to another, but not necessarily the other way around. As such, when applying operations to numbers, abacus checks which implementation is the most "general", that is, which implementation can be converted into from all present numbers. It then proceeds to "promote" all the other implementations into it. At this point, all of the numbers have the same implementation, and all the primitive operations are defined.
|
||||||
|
|
||||||
|
# Tokenization
|
||||||
|
Before the input can be converted into an expression tree and evaluated, abacus needs to convert it to a list of tokens that can then be rearranged into postfix (the postfix makes it easier to construct a tree). The tokenization is not done via a set of if-else checks, or even a handwritten lexer. Rather, abacus adapts a regular expression based approach, following a modified version of Ken Thompson's [regular expression matching algorithm](https://swtch.com/~rsc/regexp/regexp1.html), implemented using Nondeterministic Finite Automata. An instance of `Lexer` is provided with regular expressions, which are then recursively compiled into NFA, and given identifies, allowing for easy additions of possible token types and changes in grammar. The implementation of Thompson's algorithm is modified to check for all token types at once, examining each input character only once.
|
||||||
|
|
||||||
|
# Conversion to Postfix and Construction of a Tree
|
||||||
|
Unsurprisingly, abacus uses Dijkstra's Shunting Yard algorithm in order to convert infix expressions into postfix. However, the algorithm is modified to fit the purpose of the calculator. For instance, special tokens were added that mark the beginning and the end of a function call, allowing the algorithm to process variable-length function calls. Conversion into a tree is easy after having converted the input into postfix notation.
|
||||||
|
|
||||||
|
# Evaluation
|
||||||
|
Evaluation of the tree is done through the `Reducer` interface and the `reduce` function in the `TreeNode` class. As it can be inferred from its name, the Reducer is a single-function interface (can be a lambda) that is used by the tree to turn it into a single value. The default reducer, `NumberReducer`, walks the tree and attempts to evaluate every operation using the functions provided by all the plugins. At the moment, this is what the calculator uses to display results.
|
Loading…
Reference in New Issue
Block a user