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