Merge pull request #494 from alimpfard/master
[jackhill/mal.git] / skew / step6_file.sk
CommitLineData
034e82ad
DM
1def READ(str string) MalVal {
2 return read_str(str)
3}
4
5def eval_ast(ast MalVal, env Env) MalVal {
6 if ast is MalSymbol {
7 return env.get(ast as MalSymbol)
8 } else if ast is MalList {
9 return MalList.new((ast as MalList).val.map<MalVal>(e => EVAL(e, env)))
10 } else if ast is MalVector {
11 return MalVector.new((ast as MalVector).val.map<MalVal>(e => EVAL(e, env)))
12 } else if ast is MalHashMap {
13 var result List<MalVal> = []
14 (ast as MalHashMap).val.each((k string, v MalVal) => {
15 result.append(EVAL(MalVal.fromHashKey(k), env))
16 result.append(EVAL(v, env))
17 })
18 return MalHashMap.fromList(result)
19 } else {
20 return ast
21 }
22}
23
24def EVAL(ast MalVal, env Env) MalVal {
25 while true {
26 if !(ast is MalList) { return eval_ast(ast, env) }
27 const astList = ast as MalList
28 if astList.isEmpty { return ast }
29 const a0sym = astList[0] as MalSymbol
30 if a0sym.val == "def!" {
31 return env.set(astList[1] as MalSymbol, EVAL(astList[2], env))
32 } else if a0sym.val == "let*" {
33 var letenv = Env.new(env)
34 const assigns = astList[1] as MalSequential
35 for i = 0; i < assigns.count; i += 2 {
36 letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv))
37 }
38 ast = astList[2]
39 env = letenv
40 continue # TCO
41 } else if a0sym.val == "do" {
42 const parts = astList.val.slice(1)
43 eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env)
44 ast = parts[parts.count - 1]
45 continue # TCO
46 } else if a0sym.val == "if" {
47 const condRes = EVAL(astList[1], env)
48 if condRes is MalNil || condRes is MalFalse {
49 ast = astList.count > 3 ? astList[3] : gNil
50 } else {
51 ast = astList[2]
52 }
53 continue # TCO
54 } else if a0sym.val == "fn*" {
55 const argsNames = astList[1] as MalSequential
56 return MalFunc.new(astList[2], argsNames, env, (args List<MalVal>) => EVAL(astList[2], Env.new(env, argsNames.val, args)))
57 } else {
58 const evaledList = eval_ast(ast, env) as MalList
59 const fn = evaledList[0]
60 const callArgs = evaledList.val.slice(1)
61 if fn is MalNativeFunc {
62 return (fn as MalNativeFunc).call(callArgs)
63 } else if fn is MalFunc {
64 const f = fn as MalFunc
65 ast = f.ast
66 env = Env.new(f.env, f.params.val, callArgs)
67 continue # TCO
68 } else {
69 throw MalError.new("Expected function as head of list")
70 }
71 }
72 }
73}
74
75def PRINT(exp MalVal) string {
76 return exp?.print(true)
77}
78
79var repl_env = Env.new(null)
80
81def RE(str string) MalVal {
82 return EVAL(READ(str), repl_env)
83}
84
85def REP(str string) string {
86 return PRINT(RE(str))
87}
88
89@entry
90def main {
91 # core.sk: defined using Skew
92 ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func)))
93 repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map<MalVal>(e => MalString.new(e))))
94
95 # core.mal: defined using the language itself
96 RE("(def! not (fn* (a) (if a false true)))")
e6d41de4 97 RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))")
034e82ad
DM
98
99 if argv.count > 0 {
100 RE("(load-file \"" + argv[0] + "\")")
101 return
102 }
103
104 var line string
105 while (line = readLine("user> ")) != null {
106 if line == "" { continue }
107 try {
108 printLn(REP(line))
109 }
110 catch e MalError {
111 printLn("Error: \(e.message)")
112 }
113 catch e Error {
114 printLn("Error: \(e.message)")
115 }
116 }
117}