| 1 | def READ(str string) MalVal { |
| 2 | return read_str(str) |
| 3 | } |
| 4 | |
| 5 | def isPair(a MalVal) bool { |
| 6 | return a is MalSequential && !(a as MalSequential).isEmpty |
| 7 | } |
| 8 | |
| 9 | def quasiquote(ast MalVal) MalVal { |
| 10 | if !isPair(ast) { |
| 11 | return MalList.new([MalSymbol.new("quote"), ast]) |
| 12 | } |
| 13 | const astSeq = ast as MalSequential |
| 14 | const a0 = astSeq[0] |
| 15 | if a0.isSymbol("unquote") { |
| 16 | return astSeq[1] |
| 17 | } |
| 18 | if isPair(a0) { |
| 19 | const a0Seq = a0 as MalSequential |
| 20 | if a0Seq[0].isSymbol("splice-unquote") { |
| 21 | return MalList.new([MalSymbol.new("concat"), a0Seq[1], quasiquote(astSeq.rest)]) |
| 22 | } |
| 23 | } |
| 24 | return MalList.new([MalSymbol.new("cons"), quasiquote(a0), quasiquote(astSeq.rest)]) |
| 25 | } |
| 26 | |
| 27 | def isMacro(ast MalVal, env Env) bool { |
| 28 | if !(ast is MalList) { return false } |
| 29 | const astList = ast as MalList |
| 30 | if astList.isEmpty { return false } |
| 31 | const a0 = astList[0] |
| 32 | if !(a0 is MalSymbol) { return false } |
| 33 | const a0Sym = a0 as MalSymbol |
| 34 | if env.find(a0Sym) == null { return false } |
| 35 | const f = env.get(a0Sym) |
| 36 | if !(f is MalFunc) { return false } |
| 37 | return (f as MalFunc).isMacro |
| 38 | } |
| 39 | |
| 40 | def macroexpand(ast MalVal, env Env) MalVal { |
| 41 | while isMacro(ast, env) { |
| 42 | const astList = ast as MalList |
| 43 | const mac = env.get(astList[0] as MalSymbol) as MalFunc |
| 44 | ast = mac.call((astList.rest as MalSequential).val) |
| 45 | } |
| 46 | return ast |
| 47 | } |
| 48 | |
| 49 | def eval_ast(ast MalVal, env Env) MalVal { |
| 50 | if ast is MalSymbol { |
| 51 | return env.get(ast as MalSymbol) |
| 52 | } else if ast is MalList { |
| 53 | return MalList.new((ast as MalList).val.map<MalVal>(e => EVAL(e, env))) |
| 54 | } else if ast is MalVector { |
| 55 | return MalVector.new((ast as MalVector).val.map<MalVal>(e => EVAL(e, env))) |
| 56 | } else if ast is MalHashMap { |
| 57 | var result List<MalVal> = [] |
| 58 | (ast as MalHashMap).val.each((k string, v MalVal) => { |
| 59 | result.append(EVAL(MalVal.fromHashKey(k), env)) |
| 60 | result.append(EVAL(v, env)) |
| 61 | }) |
| 62 | return MalHashMap.fromList(result) |
| 63 | } else { |
| 64 | return ast |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | def EVAL(ast MalVal, env Env) MalVal { |
| 69 | while true { |
| 70 | if !(ast is MalList) { return eval_ast(ast, env) } |
| 71 | ast = macroexpand(ast, env) |
| 72 | if !(ast is MalList) { return eval_ast(ast, env) } |
| 73 | const astList = ast as MalList |
| 74 | if astList.isEmpty { return ast } |
| 75 | const a0sym = astList[0] as MalSymbol |
| 76 | if a0sym.val == "def!" { |
| 77 | return env.set(astList[1] as MalSymbol, EVAL(astList[2], env)) |
| 78 | } else if a0sym.val == "let*" { |
| 79 | var letenv = Env.new(env) |
| 80 | const assigns = astList[1] as MalSequential |
| 81 | for i = 0; i < assigns.count; i += 2 { |
| 82 | letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv)) |
| 83 | } |
| 84 | ast = astList[2] |
| 85 | env = letenv |
| 86 | continue # TCO |
| 87 | } else if a0sym.val == "quote" { |
| 88 | return astList[1] |
| 89 | } else if a0sym.val == "quasiquote" { |
| 90 | ast = quasiquote(astList[1]) |
| 91 | continue # TCO |
| 92 | } else if a0sym.val == "defmacro!" { |
| 93 | var macro = EVAL(astList[2], env) as MalFunc |
| 94 | macro.setAsMacro |
| 95 | return env.set(astList[1] as MalSymbol, macro) |
| 96 | } else if a0sym.val == "macroexpand" { |
| 97 | return macroexpand(astList[1], env) |
| 98 | } else if a0sym.val == "try*" { |
| 99 | var exc MalVal |
| 100 | try { |
| 101 | return EVAL(astList[1], env) |
| 102 | } |
| 103 | catch e MalUserError { exc = e.data } |
| 104 | catch e MalError { exc = MalString.new(e.message) } |
| 105 | catch e Error { exc = MalString.new(e.message) } |
| 106 | const catchClause = astList[2] as MalList |
| 107 | var catchEnv = Env.new(env, [catchClause[1] as MalSymbol], [exc]) |
| 108 | return EVAL(catchClause[2], catchEnv) |
| 109 | } else if a0sym.val == "do" { |
| 110 | const parts = astList.val.slice(1) |
| 111 | eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env) |
| 112 | ast = parts[parts.count - 1] |
| 113 | continue # TCO |
| 114 | } else if a0sym.val == "if" { |
| 115 | const condRes = EVAL(astList[1], env) |
| 116 | if condRes is MalNil || condRes is MalFalse { |
| 117 | ast = astList.count > 3 ? astList[3] : gNil |
| 118 | } else { |
| 119 | ast = astList[2] |
| 120 | } |
| 121 | continue # TCO |
| 122 | } else if a0sym.val == "fn*" { |
| 123 | const argsNames = astList[1] as MalSequential |
| 124 | return MalFunc.new(astList[2], argsNames, env, (args List<MalVal>) => EVAL(astList[2], Env.new(env, argsNames.val, args))) |
| 125 | } else { |
| 126 | const evaledList = eval_ast(ast, env) as MalList |
| 127 | const fn = evaledList[0] |
| 128 | const callArgs = evaledList.val.slice(1) |
| 129 | if fn is MalNativeFunc { |
| 130 | return (fn as MalNativeFunc).call(callArgs) |
| 131 | } else if fn is MalFunc { |
| 132 | const f = fn as MalFunc |
| 133 | ast = f.ast |
| 134 | env = Env.new(f.env, f.params.val, callArgs) |
| 135 | continue # TCO |
| 136 | } else { |
| 137 | throw MalError.new("Expected function as head of list") |
| 138 | } |
| 139 | } |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | def PRINT(exp MalVal) string { |
| 144 | return exp?.print(true) |
| 145 | } |
| 146 | |
| 147 | var repl_env = Env.new(null) |
| 148 | |
| 149 | def RE(str string) MalVal { |
| 150 | return EVAL(READ(str), repl_env) |
| 151 | } |
| 152 | |
| 153 | def REP(str string) string { |
| 154 | return PRINT(RE(str)) |
| 155 | } |
| 156 | |
| 157 | @entry |
| 158 | def main { |
| 159 | # core.sk: defined using Skew |
| 160 | ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func))) |
| 161 | repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map<MalVal>(e => MalString.new(e)))) |
| 162 | |
| 163 | # core.mal: defined using the language itself |
| 164 | RE("(def! not (fn* (a) (if a false true)))") |
| 165 | RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") |
| 166 | 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)))))))") |
| 167 | RE("(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))))))))") |
| 168 | |
| 169 | if argv.count > 0 { |
| 170 | RE("(load-file \"" + argv[0] + "\")") |
| 171 | return |
| 172 | } |
| 173 | |
| 174 | var line string |
| 175 | while (line = readLine("user> ")) != null { |
| 176 | if line == "" { continue } |
| 177 | try { |
| 178 | printLn(REP(line)) |
| 179 | } |
| 180 | catch e MalUserError { |
| 181 | printLn("Error: \(e.data.print(false))") |
| 182 | } |
| 183 | catch e MalError { |
| 184 | printLn("Error: \(e.message)") |
| 185 | } |
| 186 | catch e Error { |
| 187 | printLn("Error: \(e.message)") |
| 188 | } |
| 189 | } |
| 190 | } |