From 1d166495107408b10b87ce2592aa42e830300f97 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 25 Jan 2016 22:51:58 -0600 Subject: [PATCH] Haxe: step7-A, hash-maps, metadata, self-hosting. --- haxe/Makefile | 34 ++++-- haxe/Step2_eval.hx | 10 +- haxe/Step3_env.hx | 14 ++- haxe/Step4_if_fn_do.hx | 12 +- haxe/Step5_tco.hx | 19 ++- haxe/Step6_file.hx | 18 ++- haxe/Step7_quote.hx | 179 +++++++++++++++++++++++++++ haxe/Step8_macros.hx | 217 +++++++++++++++++++++++++++++++++ haxe/Step9_try.hx | 240 ++++++++++++++++++++++++++++++++++++ haxe/StepA_mal.hx | 242 +++++++++++++++++++++++++++++++++++++ haxe/core/Core.hx | 237 +++++++++++++++++++++++++++++++++++- haxe/printer/Printer.hx | 11 +- haxe/reader/Reader.hx | 12 +- haxe/types/MalException.hx | 10 ++ haxe/types/Types.hx | 162 ++++++++++++++++++++++++- 15 files changed, 1375 insertions(+), 42 deletions(-) rewrite haxe/Makefile (83%) create mode 100644 haxe/Step7_quote.hx create mode 100644 haxe/Step8_macros.hx create mode 100644 haxe/Step9_try.hx create mode 100644 haxe/StepA_mal.hx create mode 100644 haxe/types/MalException.hx diff --git a/haxe/Makefile b/haxe/Makefile dissimilarity index 83% index e1893ac6..d9818a09 100644 --- a/haxe/Makefile +++ b/haxe/Makefile @@ -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 diff --git a/haxe/Step2_eval.hx b/haxe/Step2_eval.hx index 4066e8be..e8edea06 100644 --- a/haxe/Step2_eval.hx +++ b/haxe/Step2_eval.hx @@ -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(); + 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 = ["+" => NumOp(function(a,b) {return a+b;}), diff --git a/haxe/Step3_env.hx b/haxe/Step3_env.hx index ba07d53c..1976c4b1 100644 --- a/haxe/Step3_env.hx +++ b/haxe/Step3_env.hx @@ -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(); + 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); diff --git a/haxe/Step4_if_fn_do.hx b/haxe/Step4_if_fn_do.hx index b4ea7f95..6ceb7c8d 100644 --- a/haxe/Step4_if_fn_do.hx +++ b/haxe/Step4_if_fn_do.hx @@ -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(); + 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 diff --git a/haxe/Step5_tco.hx b/haxe/Step5_tco.hx index 1ea12272..4d005a5b 100644 --- a/haxe/Step5_tco.hx +++ b/haxe/Step5_tco.hx @@ -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(); + 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 diff --git a/haxe/Step6_file.hx b/haxe/Step6_file.hx index c93f6bb9..d52c25f5 100644 --- a/haxe/Step6_file.hx +++ b/haxe/Step6_file.hx @@ -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(); + 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 index 00000000..b78b6759 --- /dev/null +++ b/haxe/Step7_quote.hx @@ -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(); + 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 index 00000000..9330fe6e --- /dev/null +++ b/haxe/Step8_macros.hx @@ -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(); + 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 index 00000000..3e841b5c --- /dev/null +++ b/haxe/Step9_try.hx @@ -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(); + 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 index 00000000..27956e64 --- /dev/null +++ b/haxe/StepA_mal.hx @@ -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(); + 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); + } + } + } +} diff --git a/haxe/core/Core.hx b/haxe/core/Core.hx index 9a57f073..c7246cc2 100644 --- a/haxe/core/Core.hx +++ b/haxe/core/Core.hx @@ -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) { + var res:Array = []; + 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 -> 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 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 ]; } diff --git a/haxe/printer/Printer.hx b/haxe/printer/Printer.hx index 501b6797..116b686d 100644 --- a/haxe/printer/Printer.hx +++ b/haxe/printer/Printer.hx @@ -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 { diff --git a/haxe/reader/Reader.hx b/haxe/reader/Reader.hx index b4ce0656..00d5fe8a 100644 --- a/haxe/reader/Reader.hx +++ b/haxe/reader/Reader.hx @@ -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 index 00000000..8e3ff2f0 --- /dev/null +++ b/haxe/types/MalException.hx @@ -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; + } +} diff --git a/haxe/types/Types.hx b/haxe/types/Types.hx index 3a0cb4a0..d7986b4d 100644 --- a/haxe/types/Types.hx +++ b/haxe/types/Types.hx @@ -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); MalVector(val:Array); + MalHashMap(val:Map); + MalAtom(val:{val:MalType}); MalFunc(val:(Array)->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(); + 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) { + var m = new Map(); + return MalHashMap(assoc_BANG(m, kvs)); + } + + public static function assoc_BANG(m:Map, + kvs:Array) { + 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, + ks:Array) { + 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; + } + } + } -- 2.20.1