1- import std/ options
1+ import std/ [ options, strutils]
22
33import ../ src/ vancode/ interpreter/ [ast, codegen, chunk, value, vm, sym]
44import ../ 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