Commit | Line | Data |
---|---|---|
034e82ad DM |
1 | def READ(str string) MalVal { |
2 | return read_str(str) | |
3 | } | |
4 | ||
fbfe6784 NB |
5 | def starts_with(lst MalList, sym string) bool { |
6 | return lst.count == 2 && lst[0].isSymbol(sym) | |
034e82ad | 7 | } |
fbfe6784 NB |
8 | def 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 | } |
15 | def 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 | } | |
22 | def 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 | ||
36 | def 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 | ||
49 | def 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 | ||
58 | def 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 | ||
77 | def 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 | ||
157 | def PRINT(exp MalVal) string { | |
158 | return exp?.print(true) | |
159 | } | |
160 | ||
161 | var repl_env = Env.new(null) | |
162 | ||
163 | def RE(str string) MalVal { | |
164 | return EVAL(READ(str), repl_env) | |
165 | } | |
166 | ||
167 | def REP(str string) string { | |
168 | return PRINT(RE(str)) | |
169 | } | |
170 | ||
171 | @entry | |
172 | def 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 | } |