| 1 | import sys |
| 2 | IS_RPYTHON = sys.argv[0].endswith('rpython') |
| 3 | |
| 4 | if IS_RPYTHON: |
| 5 | #from rpython.rlib.debug import fatalerror |
| 6 | from rpython.rtyper.lltypesystem import lltype |
| 7 | from rpython.rtyper.lltypesystem.lloperation import llop |
| 8 | else: |
| 9 | import traceback |
| 10 | |
| 11 | import mal_readline |
| 12 | import mal_types as types |
| 13 | from mal_types import (MalSym, MalInt, MalStr, |
| 14 | nil, true, false, _symbol, _keywordu, |
| 15 | MalList, _list, MalVector, MalHashMap, MalFunc) |
| 16 | import reader, printer |
| 17 | from env import Env |
| 18 | import core |
| 19 | |
| 20 | # read |
| 21 | def READ(str): |
| 22 | return reader.read_str(str) |
| 23 | |
| 24 | # eval |
| 25 | def qq_loop(elt, acc): |
| 26 | if types._list_Q(elt) and len(elt) == 2: |
| 27 | fst = elt[0] |
| 28 | if isinstance(fst, MalSym) and fst.value == u"splice-unquote": |
| 29 | return _list(_symbol(u"concat"), elt[1], acc) |
| 30 | return _list(_symbol(u"cons"), quasiquote(elt), acc) |
| 31 | |
| 32 | def qq_foldr(seq): |
| 33 | acc = _list() |
| 34 | for elt in reversed(seq): |
| 35 | acc = qq_loop (elt, acc) |
| 36 | return acc |
| 37 | |
| 38 | def quasiquote(ast): |
| 39 | if types._list_Q(ast): |
| 40 | if len(ast) == 2: |
| 41 | fst = ast[0] |
| 42 | if isinstance(fst, MalSym) and fst.value == u"unquote": |
| 43 | return ast[1] |
| 44 | return qq_foldr(ast.values) |
| 45 | elif types._vector_Q(ast): |
| 46 | return _list(_symbol(u"vec"), qq_foldr(ast.values)) |
| 47 | elif types._symbol_Q(ast) or types._hash_map_Q(ast): |
| 48 | return _list(_symbol(u"quote"), ast) |
| 49 | else: |
| 50 | return ast |
| 51 | |
| 52 | def is_macro_call(ast, env): |
| 53 | if types._list_Q(ast): |
| 54 | a0 = ast[0] |
| 55 | if isinstance(a0, MalSym): |
| 56 | if not env.find(a0) is None: |
| 57 | return env.get(a0).ismacro |
| 58 | return False |
| 59 | |
| 60 | def macroexpand(ast, env): |
| 61 | while is_macro_call(ast, env): |
| 62 | assert isinstance(ast[0], MalSym) |
| 63 | mac = env.get(ast[0]) |
| 64 | ast = macroexpand(mac.apply(ast.rest()), env) |
| 65 | return ast |
| 66 | |
| 67 | def eval_ast(ast, env): |
| 68 | if types._symbol_Q(ast): |
| 69 | assert isinstance(ast, MalSym) |
| 70 | return env.get(ast) |
| 71 | elif types._list_Q(ast): |
| 72 | res = [] |
| 73 | for a in ast.values: |
| 74 | res.append(EVAL(a, env)) |
| 75 | return MalList(res) |
| 76 | elif types._vector_Q(ast): |
| 77 | res = [] |
| 78 | for a in ast.values: |
| 79 | res.append(EVAL(a, env)) |
| 80 | return MalVector(res) |
| 81 | elif types._hash_map_Q(ast): |
| 82 | new_dct = {} |
| 83 | for k in ast.dct.keys(): |
| 84 | new_dct[k] = EVAL(ast.dct[k], env) |
| 85 | return MalHashMap(new_dct) |
| 86 | else: |
| 87 | return ast # primitive value, return unchanged |
| 88 | |
| 89 | def EVAL(ast, env): |
| 90 | while True: |
| 91 | #print("EVAL %s" % printer._pr_str(ast)) |
| 92 | if not types._list_Q(ast): |
| 93 | return eval_ast(ast, env) |
| 94 | if len(ast) == 0: return ast |
| 95 | |
| 96 | # apply list |
| 97 | ast = macroexpand(ast, env) |
| 98 | if not types._list_Q(ast): |
| 99 | return eval_ast(ast, env) |
| 100 | if len(ast) == 0: return ast |
| 101 | a0 = ast[0] |
| 102 | if isinstance(a0, MalSym): |
| 103 | a0sym = a0.value |
| 104 | else: |
| 105 | a0sym = u"__<*fn*>__" |
| 106 | |
| 107 | if u"def!" == a0sym: |
| 108 | a1, a2 = ast[1], ast[2] |
| 109 | res = EVAL(a2, env) |
| 110 | return env.set(a1, res) |
| 111 | elif u"let*" == a0sym: |
| 112 | a1, a2 = ast[1], ast[2] |
| 113 | let_env = Env(env) |
| 114 | for i in range(0, len(a1), 2): |
| 115 | let_env.set(a1[i], EVAL(a1[i+1], let_env)) |
| 116 | ast = a2 |
| 117 | env = let_env # Continue loop (TCO) |
| 118 | elif u"quote" == a0sym: |
| 119 | return ast[1] |
| 120 | elif u"quasiquoteexpand" == a0sym: |
| 121 | return quasiquote(ast[1]) |
| 122 | elif u"quasiquote" == a0sym: |
| 123 | ast = quasiquote(ast[1]) # Continue loop (TCO) |
| 124 | elif u"defmacro!" == a0sym: |
| 125 | func = EVAL(ast[2], env) |
| 126 | func.ismacro = True |
| 127 | return env.set(ast[1], func) |
| 128 | elif u"macroexpand" == a0sym: |
| 129 | return macroexpand(ast[1], env) |
| 130 | elif u"try*" == a0sym: |
| 131 | if len(ast) < 3: |
| 132 | return EVAL(ast[1], env); |
| 133 | a1, a2 = ast[1], ast[2] |
| 134 | a20 = a2[0] |
| 135 | if isinstance(a20, MalSym): |
| 136 | if a20.value == u"catch*": |
| 137 | try: |
| 138 | return EVAL(a1, env); |
| 139 | except types.MalException as exc: |
| 140 | exc = exc.object |
| 141 | catch_env = Env(env, _list(a2[1]), _list(exc)) |
| 142 | return EVAL(a2[2], catch_env) |
| 143 | except Exception as exc: |
| 144 | exc = MalStr(unicode("%s" % exc)) |
| 145 | catch_env = Env(env, _list(a2[1]), _list(exc)) |
| 146 | return EVAL(a2[2], catch_env) |
| 147 | return EVAL(a1, env); |
| 148 | elif u"do" == a0sym: |
| 149 | if len(ast) == 0: |
| 150 | return nil |
| 151 | elif len(ast) > 1: |
| 152 | eval_ast(ast.slice2(1, len(ast)-1), env) |
| 153 | ast = ast[-1] # Continue loop (TCO) |
| 154 | elif u"if" == a0sym: |
| 155 | a1, a2 = ast[1], ast[2] |
| 156 | cond = EVAL(a1, env) |
| 157 | if cond is nil or cond is false: |
| 158 | if len(ast) > 3: ast = ast[3] # Continue loop (TCO) |
| 159 | else: return nil |
| 160 | else: |
| 161 | ast = a2 # Continue loop (TCO) |
| 162 | elif u"fn*" == a0sym: |
| 163 | a1, a2 = ast[1], ast[2] |
| 164 | return MalFunc(None, a2, env, a1, EVAL) |
| 165 | else: |
| 166 | el = eval_ast(ast, env) |
| 167 | f = el.values[0] |
| 168 | if isinstance(f, MalFunc): |
| 169 | if f.ast: |
| 170 | ast = f.ast |
| 171 | env = f.gen_env(el.rest()) # Continue loop (TCO) |
| 172 | else: |
| 173 | return f.apply(el.rest()) |
| 174 | else: |
| 175 | raise Exception("%s is not callable" % f) |
| 176 | |
| 177 | # print |
| 178 | def PRINT(exp): |
| 179 | return printer._pr_str(exp) |
| 180 | |
| 181 | # repl |
| 182 | class MalEval(MalFunc): |
| 183 | def apply(self, args): |
| 184 | return self.EvalFunc(args[0], self.env) |
| 185 | |
| 186 | def entry_point(argv): |
| 187 | repl_env = Env() |
| 188 | def REP(str, env): |
| 189 | return PRINT(EVAL(READ(str), env)) |
| 190 | |
| 191 | # core.py: defined using python |
| 192 | for k, v in core.ns.items(): |
| 193 | repl_env.set(_symbol(unicode(k)), MalFunc(v)) |
| 194 | repl_env.set(types._symbol(u'eval'), |
| 195 | MalEval(None, env=repl_env, EvalFunc=EVAL)) |
| 196 | mal_args = [] |
| 197 | if len(argv) >= 3: |
| 198 | for a in argv[2:]: mal_args.append(MalStr(unicode(a))) |
| 199 | repl_env.set(_symbol(u'*ARGV*'), MalList(mal_args)) |
| 200 | |
| 201 | # core.mal: defined using the language itself |
| 202 | REP("(def! *host-language* \"rpython\")", repl_env) |
| 203 | REP("(def! not (fn* (a) (if a false true)))", repl_env) |
| 204 | REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", repl_env) |
| 205 | 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)))))))", repl_env) |
| 206 | |
| 207 | if len(argv) >= 2: |
| 208 | REP('(load-file "' + argv[1] + '")', repl_env) |
| 209 | return 0 |
| 210 | |
| 211 | REP("(println (str \"Mal [\" *host-language* \"]\"))", repl_env) |
| 212 | while True: |
| 213 | try: |
| 214 | line = mal_readline.readline("user> ") |
| 215 | if line == "": continue |
| 216 | print(REP(line, repl_env)) |
| 217 | except EOFError as e: |
| 218 | break |
| 219 | except reader.Blank: |
| 220 | continue |
| 221 | except types.MalException as e: |
| 222 | print(u"Error: %s" % printer._pr_str(e.object, False)) |
| 223 | except Exception as e: |
| 224 | print("Error: %s" % e) |
| 225 | if IS_RPYTHON: |
| 226 | llop.debug_print_traceback(lltype.Void) |
| 227 | else: |
| 228 | print("".join(traceback.format_exception(*sys.exc_info()))) |
| 229 | return 0 |
| 230 | |
| 231 | # _____ Define and setup target ___ |
| 232 | def target(*args): |
| 233 | return entry_point |
| 234 | |
| 235 | # Just run entry_point if not RPython compilation |
| 236 | import sys |
| 237 | if not sys.argv[0].endswith('rpython'): |
| 238 | entry_point(sys.argv) |