Haxe: step7-A, hash-maps, metadata, self-hosting.
[jackhill/mal.git] / haxe / Step8_macros.hx
CommitLineData
1d166495
JM
1import types.Types.MalType;
2import types.Types.*;
3import reader.*;
4import printer.*;
5import env.*;
6import core.*;
7
8class Step8_macros {
9 // READ
10 static function READ(str:String):MalType {
11 return Reader.read_str(str);
12 }
13
14 // EVAL
15 static function is_pair(ast:MalType) {
16 return switch (ast) {
17 case MalList(l) | MalVector(l): l.length > 0;
18 case _: false;
19 }
20 }
21
22 static function quasiquote(ast:MalType) {
23 if (!is_pair(ast)) {
24 return MalList([MalSymbol("quote"), ast]);
25 } else {
26 var a0 = first(ast);
27 if (_equal_Q(a0, MalSymbol("unquote"))) {
28 return _nth(ast, 1);
29 } else if (is_pair(a0)) {
30 var a00 = first(a0);
31 if (_equal_Q(a00, MalSymbol("splice-unquote"))) {
32 return MalList([MalSymbol("concat"),
33 _nth(a0, 1),
34 quasiquote(rest(ast))]);
35 }
36 }
37 return MalList([MalSymbol("cons"),
38 quasiquote(a0),
39 quasiquote(rest(ast))]);
40 }
41 }
42
43 static function is_macro(ast:MalType, env:Env) {
44 return switch(ast) {
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);
89 if (!list_Q(ast)) { return ast; }
90
91 var alst = _list(ast);
92 switch (alst[0]) {
93 case MalSymbol("def!"):
94 return env.set(alst[1], EVAL(alst[2], env));
95 case MalSymbol("let*"):
96 var let_env = new Env(env);
97 switch (alst[1]) {
98 case MalList(l) | MalVector(l):
99 for (i in 0...l.length) {
100 if ((i%2) > 0) { continue; }
101 let_env.set(l[i], EVAL(l[i+1], let_env));
102 }
103 case _: throw "Invalid let*";
104 }
105 ast = alst[2];
106 env = let_env;
107 continue; // TCO
108 case MalSymbol("quote"):
109 return alst[1];
110 case MalSymbol("quasiquote"):
111 ast = quasiquote(alst[1]);
112 continue; // TCO
113 case MalSymbol("defmacro!"):
114 var func = EVAL(alst[2], env);
115 return switch (func) {
116 case MalFunc(f,ast,e,params,_,_):
117 env.set(alst[1], MalFunc(f,ast,e,params,true,nil));
118 case _:
119 throw "Invalid defmacro! call";
120 }
121 case MalSymbol("macroexpand"):
122 return macroexpand(alst[1], env);
123 case MalSymbol("do"):
124 var el = eval_ast(MalList(alst.slice(1, alst.length-1)), env);
125 ast = last(ast);
126 continue; // TCO
127 case MalSymbol("if"):
128 var cond = EVAL(alst[1], env);
129 if (cond != MalFalse && cond != MalNil) {
130 ast = alst[2];
131 } else if (alst.length > 3) {
132 ast = alst[3];
133 } else {
134 return MalNil;
135 }
136 continue; // TCO
137 case MalSymbol("fn*"):
138 return MalFunc(function (args) {
139 return EVAL(alst[2], new Env(env, _list(alst[1]), args));
140 },alst[2],env,alst[1],false,nil);
141 case _:
142 var el = eval_ast(ast, env);
143 var lst = _list(el);
144 switch (first(el)) {
145 case MalFunc(f,a,e,params,_,_):
146 var args = _list(el).slice(1);
147 if (a != null) {
148 ast = a;
149 env = new Env(e, _list(params), args);
150 continue; // TCO
151 } else {
152 return f(args);
153 }
154 case _: throw "Call of non-function";
155 }
156 }
157 }
158 }
159
160 // PRINT
161 static function PRINT(exp:MalType):String {
162 return Printer.pr_str(exp, true);
163 }
164
165 // repl
166 static var repl_env = new Env(null);
167
168 static function rep(line:String):String {
169 return PRINT(EVAL(READ(line), repl_env));
170 }
171
172 public static function main() {
173 #if js
174 #error "JS not supported yet"
175 #end
176
177 // core.EXT: defined using Haxe
178 for (k in Core.ns.keys()) {
179 repl_env.set(MalSymbol(k), MalFunc(Core.ns[k],null,null,null,false,nil));
180 }
181
182 var evalfn = MalFunc(function(args) {
183 return EVAL(args[0], repl_env);
184 },null,null,null,false,nil);
185 repl_env.set(MalSymbol("eval"), evalfn);
186
187 var cmdargs = Sys.args().map(function(a) { return MalString(a); });
188 repl_env.set(MalSymbol("*ARGV*"), MalList(cmdargs));
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) {
198 rep('(load-file "${Sys.args()[0]}")');
199 Sys.exit(0);
200 }
201
202 while (true) {
203 try {
204 Sys.print("user> ");
205 var line = Sys.stdin().readLine();
206 if (line == "") { continue; }
207 Sys.println(rep(line));
208 } catch (exc:BlankLine) {
209 continue;
210 } catch (exc:haxe.io.Eof) {
211 Sys.exit(0);
212 } catch (exc:Dynamic) {
213 Sys.println(exc);
214 }
215 }
216 }
217}