-# 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
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;
}
}
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";
}
}
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;}),
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;
}
}
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";
}
}
case _: throw "Invalid numeric op call";
}
- },null,null,null);
+ },null,null,null,false,nil);
}
static var repl_env = new Env(null);
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;
}
}
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";
}
}
// 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
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;
}
}
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);
}
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;
// 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
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;
}
}
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);
}
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;
// 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); });
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);
}
--- /dev/null
+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);
+ }
+ }
+ }
+}
--- /dev/null
+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);
+ }
+ }
+ }
+}
--- /dev/null
+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);
+ }
+ }
+ }
+}
--- /dev/null
+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);
+ }
+ }
+ }
+}
import types.Types.MalType;
import types.Types.*;
+import types.MalException;
import printer.Printer;
import reader.Reader;
}
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) {
}
}
+ 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));
}
}
+ // 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]) {
}
}
+ 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;}),
"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
];
}
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 {
package reader;
import types.Types.MalType;
-import types.Types.MalType.*;
+import types.Types.*;
class Reader {
// Reader class implementation
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));
}
// 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);
}
}
--- /dev/null
+package types;
+
+import types.Types.MalType;
+
+class MalException {
+ public var obj:MalType = null;
+ public function new(obj:MalType) {
+ this.obj = obj;
+ }
+}
import env.Env;
+class MalAtomContainer {
+}
+
enum MalType {
MalNil;
MalTrue;
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 {
}
}
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) {
}
}
+ 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;
}
}
+ 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;
+ }
+ }
+
}