runtest: support carriage returns in tests.
[jackhill/mal.git] / haxe / Step8_macros.hx
CommitLineData
32d0a1cf 1import Compat;
1d166495
JM
2import types.Types.MalType;
3import types.Types.*;
4import reader.*;
5import printer.*;
6import env.*;
7import core.*;
8
9class Step8_macros {
10 // READ
11 static function READ(str:String):MalType {
12 return Reader.read_str(str);
13 }
14
15 // EVAL
16 static function is_pair(ast:MalType) {
17 return switch (ast) {
18 case MalList(l) | MalVector(l): l.length > 0;
19 case _: false;
20 }
21 }
22
23 static function quasiquote(ast:MalType) {
24 if (!is_pair(ast)) {
25 return MalList([MalSymbol("quote"), ast]);
26 } else {
27 var a0 = first(ast);
28 if (_equal_Q(a0, MalSymbol("unquote"))) {
29 return _nth(ast, 1);
30 } else if (is_pair(a0)) {
31 var a00 = first(a0);
32 if (_equal_Q(a00, MalSymbol("splice-unquote"))) {
33 return MalList([MalSymbol("concat"),
34 _nth(a0, 1),
35 quasiquote(rest(ast))]);
36 }
37 }
38 return MalList([MalSymbol("cons"),
39 quasiquote(a0),
40 quasiquote(rest(ast))]);
41 }
42 }
43
44 static function is_macro(ast:MalType, env:Env) {
45 return switch(ast) {
d3ec2994 46 case MalList([]): false;
1d166495
JM
47 case MalList(a):
48 var a0 = a[0];
49 return symbol_Q(a0) &&
50 env.find(a0) != null &&
51 _macro_Q(env.get(a0));
52 case _: false;
53 }
54 }
55
56 static function macroexpand(ast:MalType, env:Env) {
57 while (is_macro(ast, env)) {
58 var mac = env.get(first(ast));
59 switch (mac) {
60 case MalFunc(f,_,_,_,_,_):
61 ast = f(_list(ast).slice(1));
62 case _: break;
63 }
64 }
65 return ast;
66 }
67
68 static function eval_ast(ast:MalType, env:Env) {
69 return switch (ast) {
70 case MalSymbol(s): env.get(ast);
71 case MalList(l):
72 MalList(l.map(function(x) { return EVAL(x, env); }));
73 case MalVector(l):
74 MalVector(l.map(function(x) { return EVAL(x, env); }));
75 case MalHashMap(m):
76 var new_map = new Map<String,MalType>();
77 for (k in m.keys()) {
78 new_map[k] = EVAL(m[k], env);
79 }
80 MalHashMap(new_map);
81 case _: ast;
82 }
83 }
84
85 static function EVAL(ast:MalType, env:Env):MalType {
86 while (true) {
87 if (!list_Q(ast)) { return eval_ast(ast, env); }
88
89 // apply
90 ast = macroexpand(ast, env);
33dec7af 91 if (!list_Q(ast)) { return eval_ast(ast, env); }
1d166495
JM
92
93 var alst = _list(ast);
d3ec2994 94 if (alst.length == 0) { return ast; }
1d166495
JM
95 switch (alst[0]) {
96 case MalSymbol("def!"):
97 return env.set(alst[1], EVAL(alst[2], env));
98 case MalSymbol("let*"):
99 var let_env = new Env(env);
100 switch (alst[1]) {
101 case MalList(l) | MalVector(l):
102 for (i in 0...l.length) {
103 if ((i%2) > 0) { continue; }
104 let_env.set(l[i], EVAL(l[i+1], let_env));
105 }
106 case _: throw "Invalid let*";
107 }
108 ast = alst[2];
109 env = let_env;
110 continue; // TCO
111 case MalSymbol("quote"):
112 return alst[1];
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)))");
192 rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
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)))))))");
194 rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))");
195
196
197 if (cmdargs.length > 0) {
32d0a1cf
JM
198 rep('(load-file "${cmdargs[0]}")');
199 Compat.exit(0);
1d166495
JM
200 }
201
202 while (true) {
203 try {
32d0a1cf 204 var line = Compat.readline("user> ");
1d166495 205 if (line == "") { continue; }
32d0a1cf 206 Compat.println(rep(line));
1d166495
JM
207 } catch (exc:BlankLine) {
208 continue;
209 } catch (exc:haxe.io.Eof) {
32d0a1cf 210 Compat.exit(0);
1d166495 211 } catch (exc:Dynamic) {
32d0a1cf 212 Compat.println(exc);
1d166495
JM
213 }
214 }
215 }
216}