From c39495309e49b43a347552fa112a64f1b55c3363 Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Fri, 12 Mar 2021 18:38:52 +0100 Subject: [PATCH] Fixed various bugs and added a few functions --- README.md | 34 +++++++++++++++++---- nimkalc.nimble | 2 +- src/nimkalc/objects/ast.nim | 55 +++++++++++++++++++++++++++++----- src/nimkalc/parsing/lexer.nim | 10 +++++-- src/nimkalc/parsing/parser.nim | 13 +++++--- 5 files changed, 95 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index d21b66e..2a1c1de 100644 --- a/README.md +++ b/README.md @@ -8,19 +8,43 @@ find any bugs or issues, please report them so we can fix them and make a proper Features: -- Support for mathematical constants (`pi`, `tau` and `e` right now) -- Supported functions: +- Support for the following mathematical constants: + - `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` - `cos` - `tan` + - `sinh` + - `tanh` + - `cosh` + - `arccos` + - `arcsin` + - `arctan` + - `arcsinh` + - `arccosh` + - `arctanh` + - `hypot` - `sqrt` - - `root` (for generic roots, takes the base and the argument) - - `log` (logarithm in base `e`) - - `logN` (logarithm in a given base, second argument) + - `cbrt` + - `log10` + - `log2` + - `ln` + - `log` - Parentheses can be used to enforce different precedence levels - 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 - No equation-solving (coming soon) diff --git a/nimkalc.nimble b/nimkalc.nimble index 8187313..3815e16 100644 --- a/nimkalc.nimble +++ b/nimkalc.nimble @@ -1,6 +1,6 @@ # Package -version = "0.2.3" +version = "0.2.4" author = "Mattia Giambirtone" description = "An advanced parsing library for mathematical expressions and equations" license = "Apache 2.0" diff --git a/src/nimkalc/objects/ast.nim b/src/nimkalc/objects/ast.nim index cd685de..86ec55f 100644 --- a/src/nimkalc/objects/ast.nim +++ b/src/nimkalc/objects/ast.nim @@ -95,7 +95,7 @@ template ensureNonZero(node: AstNode) = if node.value == 0.0: case node.kind: of NodeKind.Float, NodeKind.Integer: - raise newException(MathError, &"{($node.kind).toLowerAscii()} can't be zero") + raise newException(MathError, "value can't be zero") else: 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 = ## Visits function call expressions - if node.function.name == "sin": - callFunction(sin, self.eval(node.arguments[0]).value) - if node.function.name == "cos": - callFunction(cos, self.eval(node.arguments[0]).value) - if node.function.name == "tan": - callFunction(tan, self.eval(node.arguments[0]).value) + case node.function.name: + of "sin": + callFunction(sin, self.eval(node.arguments[0]).value) + of "cos": + callFunction(cos, 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 = diff --git a/src/nimkalc/parsing/lexer.nim b/src/nimkalc/parsing/lexer.nim index 19b117e..96cc3fa 100644 --- a/src/nimkalc/parsing/lexer.nim +++ b/src/nimkalc/parsing/lexer.nim @@ -35,10 +35,16 @@ const tokens = to_table({ const constants = to_table({ "pi": Token(kind: TokenType.Float, lexeme: "3.141592653589793"), "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 -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 diff --git a/src/nimkalc/parsing/parser.nim b/src/nimkalc/parsing/parser.nim index 0e100fd..a4dbceb 100644 --- a/src/nimkalc/parsing/parser.nim +++ b/src/nimkalc/parsing/parser.nim @@ -32,7 +32,12 @@ type 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 = @@ -150,16 +155,16 @@ proc call(self: Parser): AstNode = ## Parses function calls such as sin(2) var expression = self.primary() if self.match(TokenType.LeftParen): - if expression.kind != NodeKind.Ident: - self.error(&"object of type '{expression.kind}' is not callable") var arguments: seq[AstNode] = @[] if not self.check(TokenType.RightParen): arguments.add(self.binary()) while self.match(TokenType.Comma): arguments.add(self.binary()) 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]: - 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") else: result = expression