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