def READ(str string) MalVal { return read_str(str) } def isPair(a MalVal) bool { return a is MalSequential && !(a as MalSequential).isEmpty } def quasiquote(ast MalVal) MalVal { if !isPair(ast) { return MalList.new([MalSymbol.new("quote"), ast]) } const astSeq = ast as MalSequential const a0 = astSeq[0] if a0.isSymbol("unquote") { return astSeq[1] } if isPair(a0) { const a0Seq = a0 as MalSequential if a0Seq[0].isSymbol("splice-unquote") { return MalList.new([MalSymbol.new("concat"), a0Seq[1], quasiquote(astSeq.rest)]) } } return MalList.new([MalSymbol.new("cons"), quasiquote(a0), quasiquote(astSeq.rest)]) } def isMacro(ast MalVal, env Env) bool { if !(ast is MalList) { return false } const astList = ast as MalList if astList.isEmpty { return false } const a0 = astList[0] if !(a0 is MalSymbol) { return false } const a0Sym = a0 as MalSymbol if env.find(a0Sym) == null { return false } const f = env.get(a0Sym) if !(f is MalFunc) { return false } return (f as MalFunc).isMacro } def macroexpand(ast MalVal, env Env) MalVal { while isMacro(ast, env) { const astList = ast as MalList const mac = env.get(astList[0] as MalSymbol) as MalFunc ast = mac.call((astList.rest as MalSequential).val) } return ast } def eval_ast(ast MalVal, env Env) MalVal { if ast is MalSymbol { return env.get(ast as MalSymbol) } else if ast is MalList { return MalList.new((ast as MalList).val.map(e => EVAL(e, env))) } else if ast is MalVector { return MalVector.new((ast as MalVector).val.map(e => EVAL(e, env))) } else if ast is MalHashMap { var result List = [] (ast as MalHashMap).val.each((k string, v MalVal) => { result.append(EVAL(MalVal.fromHashKey(k), env)) result.append(EVAL(v, env)) }) return MalHashMap.fromList(result) } else { return ast } } def EVAL(ast MalVal, env Env) MalVal { while true { if !(ast is MalList) { return eval_ast(ast, env) } ast = macroexpand(ast, env) if !(ast is MalList) { return eval_ast(ast, env) } const astList = ast as MalList if astList.isEmpty { return ast } const a0sym = astList[0] as MalSymbol if a0sym.val == "def!" { return env.set(astList[1] as MalSymbol, EVAL(astList[2], env)) } else if a0sym.val == "let*" { var letenv = Env.new(env) const assigns = astList[1] as MalSequential for i = 0; i < assigns.count; i += 2 { letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv)) } ast = astList[2] env = letenv continue # TCO } else if a0sym.val == "quote" { return astList[1] } else if a0sym.val == "quasiquote" { ast = quasiquote(astList[1]) continue # TCO } else if a0sym.val == "defmacro!" { var macro = EVAL(astList[2], env) as MalFunc macro.setAsMacro return env.set(astList[1] as MalSymbol, macro) } else if a0sym.val == "macroexpand" { return macroexpand(astList[1], env) } else if a0sym.val == "try*" { var exc MalVal try { return EVAL(astList[1], env) } catch e MalUserError { exc = e.data } catch e MalError { exc = MalString.new(e.message) } catch e Error { exc = MalString.new(e.message) } const catchClause = astList[2] as MalList var catchEnv = Env.new(env, [catchClause[1] as MalSymbol], [exc]) return EVAL(catchClause[2], catchEnv) } else if a0sym.val == "do" { const parts = astList.val.slice(1) eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env) ast = parts[parts.count - 1] continue # TCO } else if a0sym.val == "if" { const condRes = EVAL(astList[1], env) if condRes is MalNil || condRes is MalFalse { ast = astList.count > 3 ? astList[3] : gNil } else { ast = astList[2] } continue # TCO } else if a0sym.val == "fn*" { const argsNames = astList[1] as MalSequential return MalFunc.new(astList[2], argsNames, env, (args List) => EVAL(astList[2], Env.new(env, argsNames.val, args))) } else { const evaledList = eval_ast(ast, env) as MalList const fn = evaledList[0] const callArgs = evaledList.val.slice(1) if fn is MalNativeFunc { return (fn as MalNativeFunc).call(callArgs) } else if fn is MalFunc { const f = fn as MalFunc ast = f.ast env = Env.new(f.env, f.params.val, callArgs) continue # TCO } else { throw MalError.new("Expected function as head of list") } } } } def PRINT(exp MalVal) string { return exp?.print(true) } var repl_env = Env.new(null) def RE(str string) MalVal { return EVAL(READ(str), repl_env) } def REP(str string) string { return PRINT(RE(str)) } @entry def main { # core.sk: defined using Skew ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func))) repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map(e => MalString.new(e)))) # core.mal: defined using the language itself RE("(def! *host-language* \"skew\")") RE("(def! not (fn* (a) (if a false true)))") RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") RE("(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)))))))") RE("(def! *gensym-counter* (atom 0))") RE("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") if argv.count > 0 { RE("(load-file \"" + argv[0] + "\")") return } RE("(println (str \"Mal [\" *host-language* \"]\"))") var line string while (line = readLine("user> ")) != null { if line == "" { continue } try { printLn(REP(line)) } catch e MalUserError { printLn("Error: \(e.data.print(false))") } catch e MalError { printLn("Error: \(e.message)") } catch e Error { printLn("Error: \(e.message)") } } }