136 lines
4.7 KiB
Raw 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
mathematical expressions. Notable mentions are support for common mathematical constants (pi, tau, euler's number, etc),
functions (`sin`, `cos`, `tan`...), equation-solving algos using newton's method and scientific notation numbers (such as `2e5`)
## Current limitations
- No functions (coming soon)
- No equation-solving (coming soon)
- The parsing is a bit weird because `2 2` will parse the first 2 and just stop instead of erroring out (FIXME)
## How to use it
NimKalc parses mathematical expressions following this process:
- Tokenize the input
- Generate an AST
- Visit the nodes
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.
## Supported operators
Beyond the classical 4 operators (`+`, `-`, `/` and `*`), NimKalc supports:
- `%` for modulo division
- `^` for exponentiation
- unary `-` for negation
- Arbitrarily nested parentheses (__not__ empty ones!) to enforce precedence
## Exceptions
NimKalc defines 2 exceptions:
- `ParseError` is used when the expression is invalid
- `MathError` is used when there is an arithmetical error such as division by 0 or domain errors (e.g. `log(0)`)
## 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
and `NodeKind.Float` for decimals. It is advised that you take this into account when using the library
__Note__: The string representation of integer nodes won't show the decimal part for clarity
## 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).
Token objects will print as `Token(kind, lexeme)`: an example for the number 2 would be `Token(Integer, '2')`
## 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`)
import nimkalc/objects/ast
import nimkalc/objects/token
import nimkalc/parsing/parser
import nimkalc/parsing/lexer
import nimkalc/objects/error
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:
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)}"
discard # Unreachable
except IOError:
echo "\nGoodbye."
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:
__Note__: If you don't need the intermediate representations shown here (tokens, AST) you can just `import nimkalc` and use
the `eval` procedure, which takes in a string and returns the evaluated result as a primary AST node like so:
import nimkalc
echo eval("2+2") # Prints Integer(4)
## Installing
You can clone this repository and then install the package via nimble:
- `git clone`
- `cd nimkalc`
- `nimble install`
__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