mirror of https://github.com/nocturn9x/nimkalc.git
136 lines
4.7 KiB
Markdown
136 lines
4.7 KiB
Markdown

# NimKalc  A math parsing library






NimKalc is a simple implementation of a recursivedescent topdown parser that can evaluate



mathematical expressions. Notable mentions are support for common mathematical constants (pi, tau, euler's number, etc),



functions (`sin`, `cos`, `tan`...), equationsolving algos using newton's method and scientific notation numbers (such as `2e5`)









## Current limitations



 No functions (coming soon)



 No equationsolving (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`)






```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)



# Noone 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:






```nim



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
