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