Added $ operator in root package, minor formatting fixes

This commit is contained in:
nocturn9x 2021-03-11 22:29:59 +01:00
parent d0bd200428
commit 4330411843
5 changed files with 57 additions and 27 deletions

View File

@ -149,8 +149,8 @@ echo eval("2+2") # Prints Integer(4)
## Installing ## Installing
You can install the package via nimble with this command: `nimble install https://github.com/nocturn9x/nimkalc` You can install the package via nimble with this command: `nimble install nimkalc`
__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 __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

View File

@ -1,6 +1,6 @@
# Package # Package
version = "0.2" version = "0.2.1"
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"
@ -8,4 +8,4 @@ srcDir = "src"
# Deps # Deps
requires "nim >= 1.2.0" requires "nim >= 1.2.0"

View File

@ -12,14 +12,43 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
## ## Top-level module for nimkalc
import nimkalc/parsing/parser import nimkalc/parsing/parser
import nimkalc/objects/ast import nimkalc/objects/ast
import nimkalc/parsing/lexer import nimkalc/parsing/lexer
import nimkalc/parsing/token
proc eval*(source: string): AstNode = import strutils
import strformat
proc `$`*(self: AstNode): string =
## Stringifies an AST node
case self.kind:
of NodeKind.Grouping:
result = &"Grouping({self.expr})"
of NodeKind.Unary:
result = &"Unary({$self.unOp.kind}, {$self.operand})"
of NodeKind.Binary:
result = &"Binary({$self.left}, {$self.binOp.kind}, {$self.right})"
of NodeKind.Integer:
result = &"Integer({$int(self.value)})"
of NodeKind.Float:
result = &"Float({$self.value})"
of NodeKind.Call:
result = &"Call({self.function.name}, {self.arguments})"
of NodeKind.Ident:
result = &"Identifier({self.name})"
proc `$`*(self: Token): string =
## Returns a string representation of a token
result = &"Token({self.kind}, '{self.lexeme}')"
proc eval*(source: string): AstNode =
## Evaluates a mathematical expression as a string ## Evaluates a mathematical expression as a string
## and returns a leaf node representing the result ## and returns a leaf node representing the result
let l = initLexer() let l = initLexer()

View File

@ -25,7 +25,7 @@ import strutils
type type
NodeKind* {.pure.} = enum NodeKind* {.pure.} = enum
# An enum for all kinds of AST nodes # An enum for all kinds of AST nodes
Grouping, Unary, Binary, Integer, Grouping, Unary, Binary, Integer,
Float, Call, Ident Float, Call, Ident
AstNode* = ref object AstNode* = ref object
# An AST node object # An AST node object
@ -54,12 +54,7 @@ type
# A node visitor object # A node visitor object
proc initNodeVisitor*(): NodeVisitor = proc `$`*(self: AstNode): string =
## Initializes a node visitor
new(result)
proc `$`*(self: AstNode): string =
## Stringifies an AST node ## Stringifies an AST node
case self.kind: case self.kind:
of NodeKind.Grouping: of NodeKind.Grouping:
@ -78,7 +73,13 @@ proc `$`*(self: AstNode): string =
result = &"Identifier({self.name})" result = &"Identifier({self.name})"
template handleBinary(left, right: AstNode, operator: untyped): AstNode =
proc initNodeVisitor*(): NodeVisitor =
## Initializes a node visitor
new(result)
template handleBinary(left, right: AstNode, operator: untyped): AstNode =
## Handy template that avoids us the hassle of copy-pasting ## Handy template that avoids us the hassle of copy-pasting
## the same checks over and over again in the visitor ## the same checks over and over again in the visitor
let r = operator(left.value, right.value) let r = operator(left.value, right.value)
@ -89,7 +90,7 @@ template handleBinary(left, right: AstNode, operator: untyped): AstNode =
AstNode(kind: NodeKind.Float, value: r) AstNode(kind: NodeKind.Float, value: r)
template ensureNonZero(node: AstNode) = template ensureNonZero(node: AstNode) =
## Handy template to ensure that a given node's value is not 0 ## Handy template to ensure that a given node's value is not 0
if node.value == 0.0: if node.value == 0.0:
case node.kind: case node.kind:
@ -99,7 +100,7 @@ template ensureNonZero(node: AstNode) =
raise newException(CatchableError, &"invalid node kind '{node.kind}' for ensureNonZero") raise newException(CatchableError, &"invalid node kind '{node.kind}' for ensureNonZero")
template ensureIntegers(left, right: AstNode) = template ensureIntegers(left, right: AstNode) =
## Ensures both operands are integers ## Ensures both operands are integers
if left.kind != NodeKind.Integer or right.kind != NodeKind.Integer: if left.kind != NodeKind.Integer or right.kind != NodeKind.Integer:
raise newException(MathError, "an integer is required") raise newException(MathError, "an integer is required")
@ -113,7 +114,7 @@ proc visit_grouping(self: NodeVisitor, node: AstNode): AstNode
proc visit_call(self: NodeVisitor, node: AstNode): AstNode proc visit_call(self: NodeVisitor, node: AstNode): AstNode
proc accept(self: AstNode, visitor: NodeVisitor): AstNode = proc accept(self: AstNode, visitor: NodeVisitor): AstNode =
## Implements the accept part of the visitor pattern ## Implements the accept part of the visitor pattern
## for our AST visitor ## for our AST visitor
case self.kind: case self.kind:
@ -129,7 +130,7 @@ proc accept(self: AstNode, visitor: NodeVisitor): AstNode =
result = visitor.visit_call(self) result = visitor.visit_call(self)
proc eval*(self: NodeVisitor, node: AstNode): AstNode = proc eval*(self: NodeVisitor, node: AstNode): AstNode =
## Evaluates an AST node ## Evaluates an AST node
result = node.accept(self) result = node.accept(self)
@ -139,32 +140,32 @@ proc visit_literal(self: NodeVisitor, node: AstNode): AstNode =
result = node # Not that we can do anything else after all, lol result = node # Not that we can do anything else after all, lol
proc visit_call(self: NodeVisitor, node: AstNode): AstNode = proc visit_call(self: NodeVisitor, node: AstNode): AstNode =
## Visits function call expressions ## Visits function call expressions
var args: seq[AstNode] = @[] var args: seq[AstNode] = @[]
for arg in node.arguments: for arg in node.arguments:
args.add(self.eval(arg)) args.add(self.eval(arg))
if node.function.name == "sin": if node.function.name == "sin":
let r = sin(args[0].value) let r = sin(args[0].value)
if r is float: if r is float:
result = AstNode(kind: NodeKind.Float, value: r) result = AstNode(kind: NodeKind.Float, value: r)
else: else:
result = AstNode(kind: NodeKind.Integer, value: float(r)) result = AstNode(kind: NodeKind.Integer, value: float(r))
if node.function.name == "cos": if node.function.name == "cos":
let r = cos(args[0].value) let r = cos(args[0].value)
if r is float: if r is float:
result = AstNode(kind: NodeKind.Float, value: r) result = AstNode(kind: NodeKind.Float, value: r)
else: else:
result = AstNode(kind: NodeKind.Integer, value: float(r)) result = AstNode(kind: NodeKind.Integer, value: float(r))
if node.function.name == "tan": if node.function.name == "tan":
let r = tan(args[0].value) let r = tan(args[0].value)
if r is float: if r is float:
result = AstNode(kind: NodeKind.Float, value: r) result = AstNode(kind: NodeKind.Float, value: r)
else: else:
result = AstNode(kind: NodeKind.Integer, value: float(r)) result = AstNode(kind: NodeKind.Integer, value: float(r))
proc visit_grouping(self: NodeVisitor, node: AstNode): AstNode = proc visit_grouping(self: NodeVisitor, node: AstNode): AstNode =
## Visits grouping (i.e. parenthesized) expressions. Parentheses ## Visits grouping (i.e. parenthesized) expressions. Parentheses
## have no other meaning than to allow a lower-precedence expression ## have no other meaning than to allow a lower-precedence expression
## where a higher-precedence one is expected so that 2 * (3 + 1) is ## where a higher-precedence one is expected so that 2 * (3 + 1) is
@ -172,7 +173,7 @@ proc visit_grouping(self: NodeVisitor, node: AstNode): AstNode =
return self.eval(node.expr) return self.eval(node.expr)
proc visit_binary(self: NodeVisitor, node: AstNode): AstNode = proc visit_binary(self: NodeVisitor, node: AstNode): AstNode =
## Visits a binary AST node and evaluates it ## Visits a binary AST node and evaluates it
let right = self.eval(node.right) let right = self.eval(node.right)
let left = self.eval(node.left) let left = self.eval(node.left)
@ -197,7 +198,7 @@ proc visit_binary(self: NodeVisitor, node: AstNode): AstNode =
discard # Unreachable discard # Unreachable
proc visit_unary(self: NodeVisitor, node: AstNode): AstNode = proc visit_unary(self: NodeVisitor, node: AstNode): AstNode =
## Visits unary expressions and evaluates them ## Visits unary expressions and evaluates them
let expr = self.eval(node.operand) let expr = self.eval(node.operand)
case node.unOp.kind: case node.unOp.kind:

View File

@ -33,5 +33,5 @@ type
proc `$`*(self: Token): string = proc `$`*(self: Token): string =
## Returns a string representation of self ## Returns a string representation of a token
result = &"Token({self.kind}, '{self.lexeme}')" result = &"Token({self.kind}, '{self.lexeme}')"