mirror of https://github.com/nocturn9x/nimkalc.git
Fixed various bugs and added a few functions
This commit is contained in:
parent
dfb3794962
commit
c39495309e
34
README.md
34
README.md
|
@ -8,19 +8,43 @@ find any bugs or issues, please report them so we can fix them and make a proper
|
||||||
|
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
- Support for mathematical constants (`pi`, `tau` and `e` right now)
|
- Support for the following mathematical constants:
|
||||||
- Supported functions:
|
- `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:
|
||||||
|
- `binom`
|
||||||
- `sin`
|
- `sin`
|
||||||
- `cos`
|
- `cos`
|
||||||
- `tan`
|
- `tan`
|
||||||
|
- `sinh`
|
||||||
|
- `tanh`
|
||||||
|
- `cosh`
|
||||||
|
- `arccos`
|
||||||
|
- `arcsin`
|
||||||
|
- `arctan`
|
||||||
|
- `arcsinh`
|
||||||
|
- `arccosh`
|
||||||
|
- `arctanh`
|
||||||
|
- `hypot`
|
||||||
- `sqrt`
|
- `sqrt`
|
||||||
- `root` (for generic roots, takes the base and the argument)
|
- `cbrt`
|
||||||
- `log` (logarithm in base `e`)
|
- `log10`
|
||||||
- `logN` (logarithm in a given base, second argument)
|
- `log2`
|
||||||
|
- `ln`
|
||||||
|
- `log`
|
||||||
- Parentheses can be used to enforce different precedence levels
|
- Parentheses can be used to enforce different precedence levels
|
||||||
- Easy API for tokenization, parsing and evaluation of AST nodes
|
- Easy API for tokenization, parsing and evaluation of AST nodes
|
||||||
|
|
||||||
|
|
||||||
|
__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!
|
||||||
|
|
||||||
|
|
||||||
## Current limitations
|
## Current limitations
|
||||||
- No equation-solving (coming soon)
|
- No equation-solving (coming soon)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "0.2.3"
|
version = "0.2.4"
|
||||||
author = "Mattia Giambirtone"
|
author = "Mattia Giambirtone"
|
||||||
description = "An advanced parsing library for mathematical expressions and equations"
|
description = "An advanced parsing library for mathematical expressions and equations"
|
||||||
license = "Apache 2.0"
|
license = "Apache 2.0"
|
||||||
|
|
|
@ -95,7 +95,7 @@ template ensureNonZero(node: AstNode) =
|
||||||
if node.value == 0.0:
|
if node.value == 0.0:
|
||||||
case node.kind:
|
case node.kind:
|
||||||
of NodeKind.Float, NodeKind.Integer:
|
of NodeKind.Float, NodeKind.Integer:
|
||||||
raise newException(MathError, &"{($node.kind).toLowerAscii()} can't be zero")
|
raise newException(MathError, "value can't be zero")
|
||||||
else:
|
else:
|
||||||
raise newException(CatchableError, &"invalid node kind '{node.kind}' for ensureNonZero")
|
raise newException(CatchableError, &"invalid node kind '{node.kind}' for ensureNonZero")
|
||||||
|
|
||||||
|
@ -151,12 +151,53 @@ proc visit_literal(self: NodeVisitor, node: AstNode): AstNode =
|
||||||
|
|
||||||
proc visit_call(self: NodeVisitor, node: AstNode): AstNode =
|
proc visit_call(self: NodeVisitor, node: AstNode): AstNode =
|
||||||
## Visits function call expressions
|
## Visits function call expressions
|
||||||
if node.function.name == "sin":
|
case node.function.name:
|
||||||
callFunction(sin, self.eval(node.arguments[0]).value)
|
of "sin":
|
||||||
if node.function.name == "cos":
|
callFunction(sin, self.eval(node.arguments[0]).value)
|
||||||
callFunction(cos, self.eval(node.arguments[0]).value)
|
of "cos":
|
||||||
if node.function.name == "tan":
|
callFunction(cos, self.eval(node.arguments[0]).value)
|
||||||
callFunction(tan, self.eval(node.arguments[0]).value)
|
of "tan":
|
||||||
|
callFunction(tan, self.eval(node.arguments[0]).value)
|
||||||
|
of "sqrt":
|
||||||
|
callFunction(sqrt, self.eval(node.arguments[0]).value)
|
||||||
|
of "log":
|
||||||
|
let arg = self.eval(node.arguments[0])
|
||||||
|
ensureNonZero(arg)
|
||||||
|
callFunction(log, self.eval(node.arguments[0]).value, self.eval(node.arguments[1]).value)
|
||||||
|
of "ln":
|
||||||
|
let arg = self.eval(node.arguments[0])
|
||||||
|
ensureNonZero(arg)
|
||||||
|
callFunction(ln, self.eval(node.arguments[0]).value)
|
||||||
|
of "log2":
|
||||||
|
let arg = self.eval(node.arguments[0])
|
||||||
|
ensureNonZero(arg)
|
||||||
|
callFunction(log2, self.eval(node.arguments[0]).value)
|
||||||
|
of "log10":
|
||||||
|
let arg = self.eval(node.arguments[0])
|
||||||
|
ensureNonZero(arg)
|
||||||
|
callFunction(log10, self.eval(node.arguments[0]).value)
|
||||||
|
of "cbrt":
|
||||||
|
callFunction(cbrt, self.eval(node.arguments[0]).value)
|
||||||
|
of "tanh":
|
||||||
|
callFunction(sinh, self.eval(node.arguments[0]).value)
|
||||||
|
of "sinh":
|
||||||
|
callFunction(tanh, self.eval(node.arguments[0]).value)
|
||||||
|
of "cosh":
|
||||||
|
callFunction(cosh, self.eval(node.arguments[0]).value)
|
||||||
|
of "arcsin":
|
||||||
|
callFunction(arcsin, self.eval(node.arguments[0]).value)
|
||||||
|
of "arccos":
|
||||||
|
callFunction(arccos, self.eval(node.arguments[0]).value)
|
||||||
|
of "arctan":
|
||||||
|
callFunction(arctan, self.eval(node.arguments[0]).value)
|
||||||
|
of "arctanh":
|
||||||
|
callFunction(arctanh, self.eval(node.arguments[0]).value)
|
||||||
|
of "arcsinh":
|
||||||
|
callFunction(arcsinh, self.eval(node.arguments[0]).value)
|
||||||
|
of "arccosh":
|
||||||
|
callFunction(arccosh, self.eval(node.arguments[0]).value)
|
||||||
|
of "hypot":
|
||||||
|
callFunction(hypot, self.eval(node.arguments[0]).value, self.eval(node.arguments[1]).value)
|
||||||
|
|
||||||
|
|
||||||
proc visit_grouping(self: NodeVisitor, node: AstNode): AstNode =
|
proc visit_grouping(self: NodeVisitor, node: AstNode): AstNode =
|
||||||
|
|
|
@ -35,10 +35,16 @@ const tokens = to_table({
|
||||||
const constants = to_table({
|
const constants = to_table({
|
||||||
"pi": Token(kind: TokenType.Float, lexeme: "3.141592653589793"),
|
"pi": Token(kind: TokenType.Float, lexeme: "3.141592653589793"),
|
||||||
"e": Token(kind: TokenType.Float, lexeme: "2.718281828459045"),
|
"e": Token(kind: TokenType.Float, lexeme: "2.718281828459045"),
|
||||||
"tau": Token(kind: TokenType.Float, lexeme: "6.283185307179586")
|
"tau": Token(kind: TokenType.Float, lexeme: "6.283185307179586"),
|
||||||
|
"inf": Token(kind: TokenType.Float, lexeme: "inf"),
|
||||||
|
"nan": Token(kind: TokenType.Float, lexeme: "nan")
|
||||||
})
|
})
|
||||||
# Since also math functions are hardcoded, we can use an array
|
# Since also math functions are hardcoded, we can use an array
|
||||||
const functions = ["sin", "cos", "tan"]
|
const functions = ["sin", "cos", "tan", "cosh",
|
||||||
|
"tanh", "sinh", "arccos", "arcsin",
|
||||||
|
"arctan", "log", "log10", "ln", "log2",
|
||||||
|
"hypot", "sqrt", "cbrt", "arctanh", "arcsinh",
|
||||||
|
"arccosh"]
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
|
@ -32,7 +32,12 @@ type
|
||||||
current: int
|
current: int
|
||||||
|
|
||||||
|
|
||||||
const arities = to_table({"sin": 1, "cos": 1, "tan": 1})
|
const arities = to_table({"sin": 1, "cos": 1, "tan": 1, "cosh": 1,
|
||||||
|
"tanh": 1, "sinh": 1, "arccos": 1, "arcsin": 1,
|
||||||
|
"arctan": 1, "log": 2, "log10": 1, "ln": 1, "log2": 1,
|
||||||
|
"hypot": 2, "sqrt": 1, "cbrt": 2, "arctanh": 1, "arcsinh": 1,
|
||||||
|
"arccosh": 1
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
proc initParser*(): Parser =
|
proc initParser*(): Parser =
|
||||||
|
@ -150,16 +155,16 @@ proc call(self: Parser): AstNode =
|
||||||
## Parses function calls such as sin(2)
|
## Parses function calls such as sin(2)
|
||||||
var expression = self.primary()
|
var expression = self.primary()
|
||||||
if self.match(TokenType.LeftParen):
|
if self.match(TokenType.LeftParen):
|
||||||
if expression.kind != NodeKind.Ident:
|
|
||||||
self.error(&"object of type '{expression.kind}' is not callable")
|
|
||||||
var arguments: seq[AstNode] = @[]
|
var arguments: seq[AstNode] = @[]
|
||||||
if not self.check(TokenType.RightParen):
|
if not self.check(TokenType.RightParen):
|
||||||
arguments.add(self.binary())
|
arguments.add(self.binary())
|
||||||
while self.match(TokenType.Comma):
|
while self.match(TokenType.Comma):
|
||||||
arguments.add(self.binary())
|
arguments.add(self.binary())
|
||||||
result = AstNode(kind: NodeKind.Call, arguments: arguments, function: expression)
|
result = AstNode(kind: NodeKind.Call, arguments: arguments, function: expression)
|
||||||
|
if expression.kind != NodeKind.Ident:
|
||||||
|
self.error(&"can't call object of type {expression.kind}")
|
||||||
if len(arguments) != arities[expression.name]:
|
if len(arguments) != arities[expression.name]:
|
||||||
self.error(&"Wrong number of arguments for '{expression.name}': expected {arities[expression.name]}, got {len(arguments)}")
|
self.error(&"wrong number of arguments for '{expression.name}': expected {arities[expression.name]}, got {len(arguments)}")
|
||||||
self.expect(TokenType.RightParen, "unclosed function call")
|
self.expect(TokenType.RightParen, "unclosed function call")
|
||||||
else:
|
else:
|
||||||
result = expression
|
result = expression
|
||||||
|
|
Loading…
Reference in New Issue