mirror of
https://github.com/nocturn9x/nimkalc.git
synced 2026-09-17 02:16:02 +02:00
Version 0.1
This commit is contained in:
136
README.md
Normal file
136
README.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# 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`)
|
||||
|
||||
```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:
|
||||
|
||||
```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
|
||||
Reference in New Issue
Block a user