ada.2: typo
[jackhill/mal.git] / impls / skew / step9_try.sk
CommitLineData
034e82ad
DM
1def READ(str string) MalVal {
2 return read_str(str)
3}
4
5def isPair(a MalVal) bool {
6 return a is MalSequential && !(a as MalSequential).isEmpty
7}
8
9def quasiquote(ast MalVal) MalVal {
10 if !isPair(ast) {
11 return MalList.new([MalSymbol.new("quote"), ast])
12 }
13 const astSeq = ast as MalSequential
14 const a0 = astSeq[0]
15 if a0.isSymbol("unquote") {
16 return astSeq[1]
17 }
18 if isPair(a0) {
19 const a0Seq = a0 as MalSequential
20 if a0Seq[0].isSymbol("splice-unquote") {
21 return MalList.new([MalSymbol.new("concat"), a0Seq[1], quasiquote(astSeq.rest)])
22 }
23 }
24 return MalList.new([MalSymbol.new("cons"), quasiquote(a0), quasiquote(astSeq.rest)])
25}
26
27def isMacro(ast MalVal, env Env) bool {
28 if !(ast is MalList) { return false }
29 const astList = ast as MalList
30 if astList.isEmpty { return false }
31 const a0 = astList[0]
32 if !(a0 is MalSymbol) { return false }
33 const a0Sym = a0 as MalSymbol
34 if env.find(a0Sym) == null { return false }
35 const f = env.get(a0Sym)
36 if !(f is MalFunc) { return false }
37 return (f as MalFunc).isMacro
38}
39
40def macroexpand(ast MalVal, env Env) MalVal {
41 while isMacro(ast, env) {
42 const astList = ast as MalList
43 const mac = env.get(astList[0] as MalSymbol) as MalFunc
44 ast = mac.call((astList.rest as MalSequential).val)
45 }
46 return ast
47}
48
49def eval_ast(ast MalVal, env Env) MalVal {
50 if ast is MalSymbol {
51 return env.get(ast as MalSymbol)
52 } else if ast is MalList {
53 return MalList.new((ast as MalList).val.map<MalVal>(e => EVAL(e, env)))
54 } else if ast is MalVector {
55 return MalVector.new((ast as MalVector).val.map<MalVal>(e => EVAL(e, env)))
56 } else if ast is MalHashMap {
57 var result List<MalVal> = []
58 (ast as MalHashMap).val.each((k string, v MalVal) => {
59 result.append(EVAL(MalVal.fromHashKey(k), env))
60 result.append(EVAL(v, env))
61 })
62 return MalHashMap.fromList(result)
63 } else {
64 return ast
65 }
66}
67
68def EVAL(ast MalVal, env Env) MalVal {
69 while true {
70 if !(ast is MalList) { return eval_ast(ast, env) }
71 ast = macroexpand(ast, env)
72 if !(ast is MalList) { return eval_ast(ast, env) }
73 const astList = ast as MalList
74 if astList.isEmpty { return ast }
75 const a0sym = astList[0] as MalSymbol
76 if a0sym.val == "def!" {
77 return env.set(astList[1] as MalSymbol, EVAL(astList[2], env))
78 } else if a0sym.val == "let*" {
79 var letenv = Env.new(env)
80 const assigns = astList[1] as MalSequential
81 for i = 0; i < assigns.count; i += 2 {
82 letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv))
83 }
84 ast = astList[2]
85 env = letenv
86 continue # TCO
87 } else if a0sym.val == "quote" {
88 return astList[1]
89 } else if a0sym.val == "quasiquote" {
90 ast = quasiquote(astList[1])
91 continue # TCO
92 } else if a0sym.val == "defmacro!" {
93 var macro = EVAL(astList[2], env) as MalFunc
94 macro.setAsMacro
95 return env.set(astList[1] as MalSymbol, macro)
96 } else if a0sym.val == "macroexpand" {
97 return macroexpand(astList[1], env)
98 } else if a0sym.val == "try*" {
6d420175
DM
99 if astList.count < 3 {
100 return EVAL(astList[1], env)
101 }
034e82ad
DM
102 var exc MalVal
103 try {
104 return EVAL(astList[1], env)
105 }
106 catch e MalUserError { exc = e.data }
107 catch e MalError { exc = MalString.new(e.message) }
108 catch e Error { exc = MalString.new(e.message) }
109 const catchClause = astList[2] as MalList
110 var catchEnv = Env.new(env, [catchClause[1] as MalSymbol], [exc])
111 return EVAL(catchClause[2], catchEnv)
112 } else if a0sym.val == "do" {
113 const parts = astList.val.slice(1)
114 eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env)
115 ast = parts[parts.count - 1]
116 continue # TCO
117 } else if a0sym.val == "if" {
118 const condRes = EVAL(astList[1], env)
119 if condRes is MalNil || condRes is MalFalse {
120 ast = astList.count > 3 ? astList[3] : gNil
121 } else {
122 ast = astList[2]
123 }
124 continue # TCO
125 } else if a0sym.val == "fn*" {
126 const argsNames = astList[1] as MalSequential
127 return MalFunc.new(astList[2], argsNames, env, (args List<MalVal>) => EVAL(astList[2], Env.new(env, argsNames.val, args)))
128 } else {
129 const evaledList = eval_ast(ast, env) as MalList
130 const fn = evaledList[0]
131 const callArgs = evaledList.val.slice(1)
132 if fn is MalNativeFunc {
133 return (fn as MalNativeFunc).call(callArgs)
134 } else if fn is MalFunc {
135 const f = fn as MalFunc
136 ast = f.ast
137 env = Env.new(f.env, f.params.val, callArgs)
138 continue # TCO
139 } else {
140 throw MalError.new("Expected function as head of list")
141 }
142 }
143 }
144}
145
146def PRINT(exp MalVal) string {
147 return exp?.print(true)
148}
149
150var repl_env = Env.new(null)
151
152def RE(str string) MalVal {
153 return EVAL(READ(str), repl_env)
154}
155
156def REP(str string) string {
157 return PRINT(RE(str))
158}
159
160@entry
161def main {
162 # core.sk: defined using Skew
163 ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func)))
164 repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map<MalVal>(e => MalString.new(e))))
165
166 # core.mal: defined using the language itself
167 RE("(def! not (fn* (a) (if a false true)))")
e6d41de4 168 RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))")
034e82ad 169 RE("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
034e82ad
DM
170
171 if argv.count > 0 {
172 RE("(load-file \"" + argv[0] + "\")")
173 return
174 }
175
176 var line string
177 while (line = readLine("user> ")) != null {
178 if line == "" { continue }
179 try {
180 printLn(REP(line))
181 }
182 catch e MalUserError {
183 printLn("Error: \(e.data.print(false))")
184 }
185 catch e MalError {
186 printLn("Error: \(e.message)")
187 }
188 catch e Error {
189 printLn("Error: \(e.message)")
190 }
191 }
192}