Haxe: step7-A, hash-maps, metadata, self-hosting.
authorJoel Martin <github@martintribe.org>
Tue, 26 Jan 2016 04:51:58 +0000 (22:51 -0600)
committerJoel Martin <github@martintribe.org>
Tue, 26 Jan 2016 04:51:58 +0000 (22:51 -0600)
15 files changed:
haxe/Makefile
haxe/Step2_eval.hx
haxe/Step3_env.hx
haxe/Step4_if_fn_do.hx
haxe/Step5_tco.hx
haxe/Step6_file.hx
haxe/Step7_quote.hx [new file with mode: 0644]
haxe/Step8_macros.hx [new file with mode: 0644]
haxe/Step9_try.hx [new file with mode: 0644]
haxe/StepA_mal.hx [new file with mode: 0644]
haxe/core/Core.hx
haxe/printer/Printer.hx
haxe/reader/Reader.hx
haxe/types/MalException.hx [new file with mode: 0644]
haxe/types/Types.hx

dissimilarity index 83%
index e1893ac..d9818a0 100644 (file)
@@ -1,13 +1,21 @@
-# Python step rules
-s%.py: S%.hx
-       haxe -main $(patsubst %.hx,%,$<) -python $@
-
-step1_read_print.py: types/Types.hx reader/Reader.hx printer/Printer.hx
-step2_eval.py: types/Types.hx reader/Reader.hx printer/Printer.hx
-step3_env.py: types/Types.hx reader/Reader.hx printer/Printer.hx env/Env.hx
-step4_if_fn_do.py: types/Types.hx reader/Reader.hx printer/Printer.hx env/Env.hx core/Core.hx
-step5_tco.py: types/Types.hx reader/Reader.hx printer/Printer.hx env/Env.hx core/Core.hx
-step6_file.py: types/Types.hx reader/Reader.hx printer/Printer.hx env/Env.hx core/Core.hx
-
-clean:
-       rm -r *.py
+# Python step rules
+s%.py: S%.hx
+       haxe -main $(patsubst %.hx,%,$<) -python $@
+
+STEP1_DEPS = types/Types.hx reader/Reader.hx printer/Printer.hx
+STEP3_DEPS = $(STEP1_DEPS) env/Env.hx
+STEP4_DEPS = $(STEP3_DEPS) core/Core.hx
+
+step1_read_print.py: $(STEP1_DEPS)
+step2_eval.py: $(STEP1_DEPS)
+step3_env.py: $(STEP3_DEPS)
+step4_if_fn_do.py: $(STEP4_DEPS)
+step5_tco.py: $(STEP4_DEPS)
+step6_file.py: $(STEP4_DEPS)
+step7_quote.py: $(STEP4_DEPS)
+step8_macros.py: $(STEP4_DEPS)
+step9_try.py: $(STEP4_DEPS)
+stepA_mal.py: $(STEP4_DEPS)
+
+clean:
+       rm -r *.py
index 4066e8b..e8edea0 100644 (file)
@@ -17,6 +17,12 @@ class Step2_eval {
                 MalList(l.map(function(x) { return EVAL(x, env); }));
             case MalVector(l):
                 MalVector(l.map(function(x) { return EVAL(x, env); }));
+            case MalHashMap(m):
+                var new_map = new Map<String,MalType>();
+                for (k in m.keys()) {
+                    new_map[k] = EVAL(m[k], env);
+                }
+                MalHashMap(new_map);
             case _: ast;
         }
     }
@@ -29,7 +35,7 @@ class Step2_eval {
         var lst = switch (el) { case MalList(lst): lst; case _: []; }
         var a0 = lst[0], args = lst.slice(1);
         switch (a0) {
-            case MalFunc(f,_,_,_): return f(args);
+            case MalFunc(f,_,_,_,_,_): return f(args);
             case _: throw "Call of non-function";
         }
     }
@@ -47,7 +53,7 @@ class Step2_eval {
                 case _: throw "Invalid numeric op call"; 
             }
             
-        });
+        },null,null,null,false,nil);
     }
     static var repl_env:Map<String,MalType> = 
         ["+" => NumOp(function(a,b) {return a+b;}),
index ba07d53..1976c4b 100644 (file)
@@ -18,6 +18,12 @@ class Step3_env {
                 MalList(l.map(function(x) { return EVAL(x, env); }));
             case MalVector(l):
                 MalVector(l.map(function(x) { return EVAL(x, env); }));
+            case MalHashMap(m):
+                var new_map = new Map<String,MalType>();
+                for (k in m.keys()) {
+                    new_map[k] = EVAL(m[k], env);
+                }
+                MalHashMap(new_map);
             case _: ast;
         }
     }
@@ -44,9 +50,9 @@ class Step3_env {
             return EVAL(alst[2], let_env);
         case _:
             var el = eval_ast(ast, env);
-            var lst = switch (el) { case MalList(lst): lst; case _: []; }
-            switch (lst[0]) {
-                case MalFunc(f,_,_,_): return f(lst.slice(1));
+            var lst = _list(el);
+            switch (first(el)) {
+                case MalFunc(f,_,_,_,_,_): return f(_list(el).slice(1));
                 case _: throw "Call of non-function";
             }
         }
@@ -65,7 +71,7 @@ class Step3_env {
                 case _: throw "Invalid numeric op call"; 
             }
             
-        },null,null,null);
+        },null,null,null,false,nil);
     }
     static var repl_env = new Env(null);
 
index b4ea7f9..6ceb7c8 100644 (file)
@@ -19,6 +19,12 @@ class Step4_if_fn_do {
                 MalList(l.map(function(x) { return EVAL(x, env); }));
             case MalVector(l):
                 MalVector(l.map(function(x) { return EVAL(x, env); }));
+            case MalHashMap(m):
+                var new_map = new Map<String,MalType>();
+                for (k in m.keys()) {
+                    new_map[k] = EVAL(m[k], env);
+                }
+                MalHashMap(new_map);
             case _: ast;
         }
     }
@@ -57,12 +63,12 @@ class Step4_if_fn_do {
         case MalSymbol("fn*"):
             return MalFunc(function (args) {
                 return EVAL(alst[2], new Env(env, _list(alst[1]), args));
-            },null,null,null);
+            },null,null,null,false,nil);
         case _:
             var el = eval_ast(ast, env);
             var lst = _list(el);
             switch (first(el)) {
-                case MalFunc(f,_,_,_): return f(_list(el).slice(1));
+                case MalFunc(f,_,_,_,_,_): return f(_list(el).slice(1));
                 case _: throw "Call of non-function";
             }
         }
@@ -87,7 +93,7 @@ class Step4_if_fn_do {
 
         // core.EXT: defined using Haxe
         for (k in Core.ns.keys()) {
-            repl_env.set(MalSymbol(k), MalFunc(Core.ns[k],null,null,null));
+            repl_env.set(MalSymbol(k), MalFunc(Core.ns[k],null,null,null,false,nil));
         }
 
         // core.mal: defined using the language itself
index 1ea1227..4d005a5 100644 (file)
@@ -19,6 +19,12 @@ class Step5_tco {
                 MalList(l.map(function(x) { return EVAL(x, env); }));
             case MalVector(l):
                 MalVector(l.map(function(x) { return EVAL(x, env); }));
+            case MalHashMap(m):
+                var new_map = new Map<String,MalType>();
+                for (k in m.keys()) {
+                    new_map[k] = EVAL(m[k], env);
+                }
+                MalHashMap(new_map);
             case _: ast;
         }
     }
@@ -44,10 +50,11 @@ class Step5_tco {
                 case _: throw "Invalid let*";
             }
             ast = alst[2];
+            env = let_env;
             continue; // TCO
         case MalSymbol("do"):
-            var el = eval_ast(MalList(alst.slice(1, alst.length-2)), env);
-            ast = last(el);
+            var el = eval_ast(MalList(alst.slice(1, alst.length-1)), env);
+            ast = last(ast);
             continue; // TCO
         case MalSymbol("if"):
             var cond = EVAL(alst[1], env);
@@ -60,12 +67,14 @@ class Step5_tco {
             }
             continue; // TCO
         case MalSymbol("fn*"):
-            return MalFunc(null, alst[2], env, alst[1]);
+            return MalFunc(function (args) {
+                return EVAL(alst[2], new Env(env, _list(alst[1]), args));
+            },alst[2],env,alst[1],false,nil);
         case _:
             var el = eval_ast(ast, env);
             var lst = _list(el);
             switch (first(el)) {
-                case MalFunc(f,a,e,params):
+                case MalFunc(f,a,e,params,_,_):
                     var args = _list(el).slice(1);
                     if (a != null) {
                         ast = a;
@@ -99,7 +108,7 @@ class Step5_tco {
 
         // core.EXT: defined using Haxe
         for (k in Core.ns.keys()) {
-            repl_env.set(MalSymbol(k), MalFunc(Core.ns[k],null,null,null));
+            repl_env.set(MalSymbol(k), MalFunc(Core.ns[k],null,null,null,false,nil));
         }
 
         // core.mal: defined using the language itself
index c93f6bb..d52c25f 100644 (file)
@@ -19,6 +19,12 @@ class Step6_file {
                 MalList(l.map(function(x) { return EVAL(x, env); }));
             case MalVector(l):
                 MalVector(l.map(function(x) { return EVAL(x, env); }));
+            case MalHashMap(m):
+                var new_map = new Map<String,MalType>();
+                for (k in m.keys()) {
+                    new_map[k] = EVAL(m[k], env);
+                }
+                MalHashMap(new_map);
             case _: ast;
         }
     }
@@ -44,6 +50,7 @@ class Step6_file {
                 case _: throw "Invalid let*";
             }
             ast = alst[2];
+            env = let_env;
             continue; // TCO
         case MalSymbol("do"):
             var el = eval_ast(MalList(alst.slice(1, alst.length-1)), env);
@@ -60,12 +67,14 @@ class Step6_file {
             }
             continue; // TCO
         case MalSymbol("fn*"):
-            return MalFunc(null, alst[2], env, alst[1]);
+            return MalFunc(function (args) {
+                return EVAL(alst[2], new Env(env, _list(alst[1]), args));
+            },alst[2],env,alst[1],false,nil);
         case _:
             var el = eval_ast(ast, env);
             var lst = _list(el);
             switch (first(el)) {
-                case MalFunc(f,a,e,params):
+                case MalFunc(f,a,e,params,_,_):
                     var args = _list(el).slice(1);
                     if (a != null) {
                         ast = a;
@@ -99,12 +108,12 @@ class Step6_file {
 
         // core.EXT: defined using Haxe
         for (k in Core.ns.keys()) {
-            repl_env.set(MalSymbol(k), MalFunc(Core.ns[k],null,null,null));
+            repl_env.set(MalSymbol(k), MalFunc(Core.ns[k],null,null,null,false,nil));
         }
 
         var evalfn = MalFunc(function(args) {
             return EVAL(args[0], repl_env);
-        },null,null,null);
+        },null,null,null,false,nil);
         repl_env.set(MalSymbol("eval"), evalfn);
 
         var cmdargs = Sys.args().map(function(a) { return MalString(a); });
@@ -115,7 +124,6 @@ class Step6_file {
         rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
 
         if (cmdargs.length > 0) {
-            trace(Sys.args()[0]);
             rep('(load-file "${Sys.args()[0]}")');
             Sys.exit(0);
         }
diff --git a/haxe/Step7_quote.hx b/haxe/Step7_quote.hx
new file mode 100644 (file)
index 0000000..b78b675
--- /dev/null
@@ -0,0 +1,179 @@
+import types.Types.MalType;
+import types.Types.*;
+import reader.*;
+import printer.*;
+import env.*;
+import core.*;
+
+class Step7_quote {
+    // READ
+    static function READ(str:String):MalType {
+        return Reader.read_str(str);
+    }
+
+    // EVAL
+    static function is_pair(ast:MalType) {
+        return switch (ast) {
+            case MalList(l) | MalVector(l): l.length > 0;
+            case _: false;
+        }
+    }
+
+    static function quasiquote(ast:MalType) {
+        if (!is_pair(ast)) {
+            return MalList([MalSymbol("quote"), ast]);
+        } else {
+            var a0 = first(ast);
+            if (_equal_Q(a0, MalSymbol("unquote"))) {
+                    return _nth(ast, 1);
+            } else if (is_pair(a0)) {
+                var a00 = first(a0);
+                if (_equal_Q(a00, MalSymbol("splice-unquote"))) {
+                    return MalList([MalSymbol("concat"),
+                                    _nth(a0, 1),
+                                    quasiquote(rest(ast))]);
+                }
+            }
+            return MalList([MalSymbol("cons"),
+                            quasiquote(a0),
+                            quasiquote(rest(ast))]);
+        }
+    }
+
+    static function eval_ast(ast:MalType, env:Env) {
+        return switch (ast) {
+            case MalSymbol(s): env.get(ast);
+            case MalList(l):
+                MalList(l.map(function(x) { return EVAL(x, env); }));
+            case MalVector(l):
+                MalVector(l.map(function(x) { return EVAL(x, env); }));
+            case MalHashMap(m):
+                var new_map = new Map<String,MalType>();
+                for (k in m.keys()) {
+                    new_map[k] = EVAL(m[k], env);
+                }
+                MalHashMap(new_map);
+            case _: ast;
+        }
+    }
+
+    static function EVAL(ast:MalType, env:Env):MalType {
+      while (true) {
+        if (!list_Q(ast)) { return eval_ast(ast, env); }
+
+        // apply
+        var alst = _list(ast);
+
+        switch (alst[0]) {
+        case MalSymbol("def!"):
+            return env.set(alst[1], EVAL(alst[2], env));
+        case MalSymbol("let*"):
+            var let_env = new Env(env);
+            switch (alst[1]) {
+                case MalList(l) | MalVector(l):
+                    for (i in 0...l.length) {
+                        if ((i%2) > 0) { continue; }
+                        let_env.set(l[i], EVAL(l[i+1], let_env));
+                    }
+                case _: throw "Invalid let*";
+            }
+            ast = alst[2];
+            env = let_env;
+            continue; // TCO
+        case MalSymbol("quote"):
+            return alst[1];
+        case MalSymbol("quasiquote"):
+            ast = quasiquote(alst[1]);
+            continue; // TCO
+        case MalSymbol("do"):
+            var el = eval_ast(MalList(alst.slice(1, alst.length-1)), env);
+            ast = last(ast);
+            continue; // TCO
+        case MalSymbol("if"):
+            var cond = EVAL(alst[1], env);
+            if (cond != MalFalse && cond != MalNil) {
+                ast = alst[2];
+            } else if (alst.length > 3) {
+                ast = alst[3];
+            } else {
+                return MalNil;
+            }
+            continue; // TCO
+        case MalSymbol("fn*"):
+            return MalFunc(function (args) {
+                return EVAL(alst[2], new Env(env, _list(alst[1]), args));
+            },alst[2],env,alst[1],false,nil);
+        case _:
+            var el = eval_ast(ast, env);
+            var lst = _list(el);
+            switch (first(el)) {
+                case MalFunc(f,a,e,params,_,_):
+                    var args = _list(el).slice(1);
+                    if (a != null) {
+                        ast = a;
+                        env = new Env(e, _list(params), args);
+                        continue; // TCO
+                    } else {
+                        return f(args);
+                    }
+                case _: throw "Call of non-function";
+            }
+        }
+      }
+    }
+
+    // PRINT
+    static function PRINT(exp:MalType):String {
+        return Printer.pr_str(exp, true);
+    }
+
+    // repl
+    static var repl_env = new Env(null);
+
+    static function rep(line:String):String {
+        return PRINT(EVAL(READ(line), repl_env));
+    }
+
+    public static function main() {
+        #if js
+            #error "JS not supported yet"
+        #end
+
+        // core.EXT: defined using Haxe
+        for (k in Core.ns.keys()) {
+            repl_env.set(MalSymbol(k), MalFunc(Core.ns[k],null,null,null,false,nil));
+        }
+
+        var evalfn = MalFunc(function(args) {
+            return EVAL(args[0], repl_env);
+        },null,null,null,false,nil);
+        repl_env.set(MalSymbol("eval"), evalfn);
+
+        var cmdargs = Sys.args().map(function(a) { return MalString(a); });
+        repl_env.set(MalSymbol("*ARGV*"), MalList(cmdargs));
+
+        // core.mal: defined using the language itself
+        rep("(def! not (fn* (a) (if a false true)))");
+        rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
+
+        if (cmdargs.length > 0) {
+            rep('(load-file "${Sys.args()[0]}")');
+            Sys.exit(0);
+        }
+
+        while (true) {
+            try {
+                Sys.print("user> ");
+                var line = Sys.stdin().readLine();
+                if (line == "") { continue; }
+                Sys.println(rep(line));
+            } catch (exc:BlankLine) {
+                continue;
+            } catch (exc:haxe.io.Eof) {
+                Sys.exit(0);
+            } catch (exc:Dynamic) {
+                Sys.println(exc);
+            }
+        }
+    }
+}
diff --git a/haxe/Step8_macros.hx b/haxe/Step8_macros.hx
new file mode 100644 (file)
index 0000000..9330fe6
--- /dev/null
@@ -0,0 +1,217 @@
+import types.Types.MalType;
+import types.Types.*;
+import reader.*;
+import printer.*;
+import env.*;
+import core.*;
+
+class Step8_macros {
+    // READ
+    static function READ(str:String):MalType {
+        return Reader.read_str(str);
+    }
+
+    // EVAL
+    static function is_pair(ast:MalType) {
+        return switch (ast) {
+            case MalList(l) | MalVector(l): l.length > 0;
+            case _: false;
+        }
+    }
+
+    static function quasiquote(ast:MalType) {
+        if (!is_pair(ast)) {
+            return MalList([MalSymbol("quote"), ast]);
+        } else {
+            var a0 = first(ast);
+            if (_equal_Q(a0, MalSymbol("unquote"))) {
+                    return _nth(ast, 1);
+            } else if (is_pair(a0)) {
+                var a00 = first(a0);
+                if (_equal_Q(a00, MalSymbol("splice-unquote"))) {
+                    return MalList([MalSymbol("concat"),
+                                    _nth(a0, 1),
+                                    quasiquote(rest(ast))]);
+                }
+            }
+            return MalList([MalSymbol("cons"),
+                            quasiquote(a0),
+                            quasiquote(rest(ast))]);
+        }
+    }
+
+    static function is_macro(ast:MalType, env:Env) {
+        return switch(ast) {
+            case MalList(a):
+                var a0 = a[0];
+                return symbol_Q(a0) &&
+                       env.find(a0) != null &&
+                       _macro_Q(env.get(a0));
+            case _: false;
+        }
+    }
+
+    static function macroexpand(ast:MalType, env:Env) {
+        while (is_macro(ast, env)) {
+            var mac = env.get(first(ast));
+            switch (mac) {
+                case MalFunc(f,_,_,_,_,_):
+                    ast = f(_list(ast).slice(1));
+                case _: break;
+            }
+        }
+        return ast;
+    }
+
+    static function eval_ast(ast:MalType, env:Env) {
+        return switch (ast) {
+            case MalSymbol(s): env.get(ast);
+            case MalList(l):
+                MalList(l.map(function(x) { return EVAL(x, env); }));
+            case MalVector(l):
+                MalVector(l.map(function(x) { return EVAL(x, env); }));
+            case MalHashMap(m):
+                var new_map = new Map<String,MalType>();
+                for (k in m.keys()) {
+                    new_map[k] = EVAL(m[k], env);
+                }
+                MalHashMap(new_map);
+            case _: ast;
+        }
+    }
+
+    static function EVAL(ast:MalType, env:Env):MalType {
+      while (true) {
+        if (!list_Q(ast)) { return eval_ast(ast, env); }
+
+        // apply
+        ast = macroexpand(ast, env);
+        if (!list_Q(ast)) { return ast; }
+
+        var alst = _list(ast);
+        switch (alst[0]) {
+        case MalSymbol("def!"):
+            return env.set(alst[1], EVAL(alst[2], env));
+        case MalSymbol("let*"):
+            var let_env = new Env(env);
+            switch (alst[1]) {
+                case MalList(l) | MalVector(l):
+                    for (i in 0...l.length) {
+                        if ((i%2) > 0) { continue; }
+                        let_env.set(l[i], EVAL(l[i+1], let_env));
+                    }
+                case _: throw "Invalid let*";
+            }
+            ast = alst[2];
+            env = let_env;
+            continue; // TCO
+        case MalSymbol("quote"):
+            return alst[1];
+        case MalSymbol("quasiquote"):
+            ast = quasiquote(alst[1]);
+            continue; // TCO
+        case MalSymbol("defmacro!"):
+            var func = EVAL(alst[2], env);
+            return switch (func) {
+                case MalFunc(f,ast,e,params,_,_):
+                    env.set(alst[1], MalFunc(f,ast,e,params,true,nil));
+                case _:
+                    throw "Invalid defmacro! call";
+            }
+        case MalSymbol("macroexpand"):
+            return macroexpand(alst[1], env);
+        case MalSymbol("do"):
+            var el = eval_ast(MalList(alst.slice(1, alst.length-1)), env);
+            ast = last(ast);
+            continue; // TCO
+        case MalSymbol("if"):
+            var cond = EVAL(alst[1], env);
+            if (cond != MalFalse && cond != MalNil) {
+                ast = alst[2];
+            } else if (alst.length > 3) {
+                ast = alst[3];
+            } else {
+                return MalNil;
+            }
+            continue; // TCO
+        case MalSymbol("fn*"):
+            return MalFunc(function (args) {
+                return EVAL(alst[2], new Env(env, _list(alst[1]), args));
+            },alst[2],env,alst[1],false,nil);
+        case _:
+            var el = eval_ast(ast, env);
+            var lst = _list(el);
+            switch (first(el)) {
+                case MalFunc(f,a,e,params,_,_):
+                    var args = _list(el).slice(1);
+                    if (a != null) {
+                        ast = a;
+                        env = new Env(e, _list(params), args);
+                        continue; // TCO
+                    } else {
+                        return f(args);
+                    }
+                case _: throw "Call of non-function";
+            }
+        }
+      }
+    }
+
+    // PRINT
+    static function PRINT(exp:MalType):String {
+        return Printer.pr_str(exp, true);
+    }
+
+    // repl
+    static var repl_env = new Env(null);
+
+    static function rep(line:String):String {
+        return PRINT(EVAL(READ(line), repl_env));
+    }
+
+    public static function main() {
+        #if js
+            #error "JS not supported yet"
+        #end
+
+        // core.EXT: defined using Haxe
+        for (k in Core.ns.keys()) {
+            repl_env.set(MalSymbol(k), MalFunc(Core.ns[k],null,null,null,false,nil));
+        }
+
+        var evalfn = MalFunc(function(args) {
+            return EVAL(args[0], repl_env);
+        },null,null,null,false,nil);
+        repl_env.set(MalSymbol("eval"), evalfn);
+
+        var cmdargs = Sys.args().map(function(a) { return MalString(a); });
+        repl_env.set(MalSymbol("*ARGV*"), MalList(cmdargs));
+
+        // core.mal: defined using the language itself
+        rep("(def! not (fn* (a) (if a false true)))");
+        rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
+        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)))))))");
+        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))))))))");
+
+
+        if (cmdargs.length > 0) {
+            rep('(load-file "${Sys.args()[0]}")');
+            Sys.exit(0);
+        }
+
+        while (true) {
+            try {
+                Sys.print("user> ");
+                var line = Sys.stdin().readLine();
+                if (line == "") { continue; }
+                Sys.println(rep(line));
+            } catch (exc:BlankLine) {
+                continue;
+            } catch (exc:haxe.io.Eof) {
+                Sys.exit(0);
+            } catch (exc:Dynamic) {
+                Sys.println(exc);
+            }
+        }
+    }
+}
diff --git a/haxe/Step9_try.hx b/haxe/Step9_try.hx
new file mode 100644 (file)
index 0000000..3e841b5
--- /dev/null
@@ -0,0 +1,240 @@
+import types.Types.MalType;
+import types.Types.*;
+import types.MalException;
+import reader.*;
+import printer.*;
+import env.*;
+import core.*;
+import haxe.rtti.Meta;
+
+class Step9_try {
+    // READ
+    static function READ(str:String):MalType {
+        return Reader.read_str(str);
+    }
+
+    // EVAL
+    static function is_pair(ast:MalType) {
+        return switch (ast) {
+            case MalList(l) | MalVector(l): l.length > 0;
+            case _: false;
+        }
+    }
+
+    static function quasiquote(ast:MalType) {
+        if (!is_pair(ast)) {
+            return MalList([MalSymbol("quote"), ast]);
+        } else {
+            var a0 = first(ast);
+            if (_equal_Q(a0, MalSymbol("unquote"))) {
+                    return _nth(ast, 1);
+            } else if (is_pair(a0)) {
+                var a00 = first(a0);
+                if (_equal_Q(a00, MalSymbol("splice-unquote"))) {
+                    return MalList([MalSymbol("concat"),
+                                    _nth(a0, 1),
+                                    quasiquote(rest(ast))]);
+                }
+            }
+            return MalList([MalSymbol("cons"),
+                            quasiquote(a0),
+                            quasiquote(rest(ast))]);
+        }
+    }
+
+    static function is_macro(ast:MalType, env:Env) {
+        return switch(ast) {
+            case MalList(a):
+                var a0 = a[0];
+                return symbol_Q(a0) &&
+                       env.find(a0) != null &&
+                       _macro_Q(env.get(a0));
+            case _: false;
+        }
+    }
+
+    static function macroexpand(ast:MalType, env:Env) {
+        while (is_macro(ast, env)) {
+            var mac = env.get(first(ast));
+            switch (mac) {
+                case MalFunc(f,_,_,_,_,_):
+                    ast = f(_list(ast).slice(1));
+                case _: break;
+            }
+        }
+        return ast;
+    }
+
+    static function eval_ast(ast:MalType, env:Env) {
+        return switch (ast) {
+            case MalSymbol(s): env.get(ast);
+            case MalList(l):
+                MalList(l.map(function(x) { return EVAL(x, env); }));
+            case MalVector(l):
+                MalVector(l.map(function(x) { return EVAL(x, env); }));
+            case MalHashMap(m):
+                var new_map = new Map<String,MalType>();
+                for (k in m.keys()) {
+                    new_map[k] = EVAL(m[k], env);
+                }
+                MalHashMap(new_map);
+            case _: ast;
+        }
+    }
+
+    static function EVAL(ast:MalType, env:Env):MalType {
+      while (true) {
+        if (!list_Q(ast)) { return eval_ast(ast, env); }
+
+        // apply
+        ast = macroexpand(ast, env);
+        if (!list_Q(ast)) { return ast; }
+
+        var alst = _list(ast);
+        switch (alst[0]) {
+        case MalSymbol("def!"):
+            return env.set(alst[1], EVAL(alst[2], env));
+        case MalSymbol("let*"):
+            var let_env = new Env(env);
+            switch (alst[1]) {
+                case MalList(l) | MalVector(l):
+                    for (i in 0...l.length) {
+                        if ((i%2) > 0) { continue; }
+                        let_env.set(l[i], EVAL(l[i+1], let_env));
+                    }
+                case _: throw "Invalid let*";
+            }
+            ast = alst[2];
+            env = let_env;
+            continue; // TCO
+        case MalSymbol("quote"):
+            return alst[1];
+        case MalSymbol("quasiquote"):
+            ast = quasiquote(alst[1]);
+            continue; // TCO
+        case MalSymbol("defmacro!"):
+            var func = EVAL(alst[2], env);
+            return switch (func) {
+                case MalFunc(f,ast,e,params,_,_):
+                    env.set(alst[1], MalFunc(f,ast,e,params,true,nil));
+                case _:
+                    throw "Invalid defmacro! call";
+            }
+        case MalSymbol("macroexpand"):
+            return macroexpand(alst[1], env);
+        case MalSymbol("try*"):
+            try {
+                return EVAL(alst[1], env);
+            } catch (err:Dynamic) {
+                if (alst.length > 2) {
+                    switch (alst[2]) {
+                        case MalList([MalSymbol("catch*"), a21, a22]): 
+                            var exc;
+                            if (Type.getClass(err) == MalException) {
+                                exc = err.obj;
+                            } else {
+                                exc = MalString(Std.string(err));
+                            };
+                            return EVAL(a22, new Env(env, [a21], [exc]));
+                        case _:
+                            throw err;
+                    }
+                } else {
+                    throw err;
+                }
+            }
+        case MalSymbol("do"):
+            var el = eval_ast(MalList(alst.slice(1, alst.length-1)), env);
+            ast = last(ast);
+            continue; // TCO
+        case MalSymbol("if"):
+            var cond = EVAL(alst[1], env);
+            if (cond != MalFalse && cond != MalNil) {
+                ast = alst[2];
+            } else if (alst.length > 3) {
+                ast = alst[3];
+            } else {
+                return MalNil;
+            }
+            continue; // TCO
+        case MalSymbol("fn*"):
+            return MalFunc(function (args) {
+                return EVAL(alst[2], new Env(env, _list(alst[1]), args));
+            },alst[2],env,alst[1],false,nil);
+        case _:
+            var el = eval_ast(ast, env);
+            var lst = _list(el);
+            switch (first(el)) {
+                case MalFunc(f,a,e,params,_,_):
+                    var args = _list(el).slice(1);
+                    if (a != null) {
+                        ast = a;
+                        env = new Env(e, _list(params), args);
+                        continue; // TCO
+                    } else {
+                        return f(args);
+                    }
+                case _: throw "Call of non-function";
+            }
+        }
+      }
+    }
+
+    // PRINT
+    static function PRINT(exp:MalType):String {
+        return Printer.pr_str(exp, true);
+    }
+
+    // repl
+    static var repl_env = new Env(null);
+
+    static function rep(line:String):String {
+        return PRINT(EVAL(READ(line), repl_env));
+    }
+
+    public static function main() {
+        #if js
+            #error "JS not supported yet"
+        #end
+
+        // core.EXT: defined using Haxe
+        for (k in Core.ns.keys()) {
+            repl_env.set(MalSymbol(k), MalFunc(Core.ns[k],null,null,null,false,nil));
+        }
+
+        var evalfn = MalFunc(function(args) {
+            return EVAL(args[0], repl_env);
+        },null,null,null,false,nil);
+        repl_env.set(MalSymbol("eval"), evalfn);
+
+        var cmdargs = Sys.args().map(function(a) { return MalString(a); });
+        repl_env.set(MalSymbol("*ARGV*"), MalList(cmdargs));
+
+        // core.mal: defined using the language itself
+        rep("(def! not (fn* (a) (if a false true)))");
+        rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
+        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)))))))");
+        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))))))))");
+
+
+        if (cmdargs.length > 0) {
+            rep('(load-file "${Sys.args()[0]}")');
+            Sys.exit(0);
+        }
+
+        while (true) {
+            try {
+                Sys.print("user> ");
+                var line = Sys.stdin().readLine();
+                if (line == "") { continue; }
+                Sys.println(rep(line));
+            } catch (exc:BlankLine) {
+                continue;
+            } catch (exc:haxe.io.Eof) {
+                Sys.exit(0);
+            } catch (exc:Dynamic) {
+                Sys.println(exc);
+            }
+        }
+    }
+}
diff --git a/haxe/StepA_mal.hx b/haxe/StepA_mal.hx
new file mode 100644 (file)
index 0000000..27956e6
--- /dev/null
@@ -0,0 +1,242 @@
+import types.Types.MalType;
+import types.Types.*;
+import types.MalException;
+import reader.*;
+import printer.*;
+import env.*;
+import core.*;
+import haxe.rtti.Meta;
+
+class StepA_mal {
+    // READ
+    static function READ(str:String):MalType {
+        return Reader.read_str(str);
+    }
+
+    // EVAL
+    static function is_pair(ast:MalType) {
+        return switch (ast) {
+            case MalList(l) | MalVector(l): l.length > 0;
+            case _: false;
+        }
+    }
+
+    static function quasiquote(ast:MalType) {
+        if (!is_pair(ast)) {
+            return MalList([MalSymbol("quote"), ast]);
+        } else {
+            var a0 = first(ast);
+            if (_equal_Q(a0, MalSymbol("unquote"))) {
+                    return _nth(ast, 1);
+            } else if (is_pair(a0)) {
+                var a00 = first(a0);
+                if (_equal_Q(a00, MalSymbol("splice-unquote"))) {
+                    return MalList([MalSymbol("concat"),
+                                    _nth(a0, 1),
+                                    quasiquote(rest(ast))]);
+                }
+            }
+            return MalList([MalSymbol("cons"),
+                            quasiquote(a0),
+                            quasiquote(rest(ast))]);
+        }
+    }
+
+    static function is_macro(ast:MalType, env:Env) {
+        return switch(ast) {
+            case MalList(a):
+                var a0 = a[0];
+                return symbol_Q(a0) &&
+                       env.find(a0) != null &&
+                       _macro_Q(env.get(a0));
+            case _: false;
+        }
+    }
+
+    static function macroexpand(ast:MalType, env:Env) {
+        while (is_macro(ast, env)) {
+            var mac = env.get(first(ast));
+            switch (mac) {
+                case MalFunc(f,_,_,_,_,_):
+                    ast = f(_list(ast).slice(1));
+                case _: break;
+            }
+        }
+        return ast;
+    }
+
+    static function eval_ast(ast:MalType, env:Env) {
+        return switch (ast) {
+            case MalSymbol(s): env.get(ast);
+            case MalList(l):
+                MalList(l.map(function(x) { return EVAL(x, env); }));
+            case MalVector(l):
+                MalVector(l.map(function(x) { return EVAL(x, env); }));
+            case MalHashMap(m):
+                var new_map = new Map<String,MalType>();
+                for (k in m.keys()) {
+                    new_map[k] = EVAL(m[k], env);
+                }
+                MalHashMap(new_map);
+            case _: ast;
+        }
+    }
+
+    static function EVAL(ast:MalType, env:Env):MalType {
+      while (true) {
+        if (!list_Q(ast)) { return eval_ast(ast, env); }
+
+        // apply
+        ast = macroexpand(ast, env);
+        if (!list_Q(ast)) { return ast; }
+
+        var alst = _list(ast);
+        switch (alst[0]) {
+        case MalSymbol("def!"):
+            return env.set(alst[1], EVAL(alst[2], env));
+        case MalSymbol("let*"):
+            var let_env = new Env(env);
+            switch (alst[1]) {
+                case MalList(l) | MalVector(l):
+                    for (i in 0...l.length) {
+                        if ((i%2) > 0) { continue; }
+                        let_env.set(l[i], EVAL(l[i+1], let_env));
+                    }
+                case _: throw "Invalid let*";
+            }
+            ast = alst[2];
+            env = let_env;
+            continue; // TCO
+        case MalSymbol("quote"):
+            return alst[1];
+        case MalSymbol("quasiquote"):
+            ast = quasiquote(alst[1]);
+            continue; // TCO
+        case MalSymbol("defmacro!"):
+            var func = EVAL(alst[2], env);
+            return switch (func) {
+                case MalFunc(f,ast,e,params,_,_):
+                    env.set(alst[1], MalFunc(f,ast,e,params,true,nil));
+                case _:
+                    throw "Invalid defmacro! call";
+            }
+        case MalSymbol("macroexpand"):
+            return macroexpand(alst[1], env);
+        case MalSymbol("try*"):
+            try {
+                return EVAL(alst[1], env);
+            } catch (err:Dynamic) {
+                if (alst.length > 2) {
+                    switch (alst[2]) {
+                        case MalList([MalSymbol("catch*"), a21, a22]): 
+                            var exc;
+                            if (Type.getClass(err) == MalException) {
+                                exc = err.obj;
+                            } else {
+                                exc = MalString(Std.string(err));
+                            };
+                            return EVAL(a22, new Env(env, [a21], [exc]));
+                        case _:
+                            throw err;
+                    }
+                } else {
+                    throw err;
+                }
+            }
+        case MalSymbol("do"):
+            var el = eval_ast(MalList(alst.slice(1, alst.length-1)), env);
+            ast = last(ast);
+            continue; // TCO
+        case MalSymbol("if"):
+            var cond = EVAL(alst[1], env);
+            if (cond != MalFalse && cond != MalNil) {
+                ast = alst[2];
+            } else if (alst.length > 3) {
+                ast = alst[3];
+            } else {
+                return MalNil;
+            }
+            continue; // TCO
+        case MalSymbol("fn*"):
+            return MalFunc(function (args) {
+                return EVAL(alst[2], new Env(env, _list(alst[1]), args));
+            },alst[2],env,alst[1],false,nil);
+        case _:
+            var el = eval_ast(ast, env);
+            var lst = _list(el);
+            switch (first(el)) {
+                case MalFunc(f,a,e,params,_,_):
+                    var args = _list(el).slice(1);
+                    if (a != null) {
+                        ast = a;
+                        env = new Env(e, _list(params), args);
+                        continue; // TCO
+                    } else {
+                        return f(args);
+                    }
+                case _: throw "Call of non-function";
+            }
+        }
+      }
+    }
+
+    // PRINT
+    static function PRINT(exp:MalType):String {
+        return Printer.pr_str(exp, true);
+    }
+
+    // repl
+    static var repl_env = new Env(null);
+
+    static function rep(line:String):String {
+        return PRINT(EVAL(READ(line), repl_env));
+    }
+
+    public static function main() {
+        #if js
+            #error "JS not supported yet"
+        #end
+
+        // core.EXT: defined using Haxe
+        for (k in Core.ns.keys()) {
+            repl_env.set(MalSymbol(k), MalFunc(Core.ns[k],null,null,null,false,nil));
+        }
+
+        var evalfn = MalFunc(function(args) {
+            return EVAL(args[0], repl_env);
+        },null,null,null,false,nil);
+        repl_env.set(MalSymbol("eval"), evalfn);
+
+        var cmdargs = Sys.args().map(function(a) { return MalString(a); });
+        repl_env.set(MalSymbol("*ARGV*"), MalList(cmdargs.slice(1)));
+
+        // core.mal: defined using the language itself
+        rep("(def! *host-language* \"haxe\")");
+        rep("(def! not (fn* (a) (if a false true)))");
+        rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
+        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)))))))");
+        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))))))))");
+
+
+        if (cmdargs.length > 0) {
+            rep('(load-file "${Sys.args()[0]}")');
+            Sys.exit(0);
+        }
+
+        rep("(println (str \"Mal [\" *host-language* \"]\"))");
+        while (true) {
+            try {
+                Sys.print("user> ");
+                var line = Sys.stdin().readLine();
+                if (line == "") { continue; }
+                Sys.println(rep(line));
+            } catch (exc:BlankLine) {
+                continue;
+            } catch (exc:haxe.io.Eof) {
+                Sys.exit(0);
+            } catch (exc:Dynamic) {
+                Sys.println(exc);
+            }
+        }
+    }
+}
index 9a57f07..c7246cc 100644 (file)
@@ -2,6 +2,7 @@ package core;
 
 import types.Types.MalType;
 import types.Types.*;
+import types.MalException;
 import printer.Printer;
 import reader.Reader;
 
@@ -47,11 +48,31 @@ class Core {
     }
     static function prn(args) {
         Sys.println(args.map(function(s) { return Printer.pr_str(s,true); }).join(" "));
-        return MalNil;
+        return nil;
     }
     static function println(args) {
         Sys.println(args.map(function(s) { return Printer.pr_str(s,false); }).join(" "));
-        return MalNil;
+        return nil;
+    }
+
+    static function symbol(args) {
+        return switch (args[0]) {
+            case MalString(s): MalSymbol(s);
+            case MalSymbol(_): args[0];
+            case _: throw "Invalid symbol call";
+        }
+    }
+
+    static function keyword(args) {
+        return switch (args[0]) {
+            case MalString(s):
+                if (keyword_Q(args[0])) {
+                    args[0];
+                } else {
+                    MalString("\x7f" + s);
+                }
+            case _: throw "Invalid keyword call";
+        }
     }
 
     static function read_string(args) {
@@ -61,6 +82,19 @@ class Core {
         }
     }
 
+    static function readline(args) {
+        return switch (args[0]) {
+            case MalString(s):
+                Sys.print(s);
+                try {
+                    MalString(Sys.stdin().readLine());
+                } catch (exc:haxe.io.Eof) {
+                    nil;
+                }
+            case _: throw "invalid readline call";
+        }
+    }
+
     static function slurp(args) {
         return switch (args[0]) {
             case MalString(s): MalString(sys.io.File.getContent(s));
@@ -68,6 +102,44 @@ class Core {
         }
     }
 
+    // sequential functions
+    static function sequential_Q(args) {
+        return BoolFn(list_Q(args[0]) || vector_Q(args[0]));
+    }
+
+    static function cons(args) {
+        return switch [args[0], args[1]] {
+            case [a, MalList(l)] |
+                 [a, MalVector(l)]:
+                MalList([a].concat(l));
+            case [a, MalNil]:
+                MalList([a]);
+            case _: throw "Invalid cons call";
+        }
+    }
+
+    static function do_concat(args:Array<MalType>) {
+        var res:Array<MalType> = [];
+        for (a in args) {
+            switch (a) {
+                case MalList(l) | MalVector(l): 
+                    res = res.concat(l);
+                case MalNil:
+                    continue;
+                case _:
+                    throw "concat called with non-sequence";
+            }
+        }
+        return MalList(res);
+    }
+
+    static function nth(args) {
+        return switch [args[0], args[1]] {
+            case [seq, MalInt(idx)]:
+                _nth(seq, idx);
+            case _: throw "Invalid nth call";
+        }
+    }
 
     static function empty_Q(args) {
         return switch (args[0]) {
@@ -87,15 +159,146 @@ class Core {
         }
     }
 
+    static function apply(args) {
+        return switch [args[0], args[args.length-1]] {
+            case [MalFunc(f,_,_,_,_), MalList(l)] |
+                 [MalFunc(f,_,_,_,_), MalVector(l)]:
+                var fargs = args.slice(1,args.length-1).concat(l);
+                return f(fargs);
+            case _: throw "Invalid apply call";
+        }
+    }
+
+    static function do_map(args) {
+        return switch [args[0], args[1]] {
+            case [MalFunc(f,_,_,_,_), MalList(l)] |
+                 [MalFunc(f,_,_,_,_), MalVector(l)]:
+                return MalList(l.map(function(x) { return f([x]); }));
+            case _: throw "Invalid map call";
+        }
+    }
+
+    // hash-map functions
+
+    public static function get(hm:MalType, key:MalType) {
+        return switch [hm, key] {
+            case [MalHashMap(m), MalString(k)]:
+                if (m.exists(k)) {
+                    m[k];
+                } else {
+                    nil;
+                }
+            case [nil, MalString(k)]:
+                nil;
+            case _: throw "invalid get call";
+        }
+    }
+
+    public static function assoc(args) {
+        return switch (args[0]) {
+            case MalHashMap(m):
+                var new_m = _clone(args[0]);
+                MalHashMap(assoc_BANG(new_m, args.slice(1)));
+            case _: throw "invalid assoc call";
+        }
+    }
+
+    public static function dissoc(args) {
+        return switch (args[0]) {
+            case MalHashMap(m):
+                var new_m = _clone(args[0]);
+                MalHashMap(dissoc_BANG(new_m, args.slice(1)));
+            case _: throw "invalid dissoc call";
+        }
+    }
+
+    public static function contains_Q(hm:MalType, key:MalType) {
+        return switch [hm, key] {
+            case [MalHashMap(m), MalString(k)]:
+                m.exists(k);
+            case _: throw "invalid contains? call";
+        }
+    }
+
+    public static function keys(hm:MalType) {
+        return switch (hm) {
+            case MalHashMap(m):
+                MalList([for (k in m.keys()) MalString(k)]);
+            case _: throw "invalid keys call";
+        }
+    }
+
+    public static function vals(hm:MalType) {
+        return switch (hm) {
+            case MalHashMap(m):
+                MalList([for (k in m.keys()) m[k]]);
+            case _: throw "invalid vals call";
+        }
+    }
+
+    // metadata functions
+    static function meta(args) {
+        return switch (args[0]) {
+            case MalFunc(f,_,_,_,_,meta): meta;
+            case _: throw "meta called on non-function";
+        }
+    }
+
+    static function with_meta(args) {
+        return switch (args[0]) {
+            case MalFunc(f,a,e,p,mac,_):
+                MalFunc(f,a,e,p,mac,args[1]);
+            case _: throw "with_meta called on non-function";
+        }
+    }
+
+
+
+    // atom functions
+
+    static function deref(args) {
+        return switch (args[0]) {
+            case MalAtom(v): v.val;
+            case _: throw "deref called on non-atom";
+        }
+    }
+
+    static function reset_BANG(args) {
+        return switch (args[0]) {
+            case MalAtom(v): v.val = args[1];
+            case _: throw "reset! called on non-atom";
+        }
+    }
+
+    static function swap_BANG(args) {
+        return switch [args[0], args[1]] {
+            case [MalAtom(v), MalFunc(f,_,_,_,_)]:
+                var fargs = [v.val].concat(args.slice(2));
+                v.val = f(fargs);
+                v.val;
+            case _: throw "swap! called on non-atom";
+        }
+    }
+
 
     public static var ns:Map<String,Array<MalType> -> MalType> = [
         "=" => function(a) { return BoolFn(_equal_Q(a[0],a[1])); },
+        "throw" => function(a) { throw new MalException(a[0]); },
+
+        "nil?" => function(a) { return BoolFn(nil_Q(a[0])); },
+        "true?" => function(a) { return BoolFn(true_Q(a[0])); },
+        "false?" => function(a) { return BoolFn(false_Q(a[0])); },
+        "symbol" => symbol,
+        "symbol?" => function(a) { return BoolFn(symbol_Q(a[0])); },
+        "keyword" => keyword,
+        "keyword?" => function(a) { return BoolFn(keyword_Q(a[0])); },
 
         "pr-str" => pr_str,
         "str" => str,
         "prn" => prn,
         "println" => println,
         "read-string" => read_string,
+        "readline" => readline,
         "slurp" => slurp,
 
         "<" => BoolOp(function(a,b) {return a<b;}),
@@ -109,8 +312,36 @@ class Core {
 
         "list" => function(a) { return MalList(a); },
         "list?" => function(a) { return BoolFn(list_Q(a[0])); },
+        "vector" => function(a) { return MalVector(a); },
+        "vector?" => function(a) { return BoolFn(vector_Q(a[0])); },
+        "hash-map" => hash_map,
+        "map?" => function(a) { return BoolFn(hash_map_Q(a[0])); },
+        "assoc" => assoc,
+        "dissoc" => dissoc,
+        "get" => function(a) { return get(a[0],a[1]); },
+        "contains?" => function(a) { return BoolFn(contains_Q(a[0], a[1])); },
+        "keys" => function(a) { return keys(a[0]); } ,
+        "vals" => function(a) { return vals(a[0]); } ,
 
+        "sequential?" => sequential_Q,
+        "cons" => cons,
+        "concat" => do_concat,
+        "nth" => nth,
+        "first" => function(a) { return first(a[0]); },
+        "rest" => function(a) { return MalList(_list(a[0]).slice(1)); },
         "empty?" => empty_Q,
-        "count" => count
+        "count" => count,
+        "apply" => apply,
+        "map" => do_map,
+
+        "conj" => function(a) { return nil; },
+
+        "meta" => meta,
+        "with-meta" => with_meta,
+        "atom" => function(a) { return MalAtom({val:a[0]}); },
+        "atom?" => function(a) { return BoolFn(atom_Q(a[0])); },
+        "deref" => deref,
+        "reset!" => reset_BANG,
+        "swap!" => swap_BANG
     ];
 }
index 501b679..116b686 100644 (file)
@@ -34,7 +34,16 @@ class Printer {
             case MalVector(l):
                 var lst = l.map(function(e) {return pr_str(e,_r);});
                 '[${lst.join(" ")}]';
-            case MalFunc(f,ast,_,params):
+            case MalHashMap(m):
+                var elems = [];
+                for (k in m.keys()) {
+                    elems.push(pr_str(MalString(k), _r));
+                    elems.push(pr_str(m[k], _r));
+                }
+                '{${elems.join(" ")}}';
+            case MalAtom(v):
+                '(atom ${pr_str(v.val,_r)})';
+            case MalFunc(f,ast,_,params,_):
                 if (ast != null) {
                     '(fn* ${pr_str(params,true)} ${pr_str(ast)})';
                 } else {
index b4ce065..00d5fe8 100644 (file)
@@ -1,7 +1,7 @@
 package reader;
 
 import types.Types.MalType;
-import types.Types.MalType.*;
+import types.Types.*;
 
 class Reader {
     // Reader class implementation
@@ -76,11 +76,11 @@ class Reader {
         var lst = [];
         var token = rdr.next();
         if (token != start) {
-            throw "expected '$start'";
+            throw 'expected \'${start}\'';
         }
         while ((token = rdr.peek()) != end) {
             if (token == null) {
-                throw "expected '$end', got EOF";
+                throw 'expected \'${end}\', got EOF';
             }
             lst.push(read_form(rdr));
         }
@@ -109,8 +109,14 @@ class Reader {
             // list
             case ")": throw("unexpected ')'");
             case "(": MalList(read_seq(rdr, '(', ')'));
+
+            // vector
             case "]": throw("unexpected ']'");
             case "[": MalVector(read_seq(rdr, '[', ']'));
+
+            // hashmap
+            case "}": throw("unexpected '}'");
+            case "{": hash_map(read_seq(rdr, '{', '}'));
             case _: read_atom(rdr);
         }
     }
diff --git a/haxe/types/MalException.hx b/haxe/types/MalException.hx
new file mode 100644 (file)
index 0000000..8e3ff2f
--- /dev/null
@@ -0,0 +1,10 @@
+package types;
+
+import types.Types.MalType;
+
+class MalException {
+    public var obj:MalType = null;
+    public function new(obj:MalType) {
+        this.obj = obj;
+    }
+}
index 3a0cb4a..d7986b4 100644 (file)
@@ -2,6 +2,9 @@ package types;
 
 import env.Env;
 
+class MalAtomContainer {
+}
+
 enum MalType {
     MalNil;
     MalTrue;
@@ -11,10 +14,14 @@ enum MalType {
     MalSymbol(val:String);
     MalList(val:Array<MalType>);
     MalVector(val:Array<MalType>);
+    MalHashMap(val:Map<String,MalType>);
+    MalAtom(val:{val:MalType});
     MalFunc(val:(Array<MalType>)->MalType,
             ast:MalType,
             env:Env,
-            params:MalType);
+            params:MalType,
+            ismacro:Bool,
+            meta:MalType);
 }
 
 class Types {
@@ -36,10 +43,79 @@ class Types {
                     }
                 }
                 true;
+            case [MalHashMap(ma), MalHashMap(mb)]:
+                var maks = ma.keys(),
+                    mbks = mb.keys(),
+                    malen = 0,
+                    mblen = 0;
+                for (k in maks) {
+                    malen += 1;
+                    if ((!mb.exists(k)) || !_equal_Q(ma[k], mb[k])) {
+                        return false;
+                    }
+                }
+                for (k in mbks) { mblen += 1; }
+                if (malen != mblen) { return false; }
+                true;
             case _: a == b;
         }
     }
 
+    public static function _clone(a:MalType) {
+        return switch (a) {
+            case MalHashMap(m):
+                var new_m = new Map<String,MalType>();
+                for (k in m.keys()) {
+                    new_m[k] = m[k];
+                }
+                return new_m;
+            case _: throw "unsupported clone call";
+        }
+    }
+
+    public static function _macro_Q(x:MalType) {
+        return switch (x) {
+            case MalFunc(_,_,_,_,ismacro,_): ismacro;
+            case _: false;
+        }
+    }
+
+    public static function nil_Q(x:MalType) {
+        return switch (x) {
+            case MalNil: true;
+            case _: false;
+        }
+    }
+
+    public static function true_Q(x:MalType) {
+        return switch (x) {
+            case MalTrue: true;
+            case _: false;
+        }
+    }
+
+    public static function false_Q(x:MalType) {
+        return switch (x) {
+            case MalFalse: true;
+            case _: false;
+        }
+    }
+
+    public static function symbol_Q(x:MalType) {
+        return switch (x) {
+            case MalSymbol(_): true;
+            case _: false;
+        }
+    }
+
+    public static function keyword_Q(x:MalType) {
+        return switch (x) {
+            case MalString(s):
+                s.charAt(0) == "\x7f";
+            case _: false;
+        }
+    }
+
     // Sequence operations
     public static function list_Q(x:MalType) {
         return switch (x) {
@@ -48,15 +124,43 @@ class Types {
         }
     }
 
+    public static function vector_Q(x:MalType) {
+        return switch (x) {
+            case MalVector(_): true;
+            case _: false;
+        }
+    }
+
     public static function first(seq:MalType) {
         return switch (seq) {
             case MalList(l) | MalVector(l):
-                if (l.length == 0) { MalNil; }
+                if (l.length == 0) { nil; }
                 else               { l[0]; }
             case _: throw "first called on non-sequence";
         }
     }
 
+    public static function rest(seq:MalType) {
+        return switch (seq) {
+            case MalList(l) | MalVector(l):
+                if (l.length <= 1) { nil; }
+                else               { MalList(l.slice(1)); }
+            case _: throw "rest called on non-sequence";
+        }
+    }
+
+    public static function _nth(seq:MalType, idx:Int) {
+        return switch (seq) {
+            case MalList(l) | MalVector(l):
+                if (l.length > idx) {
+                    l[idx];
+                } else {
+                    throw "nth index out of bounds";
+                }
+            case _: throw "nth called on non-sequence";
+        }
+    }
+
     public static function _list(seq:MalType) {
         return switch (seq) {
             case MalList(l) | MalVector(l): l;
@@ -64,13 +168,65 @@ class Types {
         }
     }
 
+    public static function _map(hm:MalType) {
+        return switch (hm) {
+            case MalHashMap(m): m;
+            case _: throw "_map called on non-hash-map";
+        }
+    }
+
     public static function last(seq:MalType) {
         return switch (seq) {
             case MalList(l) | MalVector(l):
-                if (l.length == 0) { MalNil; }
+                if (l.length == 0) { nil; }
                 else               { l[l.length-1]; }
             case _: throw "last called on non-sequence";
         }
     }
+
+    public static function hash_map(kvs:Array<MalType>) {
+        var m = new Map<String,MalType>();
+        return MalHashMap(assoc_BANG(m, kvs));
+    }
+
+    public static function assoc_BANG(m:Map<String,MalType>,
+                                      kvs:Array<MalType>) {
+        for (i in 0...kvs.length) {
+            if (i % 2 > 0) { continue; }
+            switch (kvs[i]) {
+                case MalString(k):
+                    m[k] = kvs[i+1];
+                case _: throw "invalid assoc! call";
+            }
+        }
+        return m;
+    }
+
+    public static function dissoc_BANG(m:Map<String,MalType>,
+                                      ks:Array<MalType>) {
+        for (i in 0...ks.length) {
+            switch (ks[i]) {
+                case MalString(k):
+                    m.remove(k);
+                case _: throw "invalid dissoc! call";
+            }
+        }
+        return m;
+    }
+
+    public static function hash_map_Q(x:MalType) {
+        return switch (x) {
+            case MalHashMap(_): true;
+            case _: false;
+        }
+    }
+
+    public static function atom_Q(x:MalType) {
+        return switch (x) {
+            case MalAtom(_): true;
+            case _: false;
+        }
+    }
+
 }