1
0
mirror of https://github.com/DanilaFe/abacus synced 2024-12-21 15:00:09 -08:00
3 Internals
Danila edited this page 2017-07-26 21:48:59 -07:00

Number Implementation

Abacus, being designed 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 implementations. 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, 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.