Move implementations into impls/ dir
[jackhill/mal.git] / impls / haxe / StepA_mal.hx
1 import Compat;
2 import types.Types.MalType;
3 import types.Types.*;
4 import types.MalException;
5 import reader.*;
6 import printer.*;
7 import env.*;
8 import core.*;
9 import haxe.rtti.Meta;
10
11 class StepA_mal {
12 // READ
13 static function READ(str:String):MalType {
14 return Reader.read_str(str);
15 }
16
17 // EVAL
18 static function is_pair(ast:MalType) {
19 return switch (ast) {
20 case MalList(l) | MalVector(l): l.length > 0;
21 case _: false;
22 }
23 }
24
25 static function quasiquote(ast:MalType) {
26 if (!is_pair(ast)) {
27 return MalList([MalSymbol("quote"), ast]);
28 } else {
29 var a0 = first(ast);
30 if (_equal_Q(a0, MalSymbol("unquote"))) {
31 return _nth(ast, 1);
32 } else if (is_pair(a0)) {
33 var a00 = first(a0);
34 if (_equal_Q(a00, MalSymbol("splice-unquote"))) {
35 return MalList([MalSymbol("concat"),
36 _nth(a0, 1),
37 quasiquote(rest(ast))]);
38 }
39 }
40 return MalList([MalSymbol("cons"),
41 quasiquote(a0),
42 quasiquote(rest(ast))]);
43 }
44 }
45
46 static function is_macro(ast:MalType, env:Env) {
47 return switch(ast) {
48 case MalList([]): false;
49 case MalList(a):
50 var a0 = a[0];
51 return symbol_Q(a0) &&
52 env.find(a0) != null &&
53 _macro_Q(env.get(a0));
54 case _: false;
55 }
56 }
57
58 static function macroexpand(ast:MalType, env:Env) {
59 while (is_macro(ast, env)) {
60 var mac = env.get(first(ast));
61 switch (mac) {
62 case MalFunc(f,_,_,_,_,_):
63 ast = f(_list(ast).slice(1));
64 case _: break;
65 }
66 }
67 return ast;
68 }
69
70 static function eval_ast(ast:MalType, env:Env) {
71 return switch (ast) {
72 case MalSymbol(s): env.get(ast);
73 case MalList(l):
74 MalList(l.map(function(x) { return EVAL(x, env); }));
75 case MalVector(l):
76 MalVector(l.map(function(x) { return EVAL(x, env); }));
77 case MalHashMap(m):
78 var new_map = new Map<String,MalType>();
79 for (k in m.keys()) {
80 new_map[k] = EVAL(m[k], env);
81 }
82 MalHashMap(new_map);
83 case _: ast;
84 }
85 }
86
87 static function EVAL(ast:MalType, env:Env):MalType {
88 while (true) {
89 if (!list_Q(ast)) { return eval_ast(ast, env); }
90
91 // apply
92 ast = macroexpand(ast, env);
93 if (!list_Q(ast)) { return eval_ast(ast, env); }
94
95 var alst = _list(ast);
96 if (alst.length == 0) { return ast; }
97 switch (alst[0]) {
98 case MalSymbol("def!"):
99 return env.set(alst[1], EVAL(alst[2], env));
100 case MalSymbol("let*"):
101 var let_env = new Env(env);
102 switch (alst[1]) {
103 case MalList(l) | MalVector(l):
104 for (i in 0...l.length) {
105 if ((i%2) > 0) { continue; }
106 let_env.set(l[i], EVAL(l[i+1], let_env));
107 }
108 case _: throw "Invalid let*";
109 }
110 ast = alst[2];
111 env = let_env;
112 continue; // TCO
113 case MalSymbol("quote"):
114 return alst[1];
115 case MalSymbol("quasiquote"):
116 ast = quasiquote(alst[1]);
117 continue; // TCO
118 case MalSymbol("defmacro!"):
119 var func = EVAL(alst[2], env);
120 return switch (func) {
121 case MalFunc(f,ast,e,params,_,_):
122 env.set(alst[1], MalFunc(f,ast,e,params,true,nil));
123 case _:
124 throw "Invalid defmacro! call";
125 }
126 case MalSymbol("macroexpand"):
127 return macroexpand(alst[1], env);
128 case MalSymbol("try*"):
129 try {
130 return EVAL(alst[1], env);
131 } catch (err:Dynamic) {
132 if (alst.length > 2) {
133 switch (alst[2]) {
134 case MalList([MalSymbol("catch*"), a21, a22]):
135 var exc;
136 if (Type.getClass(err) == MalException) {
137 exc = err.obj;
138 } else {
139 exc = MalString(Std.string(err));
140 };
141 return EVAL(a22, new Env(env, [a21], [exc]));
142 case _:
143 throw err;
144 }
145 } else {
146 throw err;
147 }
148 }
149 case MalSymbol("do"):
150 var el = eval_ast(MalList(alst.slice(1, alst.length-1)), env);
151 ast = last(ast);
152 continue; // TCO
153 case MalSymbol("if"):
154 var cond = EVAL(alst[1], env);
155 if (cond != MalFalse && cond != MalNil) {
156 ast = alst[2];
157 } else if (alst.length > 3) {
158 ast = alst[3];
159 } else {
160 return MalNil;
161 }
162 continue; // TCO
163 case MalSymbol("fn*"):
164 return MalFunc(function (args) {
165 return EVAL(alst[2], new Env(env, _list(alst[1]), args));
166 },alst[2],env,alst[1],false,nil);
167 case _:
168 var el = eval_ast(ast, env);
169 var lst = _list(el);
170 switch (first(el)) {
171 case MalFunc(f,a,e,params,_,_):
172 var args = _list(el).slice(1);
173 if (a != null) {
174 ast = a;
175 env = new Env(e, _list(params), args);
176 continue; // TCO
177 } else {
178 return f(args);
179 }
180 case _: throw "Call of non-function";
181 }
182 }
183 }
184 }
185
186 // PRINT
187 static function PRINT(exp:MalType):String {
188 return Printer.pr_str(exp, true);
189 }
190
191 // repl
192 static var repl_env = new Env(null);
193
194 static function rep(line:String):String {
195 return PRINT(EVAL(READ(line), repl_env));
196 }
197
198 public static function main() {
199 // core.EXT: defined using Haxe
200 for (k in Core.ns.keys()) {
201 repl_env.set(MalSymbol(k), MalFunc(Core.ns[k],null,null,null,false,nil));
202 }
203
204 var evalfn = MalFunc(function(args) {
205 return EVAL(args[0], repl_env);
206 },null,null,null,false,nil);
207 repl_env.set(MalSymbol("eval"), evalfn);
208
209 var cmdargs = Compat.cmdline_args();
210 var argarray = cmdargs.map(function(a) { return MalString(a); });
211 repl_env.set(MalSymbol("*ARGV*"), MalList(argarray.slice(1)));
212
213 // core.mal: defined using the language itself
214 rep("(def! *host-language* \"haxe\")");
215 rep("(def! not (fn* (a) (if a false true)))");
216 rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))");
217 rep("(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)))))))");
218
219
220 if (cmdargs.length > 0) {
221 rep('(load-file "${cmdargs[0]}")');
222 Compat.exit(0);
223 }
224
225 rep("(println (str \"Mal [\" *host-language* \"]\"))");
226 while (true) {
227 try {
228 var line = Compat.readline("user> ");
229 if (line == "") { continue; }
230 Compat.println(rep(line));
231 } catch (exc:BlankLine) {
232 continue;
233 } catch (exc:haxe.io.Eof) {
234 Compat.exit(0);
235 } catch (exc:Dynamic) {
236 if (Type.getClass(exc) == MalException) {
237 Compat.println("Error: " + Printer.pr_str(exc.obj, true));
238 } else {
239 Compat.println("Error: " + exc);
240 };
241 }
242 }
243 }
244 }