| 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 | |
| 10 | class Step8_macros { |
| 11 | // READ |
| 12 | static function READ(str:String):MalType { |
| 13 | return Reader.read_str(str); |
| 14 | } |
| 15 | |
| 16 | // EVAL |
| 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]); |
| 23 | } |
| 24 | } |
| 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 | } |
| 32 | static function quasiquote(ast:MalType) { |
| 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; |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | static function is_macro(ast:MalType, env:Env) { |
| 43 | return switch(ast) { |
| 44 | case MalList([]): false; |
| 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 eval_ast(ast, env); } |
| 90 | |
| 91 | var alst = _list(ast); |
| 92 | if (alst.length == 0) { return ast; } |
| 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]; |
| 111 | case MalSymbol("quasiquoteexpand"): |
| 112 | return quasiquote(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() { |
| 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 | |
| 186 | var cmdargs = Compat.cmdline_args(); |
| 187 | var argarray = cmdargs.map(function(a) { return MalString(a); }); |
| 188 | repl_env.set(MalSymbol("*ARGV*"), MalList(argarray.slice(1))); |
| 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) \"\nnil)\")))))"); |
| 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 | |
| 195 | |
| 196 | if (cmdargs.length > 0) { |
| 197 | rep('(load-file "${cmdargs[0]}")'); |
| 198 | Compat.exit(0); |
| 199 | } |
| 200 | |
| 201 | while (true) { |
| 202 | try { |
| 203 | var line = Compat.readline("user> "); |
| 204 | if (line == "") { continue; } |
| 205 | Compat.println(rep(line)); |
| 206 | } catch (exc:BlankLine) { |
| 207 | continue; |
| 208 | } catch (exc:haxe.io.Eof) { |
| 209 | Compat.exit(0); |
| 210 | } catch (exc:Dynamic) { |
| 211 | if (Type.getClass(exc) == MalException) { |
| 212 | Compat.println("Error: " + Printer.pr_str(exc.obj, true)); |
| 213 | } else { |
| 214 | Compat.println("Error: " + exc); |
| 215 | }; |
| 216 | } |
| 217 | } |
| 218 | } |
| 219 | } |