Commit | Line | Data |
---|---|---|
85110962 JM |
1 | #!/usr/bin/env julia |
2 | ||
82484631 | 3 | push!(LOAD_PATH, pwd(), "/usr/share/julia/base") |
85110962 JM |
4 | import readline_mod |
5 | import reader | |
6 | import printer | |
7 | using env | |
8 | import core | |
9 | using types | |
10 | ||
11 | # READ | |
12 | function READ(str) | |
13 | reader.read_str(str) | |
14 | end | |
15 | ||
16 | # EVAL | |
17 | function ispair(ast) | |
18 | (isa(ast, Array) || isa(ast, Tuple)) && length(ast) > 0 | |
19 | end | |
20 | ||
21 | function quasiquote(ast) | |
22 | if !ispair(ast) | |
82484631 | 23 | [[:quote]; Any[ast]] |
85110962 JM |
24 | elseif ast[1] == :unquote |
25 | ast[2] | |
26 | elseif ispair(ast[1]) && ast[1][1] == symbol("splice-unquote") | |
82484631 | 27 | [[:concat]; Any[ast[1][2]]; Any[quasiquote(ast[2:end])]] |
85110962 | 28 | else |
82484631 | 29 | [[:cons]; Any[quasiquote(ast[1])]; Any[quasiquote(ast[2:end])]] |
85110962 JM |
30 | end |
31 | end | |
32 | ||
33 | function ismacroCall(ast, env) | |
34 | return isa(ast, Array) && | |
35 | isa(ast[1], Symbol) && | |
82484631 JM |
36 | env_find(env, ast[1]) != nothing && |
37 | isa(env_get(env, ast[1]), MalFunc) && | |
38 | env_get(env, ast[1]).ismacro | |
85110962 JM |
39 | end |
40 | ||
41 | function macroexpand(ast, env) | |
42 | while ismacroCall(ast, env) | |
82484631 | 43 | mac = env_get(env, ast[1]) |
85110962 JM |
44 | ast = mac.fn(ast[2:end]...) |
45 | end | |
46 | ast | |
47 | end | |
48 | ||
49 | function eval_ast(ast, env) | |
50 | if typeof(ast) == Symbol | |
82484631 | 51 | env_get(env,ast) |
85110962 JM |
52 | elseif isa(ast, Array) || isa(ast, Tuple) |
53 | map((x) -> EVAL(x,env), ast) | |
7e0bb668 JM |
54 | elseif isa(ast, Dict) |
55 | [EVAL(x[1],env) => EVAL(x[2], env) for x=ast] | |
85110962 JM |
56 | else |
57 | ast | |
58 | end | |
59 | end | |
60 | ||
61 | function EVAL(ast, env) | |
62 | while true | |
63 | #println("EVAL: $(printer.pr_str(ast,true))") | |
64 | if !isa(ast, Array) return eval_ast(ast, env) end | |
65 | ||
66 | # apply | |
67 | ast = macroexpand(ast, env) | |
d5b81cc0 | 68 | if !isa(ast, Array) return eval_ast(ast, env) end |
85110962 JM |
69 | |
70 | if :def! == ast[1] | |
82484631 | 71 | return env_set(env, ast[2], EVAL(ast[3], env)) |
85110962 JM |
72 | elseif symbol("let*") == ast[1] |
73 | let_env = Env(env) | |
74 | for i = 1:2:length(ast[2]) | |
82484631 | 75 | env_set(let_env, ast[2][i], EVAL(ast[2][i+1], let_env)) |
85110962 JM |
76 | end |
77 | env = let_env | |
78 | ast = ast[3] | |
79 | # TCO loop | |
80 | elseif :quote == ast[1] | |
81 | return ast[2] | |
82 | elseif :quasiquote == ast[1] | |
83 | ast = quasiquote(ast[2]) | |
84 | # TCO loop | |
85 | elseif :defmacro! == ast[1] | |
86 | func = EVAL(ast[3], env) | |
87 | func.ismacro = true | |
82484631 | 88 | return env_set(env, ast[2], func) |
85110962 JM |
89 | elseif :macroexpand == ast[1] |
90 | return macroexpand(ast[2], env) | |
91 | elseif symbol("try*") == ast[1] | |
92 | try | |
93 | return EVAL(ast[2], env) | |
94 | catch exc | |
95 | e = string(exc) | |
96 | if isa(exc, MalException) | |
97 | e = exc.malval | |
98 | elseif isa(exc, ErrorException) | |
99 | e = exc.msg | |
100 | else | |
101 | e = string(e) | |
102 | end | |
103 | if length(ast) > 2 && ast[3][1] == symbol("catch*") | |
82484631 | 104 | return EVAL(ast[3][3], Env(env, Any[ast[3][2]], Any[e])) |
85110962 JM |
105 | else |
106 | rethrow(exc) | |
107 | end | |
108 | end | |
109 | elseif :do == ast[1] | |
110 | eval_ast(ast[2:end-1], env) | |
111 | ast = ast[end] | |
112 | # TCO loop | |
113 | elseif :if == ast[1] | |
114 | cond = EVAL(ast[2], env) | |
115 | if cond === nothing || cond === false | |
116 | if length(ast) >= 4 | |
117 | ast = ast[4] | |
118 | # TCO loop | |
119 | else | |
120 | return nothing | |
121 | end | |
122 | else | |
123 | ast = ast[3] | |
124 | # TCO loop | |
125 | end | |
126 | elseif symbol("fn*") == ast[1] | |
127 | return MalFunc( | |
f98e3ea9 | 128 | (args...) -> EVAL(ast[3], Env(env, ast[2], Any[args...])), |
85110962 JM |
129 | ast[3], env, ast[2]) |
130 | else | |
131 | el = eval_ast(ast, env) | |
132 | f, args = el[1], el[2:end] | |
133 | if isa(f, MalFunc) | |
134 | ast = f.ast | |
135 | env = Env(f.env, f.params, args) | |
136 | # TCO loop | |
137 | else | |
138 | return f(args...) | |
139 | end | |
140 | end | |
141 | end | |
142 | end | |
143 | ||
144 | ||
145 | function PRINT(exp) | |
146 | printer.pr_str(exp) | |
147 | end | |
148 | ||
149 | # REPL | |
150 | repl_env = nothing | |
151 | function REP(str) | |
152 | return PRINT(EVAL(READ(str), repl_env)) | |
153 | end | |
154 | ||
155 | # core.jl: defined using Julia | |
156 | repl_env = Env(nothing, core.ns) | |
82484631 JM |
157 | env_set(repl_env, :eval, (ast) -> EVAL(ast, repl_env)) |
158 | env_set(repl_env, symbol("*ARGV*"), ARGS[2:end]) | |
85110962 JM |
159 | |
160 | # core.mal: defined using the language itself | |
161 | REP("(def! *host-language* \"julia\")") | |
162 | REP("(def! not (fn* (a) (if a false true)))") | |
163 | REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") | |
164 | 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)))))))") | |
29ba1fb6 DM |
165 | REP("(def! *gensym-counter* (atom 0))") |
166 | REP("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") | |
167 | REP("(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)))))))))") | |
85110962 JM |
168 | |
169 | ||
170 | if length(ARGS) > 0 | |
171 | REP("(load-file \"$(ARGS[1])\")") | |
172 | exit(0) | |
173 | end | |
174 | ||
175 | REP("(println (str \"Mal [\" *host-language* \"]\"))") | |
176 | while true | |
177 | line = readline_mod.do_readline("user> ") | |
178 | if line === nothing break end | |
179 | try | |
180 | println(REP(line)) | |
181 | catch e | |
182 | if isa(e, ErrorException) | |
183 | println("Error: $(e.msg)") | |
184 | else | |
185 | println("Error: $(string(e))") | |
186 | end | |
82484631 JM |
187 | # TODO: show at least part of stack |
188 | if !isa(e, StackOverflowError) | |
189 | bt = catch_backtrace() | |
190 | Base.show_backtrace(STDERR, bt) | |
191 | end | |
85110962 JM |
192 | println() |
193 | end | |
194 | end |