| 1 | source readline.vim |
| 2 | source types.vim |
| 3 | source reader.vim |
| 4 | source printer.vim |
| 5 | source env.vim |
| 6 | source core.vim |
| 7 | |
| 8 | let MalExceptionObj = "" |
| 9 | |
| 10 | function READ(str) |
| 11 | return ReadStr(a:str) |
| 12 | endfunction |
| 13 | |
| 14 | function PairQ(obj) |
| 15 | return SequentialQ(a:obj) && !EmptyQ(a:obj) |
| 16 | endfunction |
| 17 | |
| 18 | function Quasiquote(ast) |
| 19 | if !PairQ(a:ast) |
| 20 | return ListNew([SymbolNew("quote"), a:ast]) |
| 21 | endif |
| 22 | let a0 = ListFirst(a:ast) |
| 23 | if SymbolQ(a0) && a0.val == "unquote" |
| 24 | return ListNth(a:ast, 1) |
| 25 | elseif PairQ(a0) && SymbolQ(ListFirst(a0)) && ListFirst(a0).val == "splice-unquote" |
| 26 | return ListNew([SymbolNew("concat"), ListNth(a0, 1), Quasiquote(ListRest(a:ast))]) |
| 27 | else |
| 28 | return ListNew([SymbolNew("cons"), Quasiquote(a0), Quasiquote(ListRest(a:ast))]) |
| 29 | end |
| 30 | endfunction |
| 31 | |
| 32 | function IsMacroCall(ast, env) |
| 33 | if !ListQ(a:ast) |
| 34 | return 0 |
| 35 | endif |
| 36 | let a0 = ListFirst(a:ast) |
| 37 | if !SymbolQ(a0) |
| 38 | return 0 |
| 39 | endif |
| 40 | let macroname = a0.val |
| 41 | if empty(a:env.find(macroname)) |
| 42 | return 0 |
| 43 | endif |
| 44 | return MacroQ(a:env.get(macroname)) |
| 45 | endfunction |
| 46 | |
| 47 | function MacroExpand(ast, env) |
| 48 | let ast = a:ast |
| 49 | while IsMacroCall(ast, a:env) |
| 50 | let macroobj = a:env.get(ListFirst(ast).val) |
| 51 | let macroargs = ListRest(ast) |
| 52 | let ast = FuncInvoke(macroobj, macroargs) |
| 53 | endwhile |
| 54 | return ast |
| 55 | endfunction |
| 56 | |
| 57 | function EvalAst(ast, env) |
| 58 | if SymbolQ(a:ast) |
| 59 | let varname = a:ast.val |
| 60 | return a:env.get(varname) |
| 61 | elseif ListQ(a:ast) |
| 62 | let ret = [] |
| 63 | for e in a:ast.val |
| 64 | call add(ret, EVAL(e, a:env)) |
| 65 | endfor |
| 66 | return ListNew(ret) |
| 67 | elseif VectorQ(a:ast) |
| 68 | let ret = [] |
| 69 | for e in a:ast.val |
| 70 | call add(ret, EVAL(e, a:env)) |
| 71 | endfor |
| 72 | return VectorNew(ret) |
| 73 | elseif HashQ(a:ast) |
| 74 | let ret = {} |
| 75 | for [k,v] in items(a:ast.val) |
| 76 | let keyobj = HashParseKey(k) |
| 77 | let newkey = EVAL(keyobj, a:env) |
| 78 | let newval = EVAL(v, a:env) |
| 79 | let keystring = HashMakeKey(newkey) |
| 80 | let ret[keystring] = newval |
| 81 | endfor |
| 82 | return HashNew(ret) |
| 83 | else |
| 84 | return a:ast |
| 85 | end |
| 86 | endfunction |
| 87 | |
| 88 | function GetCatchClause(ast) |
| 89 | if ListCount(a:ast) < 3 |
| 90 | return "" |
| 91 | end |
| 92 | let catch_clause = ListNth(a:ast, 2) |
| 93 | if ListFirst(catch_clause) == SymbolNew("catch*") |
| 94 | return catch_clause |
| 95 | else |
| 96 | return "" |
| 97 | end |
| 98 | endfunction |
| 99 | |
| 100 | function EVAL(ast, env) |
| 101 | let ast = a:ast |
| 102 | let env = a:env |
| 103 | |
| 104 | while 1 |
| 105 | if !ListQ(ast) |
| 106 | return EvalAst(ast, env) |
| 107 | end |
| 108 | |
| 109 | let ast = MacroExpand(ast, env) |
| 110 | if !ListQ(ast) |
| 111 | return EvalAst(ast, env) |
| 112 | end |
| 113 | if EmptyQ(ast) |
| 114 | return ast |
| 115 | endif |
| 116 | |
| 117 | let first = ListFirst(ast) |
| 118 | let first_symbol = SymbolQ(first) ? first.val : "" |
| 119 | if first_symbol == "def!" |
| 120 | let a1 = ast.val[1] |
| 121 | let a2 = ast.val[2] |
| 122 | return env.set(a1.val, EVAL(a2, env)) |
| 123 | elseif first_symbol == "let*" |
| 124 | let a1 = ast.val[1] |
| 125 | let a2 = ast.val[2] |
| 126 | let env = NewEnv(env) |
| 127 | let let_binds = a1.val |
| 128 | let i = 0 |
| 129 | while i < len(let_binds) |
| 130 | call env.set(let_binds[i].val, EVAL(let_binds[i+1], env)) |
| 131 | let i = i + 2 |
| 132 | endwhile |
| 133 | let ast = a2 |
| 134 | " TCO |
| 135 | elseif first_symbol == "quote" |
| 136 | return ListNth(ast, 1) |
| 137 | elseif first_symbol == "quasiquote" |
| 138 | let ast = Quasiquote(ListNth(ast, 1)) |
| 139 | " TCO |
| 140 | elseif first_symbol == "defmacro!" |
| 141 | let a1 = ListNth(ast, 1) |
| 142 | let a2 = ListNth(ast, 2) |
| 143 | let macro = MarkAsMacro(EVAL(a2, env)) |
| 144 | return env.set(a1.val, macro) |
| 145 | elseif first_symbol == "macroexpand" |
| 146 | return MacroExpand(ListNth(ast, 1), env) |
| 147 | elseif first_symbol == "if" |
| 148 | let condvalue = EVAL(ast.val[1], env) |
| 149 | if FalseQ(condvalue) || NilQ(condvalue) |
| 150 | if len(ast.val) < 4 |
| 151 | return g:MalNil |
| 152 | else |
| 153 | let ast = ast.val[3] |
| 154 | endif |
| 155 | else |
| 156 | let ast = ast.val[2] |
| 157 | endif |
| 158 | " TCO |
| 159 | elseif first_symbol == "try*" |
| 160 | try |
| 161 | return EVAL(ListNth(ast, 1), env) |
| 162 | catch |
| 163 | let catch_clause = GetCatchClause(ast) |
| 164 | if empty(catch_clause) |
| 165 | throw v:exception |
| 166 | endif |
| 167 | |
| 168 | let exc_var = ListNth(catch_clause, 1).val |
| 169 | if v:exception == "__MalException__" |
| 170 | let exc_value = g:MalExceptionObj |
| 171 | else |
| 172 | let exc_value = StringNew(v:exception) |
| 173 | endif |
| 174 | let catch_env = NewEnvWithBinds(env, ListNew([SymbolNew(exc_var)]), ListNew([exc_value])) |
| 175 | return EVAL(ListNth(catch_clause, 2), catch_env) |
| 176 | endtry |
| 177 | elseif first_symbol == "do" |
| 178 | let astlist = ast.val |
| 179 | call EvalAst(ListNew(astlist[1:-2]), env) |
| 180 | let ast = astlist[-1] |
| 181 | " TCO |
| 182 | elseif first_symbol == "fn*" |
| 183 | let fn = NewFn(ListNth(ast, 2), env, ListNth(ast, 1)) |
| 184 | return fn |
| 185 | elseif first_symbol == "eval" |
| 186 | let ast = EVAL(ListNth(ast, 1), env) |
| 187 | let env = env.root() |
| 188 | " TCO |
| 189 | else |
| 190 | " apply list |
| 191 | let el = EvalAst(ast, env) |
| 192 | let funcobj = ListFirst(el) |
| 193 | let args = ListRest(el) |
| 194 | if NativeFunctionQ(funcobj) |
| 195 | return NativeFuncInvoke(funcobj, args) |
| 196 | elseif FunctionQ(funcobj) |
| 197 | let fn = funcobj.val |
| 198 | let ast = fn.ast |
| 199 | let env = NewEnvWithBinds(fn.env, fn.params, args) |
| 200 | " TCO |
| 201 | else |
| 202 | throw "Not a function" |
| 203 | endif |
| 204 | endif |
| 205 | endwhile |
| 206 | endfunction |
| 207 | |
| 208 | function PRINT(exp) |
| 209 | return PrStr(a:exp, 1) |
| 210 | endfunction |
| 211 | |
| 212 | function RE(str, env) |
| 213 | return EVAL(READ(a:str), a:env) |
| 214 | endfunction |
| 215 | |
| 216 | function REP(str, env) |
| 217 | return PRINT(EVAL(READ(a:str), a:env)) |
| 218 | endfunction |
| 219 | |
| 220 | function GetArgvList() |
| 221 | return ListNew(map(copy(argv()[1:]), {_, arg -> StringNew(arg)})) |
| 222 | endfunction |
| 223 | |
| 224 | set maxfuncdepth=10000 |
| 225 | let repl_env = NewEnv("") |
| 226 | |
| 227 | for [k, v] in items(CoreNs) |
| 228 | call repl_env.set(k, v) |
| 229 | endfor |
| 230 | |
| 231 | call repl_env.set("*ARGV*", GetArgvList()) |
| 232 | |
| 233 | call RE("(def! *host-language* \"vimscript\")", repl_env) |
| 234 | call RE("(def! not (fn* (a) (if a false true)))", repl_env) |
| 235 | call RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env) |
| 236 | call 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)))))))", repl_env) |
| 237 | call RE("(def! *gensym-counter* (atom 0))", repl_env) |
| 238 | call RE("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))", repl_env) |
| 239 | call 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)))))))))", repl_env) |
| 240 | |
| 241 | if !empty(argv()) |
| 242 | try |
| 243 | call RE('(load-file "' . argv(0) . '")', repl_env) |
| 244 | catch |
| 245 | call PrintLn("Error: " . v:exception) |
| 246 | endtry |
| 247 | qall! |
| 248 | endif |
| 249 | |
| 250 | call REP("(println (str \"Mal [\" *host-language* \"]\"))", repl_env) |
| 251 | |
| 252 | while 1 |
| 253 | let [eof, line] = Readline("user> ") |
| 254 | if eof |
| 255 | break |
| 256 | endif |
| 257 | if line == "" |
| 258 | continue |
| 259 | endif |
| 260 | try |
| 261 | call PrintLn(REP(line, repl_env)) |
| 262 | catch |
| 263 | call PrintLn("Error: " . v:exception) |
| 264 | endtry |
| 265 | endwhile |
| 266 | qall! |