Implement step 7
[jackhill/mal.git] / julia / stepA_mal.jl
CommitLineData
85110962
JM
1#!/usr/bin/env julia
2
82484631 3push!(LOAD_PATH, pwd(), "/usr/share/julia/base")
85110962
JM
4import readline_mod
5import reader
6import printer
7using env
8import core
9using types
10
11# READ
12function READ(str)
13 reader.read_str(str)
14end
15
16# EVAL
17function ispair(ast)
18 (isa(ast, Array) || isa(ast, Tuple)) && length(ast) > 0
19end
20
21function 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
31end
32
33function 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
39end
40
41function 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
47end
48
49function 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
59end
60
61function 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
142end
143
144# PRINT
145function PRINT(exp)
146 printer.pr_str(exp)
147end
148
149# REPL
150repl_env = nothing
151function REP(str)
152 return PRINT(EVAL(READ(str), repl_env))
153end
154
155# core.jl: defined using Julia
156repl_env = Env(nothing, core.ns)
82484631
JM
157env_set(repl_env, :eval, (ast) -> EVAL(ast, repl_env))
158env_set(repl_env, symbol("*ARGV*"), ARGS[2:end])
85110962
JM
159
160# core.mal: defined using the language itself
161REP("(def! *host-language* \"julia\")")
162REP("(def! not (fn* (a) (if a false true)))")
163REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
164REP("(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
165REP("(def! *gensym-counter* (atom 0))")
166REP("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))")
167REP("(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
170if length(ARGS) > 0
171 REP("(load-file \"$(ARGS[1])\")")
172 exit(0)
173end
174
175REP("(println (str \"Mal [\" *host-language* \"]\"))")
176while 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
194end