Browse Source

Shunting-Yard implementation in Nim

pull/1/head
Wesley Kerfoot 3 years ago
parent
commit
fa9478665e
  1. 149
      shunting_yard.nim

149
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
Loading…
Cancel
Save