From fa9478665ea63259b8ad7f5075c57c7a31757935 Mon Sep 17 00:00:00 2001 From: Wesley Kerfoot Date: Mon, 20 Jan 2020 20:24:00 -0500 Subject: [PATCH] Shunting-Yard implementation in Nim --- shunting_yard.nim | 149 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 shunting_yard.nim diff --git a/shunting_yard.nim b/shunting_yard.nim new file mode 100644 index 0000000..1edf09a --- /dev/null +++ b/shunting_yard.nim @@ -0,0 +1,149 @@ +import deques, parseutils, tables, strutils, math + +type + RPNKind = enum + rpnkNum, + rpnkOp + RPNVal = ref object + case kind: RPNKind + of rpnkNum: num: float + of rpnkOp: op: string + +proc tokenize(expr : string) : seq[string] = + var currentTok : string + for c in expr: + if c in Whitespace: + if currentTok.len > 0: + result = result & currentTok + currentTok = "" + continue + + if c == '(' or c == ')': + if currentTok.len > 0: + result = result & currentTok + currentTok = "" + + result = result & $c + + if c in Digits: + currentTok = currentTok & c + + if c in "+-*/^": + currentTok = "" + result = result & $c + + if currentTok.len > 0: + result = result & currentTok + +proc toRPN(expr : string) : seq[RPNVal] = + let prec = { + "^" : 0, + "+" : 2, + "-" : 2, + "*" : 1, + "/" : 1 + }.toTable + + let assoc = { + "+" : "left", + "-" : "left", + "*" : "left", + "/" : "left", + "^" : "right" + }.toTable + + var stack : seq[string] + var q = initDeque[string]() + + var tokens = expr.tokenize + + while tokens.len > 0: + let tok = tokens[0] + + if tok[0] in Digits: + q.addLast(tok) + + if not (tok[0] in Digits): + if tok == ")": + # Drain the stack onto the queue + while stack.len > 0: + let op = stack[0] + stack = stack[1..^1] + if op == "(": + break + q.addLast(op) + tokens = tokens[1..^1] + continue + + if stack.len > 0 and (stack[0] in prec) and (tok in prec): + # there are ops the op on the stack has higher prec + # or the op on the stack is left assoc and == prec + while (stack.len > 0 and + ((prec[stack[0]] < prec[tok]) or + (assoc[stack[0]] == "left" and prec[stack[0]] == prec[tok]))): + # We want to evaluate higher precedence sub-expressions first + # This should also check the associativity + # If we have 3 ^ 2 + 4, it should be 3 2 ^ 4 +, not 3 2 4 + ^ + # If the operator is right-associative, that means we will leave it + q.addLast(stack[0]) + stack = stack[1..^1] + + stack = @[tok] & stack + tokens = tokens[1..^1] + + while stack.len > 0: + q.addLast(stack[0]) + stack = stack[1..^1] + + while q.len > 0: + var numRes : float + let tok = q.popFirst + let num = tok.parseBiggestFloat(numRes) + if numRes == 0: + # It's an operator + result = result & RPNVal(kind: rpnkOp, op: $tok) + else: + # It's a number + result = result & RPNVal(kind: rpnkNum, num: numRes) + +proc operate(stack : seq[float], op : RPNVal) : seq[float] = + let a : float = stack[1] + let b : float = stack[0] + + var res : float + + if op.op == "+": + res = a + b + if op.op == "-": + res = a - b + if op.op == "*": + res = a * b + if op.op == "/": + res = a / b + if op.op == "^": + res = a.pow(b) + + result = stack[2..^1] + result = @[res] & result + +proc calculate(expr : string) : float = + let tokens = expr.toRPN + var stack : seq[float] + + for token in tokens: + if token.kind == rpnkNum: + # If it's a number, push it onto the stack + stack = @[token.num] & stack + if token.kind == rpnkOp: + # If it's an operator, pop two elements off + # Then push the result back on + stack = stack.operate(token) + + assert(stack.len == 1) + stack[0] + +echo "7 / (2 ^ 3) ^ (4 * 2) + 10".calculate +echo "2 ^ 3 ^ 4".calculate +echo "3 ^ 2 + 4".calculate +for tok in "2 ^ 3 ^ 4".toRPN: + echo tok.repr