nimkalc/README.md

174 lines
6.2 KiB
Markdown
Raw Permalink Normal View History

2021-03-11 11:12:49 +01:00
# NimKalc - A math parsing library
NimKalc is a simple implementation of a recursive-descent top-down parser that can evaluate
2021-03-11 20:02:51 +01:00
mathematical expressions.
2021-03-11 20:03:27 +01:00
__Disclaimer__: This library is __in beta__ and is not fully tested yet. It will be soon, though. If you
find any bugs or issues, please report them so we can fix them and make a proper test suite!
2021-03-11 20:02:51 +01:00
Features:
- Support for the following mathematical constants:
- `pi`
- `tau` (pi * 2)
- `e` (Euler's number)
- `inf` (Infinity)
- `nan` (Not a number)
- Support for the following of nim's [math library](https://nim-lang.org/docs/math.html#log10%2Cfloat32) functions:
2021-03-11 20:02:51 +01:00
- `sin`
- `cos`
- `tan`
- `sinh`
- `tanh`
- `cosh`
- `arccos`
- `arcsin`
- `arctan`
- `arcsinh`
- `arccosh`
- `arctanh`
- `hypot`
2021-03-11 20:02:51 +01:00
- `sqrt`
- `cbrt`
- `log10`
- `log2`
- `ln`
- `log`
2021-03-11 20:02:51 +01:00
- Parentheses can be used to enforce different precedence levels
- Easy API for tokenization, parsing and evaluation of AST nodes
2021-03-11 11:12:49 +01:00
__Note__: Some procedures were not implemented because for any of the following reasons:
- They return booleans or other custom types that we don't support, like `classify`
- They weren't useful enough or their functionality was already implemented in other ways (such as `pow` which we use as the `^` operator)
- They just haven't made their way into the library yet, be patient!
2021-03-11 11:12:49 +01:00
## Current limitations
- No equation-solving (coming soon)
## How to use it
NimKalc parses mathematical expressions following this process:
- Tokenize the input
- Generate an AST
- Visit the nodes
2021-03-11 20:02:51 +01:00
Each of these steps can be run separately, but for convenience a wrapper `eval` procedure has been defined which takes in a string
and returns a single AST node containing the result of the given expression.
2021-03-11 11:12:49 +01:00
## Supported operators
Beyond the classical 4 operators (`+`, `-`, `/` and `*`), NimKalc supports:
- `%` for modulo division
- `^` for exponentiation
- unary `-` for negation
## Exceptions
2021-03-11 20:02:51 +01:00
NimKalc defines various exceptions:
- `NimKalcException` is a generic superclass for all errors
- `ParseError` is used when the expression is syntactically invalid
2021-03-11 11:12:49 +01:00
- `MathError` is used when there is an arithmetical error such as division by 0 or domain errors (e.g. `log(0)`)
2021-03-11 20:02:51 +01:00
- `EvaluationError` is used when the runtime evaluation of an expression fails (e.g. trying to call something that isn't a function)
2021-03-11 11:12:49 +01:00
## Design
NimKalc treats all numerical values as `float` to simplify the implementation of the underlying operators. To tell integers
from floating point numbers the `AstNode` object has a `kind` discriminant which will be equal to `NodeKind.Integer` for ints
2021-03-11 20:02:51 +01:00
and `NodeKind.Float` for decimals. It is advised that you take this into account when using the library, since integers might
start losing precision when converted from their float counterpart due to the difference of the two types. Everything should
2022-04-10 14:02:38 +02:00
be fine as long as the value doesn't exceed 2 ^ 53 though.
2021-03-11 11:12:49 +01:00
__Note__: The string representation of integer nodes won't show the decimal part for clarity
2021-03-11 20:02:51 +01:00
Some other notable design choices (due to the underlying simplicity of the language we parse) are as follows:
- Identifiers are checked when tokenizing, since they're all constant
- Mathematical constants are immediately mapped to their real values when tokenizing with no intermediate steps or tokens
- Type errors (such as trying to call an integer) are detected statically at parse time
2021-03-11 11:12:49 +01:00
## String representations
All of NimKalc's objects implement the `$` operator and are therefore printable. Integer nodes will look like `Integer(x)`, while
floats are represented with `Float(x.x)`. Unary operators print as `Unary(operator, right)`, while binary operators print as `Binary(left, operator, right)`.
Parenthesized expressions print as `Grouping(expr)`, where `expr` is the expression enclosed in parentheses (as an AST node, obviously).
2021-03-11 20:02:51 +01:00
Token objects will print as `Token(kind, lexeme)`: an example for the number 2 would be `Token(Integer, '2')`. Function calls print like `Call(name, args)`
where `name` is the function name and `args` is a list of arguments
2021-03-11 11:12:49 +01:00
## Example
Here is an example of a REPL using all of NimKalc's functionality to evaluate expressions from stdin (can be found at `examples/repl.nim`)
```nim
import nimkalc
2021-03-11 11:12:49 +01:00
import strformat
import strutils
proc repl() =
## A simple REPL to demonstrate NimKalc's functionality
var line: string
var result: AstNode
var tokens: seq[Token]
let lexerObj = initLexer()
let parserObj = initParser()
let visitor = initNodeVisitor()
echo "Welcome to the NimKalc REPL, type a math expression and press enter"
while true:
try:
stdout.write("=> ")
line = stdin.readLine()
echo &"Parsing and evaluation of {line} below:"
tokens = lexerObj.lex(line)
# No-one cares about the EOF token after all
echo &"Tokenization of {line}: {tokens[0..^2].join(\", \")}"
result = parserObj.parse(tokens)
echo &"AST for {line}: {result}"
result = visitor.eval(result)
case result.kind:
# The result is an AstNode object, specifically
# either a node of type NodeKind.Float or a NodeKind.Integer
of NodeKind.Float:
echo &"Value of {line}: {result.value}"
of NodeKind.Integer:
echo &"Value of {line}: {int(result.value)}"
else:
discard # Unreachable
except IOError:
echo "\nGoodbye."
break
except ParseError:
echo &"A parsing error occurred: {getCurrentExceptionMsg()}"
except MathError:
echo &"An arithmetic error occurred: {getCurrentExceptionMsg()}"
except OverflowDefect:
echo &"Value overflow/underflow detected: {getCurrentExceptionMsg()}"
when isMainModule:
repl()
```
2021-03-11 20:02:51 +01:00
__Note__: If you don't need the intermediate representations shown here (tokens/AST) you can just `import nimkalc` and use
2021-03-11 11:12:49 +01:00
the `eval` procedure, which takes in a string and returns the evaluated result as a primary AST node like so:
```nim
import nimkalc
echo eval("2+2") # Prints Integer(4)
```
## Installing
You can install the package via nimble with this command: `nimble install nimkalc`
2021-03-11 11:38:05 +01:00
2021-03-11 11:12:49 +01:00
__Note__: Nim 1.2.0 or higher is required to build NimKalc! Other versions are likely work if they're not too old, but they have not been tested