-
Notifications
You must be signed in to change notification settings - Fork 0
/
shunting_yard.nim
149 lines (123 loc) · 3.46 KB
/
shunting_yard.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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