4.7 KiB
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 invalidMathError
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:
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()
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 https://github.com/nocturn9x/nimkalc
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