Skip to content

Commit df4c129

Browse files
committed
update calculator example
Signed-off-by: George Lemon <georgelemon@protonmail.com>
1 parent a5d80de commit df4c129

1 file changed

Lines changed: 121 additions & 46 deletions

File tree

examples/calculator.nim

Lines changed: 121 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,126 @@
1-
import std/options
1+
import std/[options, strutils]
22

33
import ../src/vancode/interpreter/[ast, codegen, chunk, value, vm, sym]
44
import ../src/vancode/interpreter/stdlib/syslib
55

6-
# 1. Build the AST for: 1 + 2 * 3
7-
let astExpr =
8-
ast.newCall(
9-
ast.newIdent("echo"),
10-
ast.newTree(nkInfix,
11-
ast.newIdent("+"),
12-
ast.newIntLit(1),
13-
ast.newTree(nkInfix,
14-
ast.newIdent("*"),
15-
ast.newIntLit(2),
16-
ast.newIntLit(3)
17-
)
6+
when isMainModule:
7+
proc parseExpr(tokens: seq[string]): Node =
8+
# Recursive parser with operator precedence for +, -, *, /
9+
# Handles expressions like: 1 + 2 * 3 - 4 / 2
10+
11+
proc parsePrimary(i: var int): Node =
12+
if i >= tokens.len:
13+
raise newException(ValueError, "Unexpected end of input.")
14+
let t = tokens[i]
15+
inc i
16+
try:
17+
if t.contains('.'):
18+
result = ast.newFloatLit(parseFloat(t))
19+
else:
20+
result = ast.newIntLit(parseInt(t))
21+
except ValueError:
22+
raise newException(ValueError, "Expected a number, got: '" & t & "'")
23+
24+
proc precedence(op: string): int =
25+
case op
26+
of "+", "-": 1
27+
of "*", "/": 2
28+
else: -1
29+
30+
proc parseBinOpRhs(i: var int, exprPrec: int, lhs: Node): Node =
31+
var lhs = lhs
32+
while i < tokens.len:
33+
let op = tokens[i]
34+
let opPrec = precedence(op)
35+
36+
if opPrec < 0:
37+
raise newException(ValueError, "Unknown operator '" & op & "'.")
38+
if opPrec < exprPrec:
39+
break
40+
41+
inc i
42+
var rhs = parsePrimary(i)
43+
# If next operator has higher precedence, parse it first
44+
while i < tokens.len:
45+
let nextOp = tokens[i]
46+
let nextPrec = precedence(nextOp)
47+
if nextPrec < 0:
48+
raise newException(ValueError, "Unknown operator '" & nextOp & "'.")
49+
if nextPrec > opPrec:
50+
rhs = parseBinOpRhs(i, opPrec + 1, rhs)
51+
else:
52+
break
53+
54+
# Combine lhs and rhs into a new AST node
55+
lhs = ast.newTree(nkInfix, ast.newIdent(op), lhs, rhs)
56+
result = lhs
57+
58+
# ain't good
59+
if tokens.len < 3 or tokens.len mod 2 == 0:
60+
raise newException(ValueError, "Usage: <int|float> <op> <int|float> [<op> <int|float>] ...")
61+
62+
var i = 0
63+
let lhs = parsePrimary(i)
64+
result = parseBinOpRhs(i, 1, lhs)
65+
66+
if i != tokens.len:
67+
raise newException(ValueError, "Unexpected token: '" & tokens[i] & "'.")
68+
69+
proc evalLine(line: string) =
70+
let tokens = line.splitWhitespace()
71+
let exprAst = parseExpr(tokens)
72+
73+
let astExpr = ast.newCall(ast.newIdent("echo"), exprAst)
74+
let astScript = Ast(
75+
sourcePath: "calculator-repl",
76+
nodes: @[astExpr]
1877
)
19-
)
20-
21-
# 2. Wrap in a script AST node
22-
let astScript = Ast(
23-
sourcePath: "calculator",
24-
nodes: @[astExpr]
25-
)
26-
27-
# 3. Prepare codegen context
28-
let mainChunk = newChunk("calculator")
29-
let script = newScript(mainChunk)
30-
31-
let module = newModule("calculator", some("calculator"))
32-
block init_system_module:
33-
module.initSystemTypes()
34-
script.initSystemOps(module)
35-
36-
# Adding a FFI proc for `echo` so we can see the output of the calculation
37-
script.addProc(module, "echo", @[paramDef("x", ttyInt)], ttyVoid,
38-
proc (args: StackView, argc: int): Value =
39-
echo args[0].intVal)
40-
41-
script.addProc(module, "echo", @[paramDef("x", ttyFloat)], ttyVoid,
42-
proc (args: StackView, argc: int): Value =
43-
echo args[0].floatVal)
44-
45-
# 4. Generate bytecode from AST
46-
let gen = initCompiler(script, module, mainChunk, nil, nil)
47-
gen.genScript(astScript, none(string))
48-
49-
# 5. Run in the VM
50-
let vmInstance = newVm()
51-
discard vmInstance.interpret(script, mainChunk)
78+
79+
# Setup the script and module
80+
let mainChunk = newChunk("calculator-repl")
81+
let script = newScript(mainChunk)
82+
let module = newModule("calculator", some("calculator"))
83+
84+
module.initSystemTypes()
85+
script.initSystemOps(module)
86+
87+
# Add an 'echo' procedure to print results, overloaded for int and float
88+
# basically, this is a FFI for Native Nim functions, we can add any proc we want
89+
# so we can build our own standard library on top of it. Crazy!
90+
script.addProc(module, "echo", @[paramDef("x", ttyInt)], ttyVoid,
91+
proc (args: StackView, argc: int): Value =
92+
echo args[0].intVal)
93+
94+
script.addProc(module, "echo", @[paramDef("x", ttyFloat)], ttyVoid,
95+
proc (args: StackView, argc: int): Value =
96+
echo args[0].floatVal)
97+
98+
# Generate bytecode for the script
99+
let gen = initCompiler(script, module, mainChunk, nil, nil)
100+
gen.genScript(astScript, none(string))
101+
102+
# Execute the bytecode in the VM
103+
let vmInstance = newVm()
104+
discard vmInstance.interpret(script, mainChunk)
105+
106+
# cli stuff
107+
echo "Calculator REPL. Type expressions like: 1 + 1 * 3"
108+
echo "Type 'exit' or 'quit' to stop."
109+
while true:
110+
stdout.write("calc> ")
111+
stdout.flushFile()
112+
113+
var line: string
114+
if not stdin.readLine(line):
115+
break
116+
117+
line = line.strip()
118+
if line.len == 0:
119+
continue
120+
if line == "exit" or line == "quit":
121+
break
122+
123+
try:
124+
evalLine(line)
125+
except CatchableError as e:
126+
echo "Error: ", e.msg

0 commit comments

Comments
 (0)