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