forked from kanaka/mal
-
Notifications
You must be signed in to change notification settings - Fork 0
/
step7_quote.rb
136 lines (121 loc) · 3.07 KB
/
step7_quote.rb
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
require_relative "mal_readline"
require_relative "types"
require_relative "reader"
require_relative "printer"
require_relative "env"
require_relative "core"
# read
def READ(str)
return read_str(str)
end
# eval
def pair?(x)
return sequential?(x) && x.size > 0
end
def quasiquote(ast)
if not pair?(ast)
return List.new [:quote, ast]
elsif ast[0] == :unquote
return ast[1]
elsif pair?(ast[0]) && ast[0][0] == :"splice-unquote"
return List.new [:concat, ast[0][1], quasiquote(ast.drop(1))]
else
return List.new [:cons, quasiquote(ast[0]), quasiquote(ast.drop(1))]
end
end
def eval_ast(ast, env)
return case ast
when Symbol
env.get(ast)
when List
List.new ast.map{|a| EVAL(a, env)}
when Vector
Vector.new ast.map{|a| EVAL(a, env)}
when Hash
new_hm = {}
ast.each{|k,v| new_hm[EVAL(k,env)] = EVAL(v, env)}
new_hm
else
ast
end
end
def EVAL(ast, env)
while true
#puts "EVAL: #{_pr_str(ast, true)}"
if not ast.is_a? List
return eval_ast(ast, env)
end
if ast.empty?
return ast
end
# apply list
a0,a1,a2,a3 = ast
case a0
when :def!
return env.set(a1, EVAL(a2, env))
when :"let*"
let_env = Env.new(env)
a1.each_slice(2) do |a,e|
let_env.set(a, EVAL(e, let_env))
end
env = let_env
ast = a2 # Continue loop (TCO)
when :quote
return a1
when :quasiquote
ast = quasiquote(a1); # Continue loop (TCO)
when :do
eval_ast(ast[1..-2], env)
ast = ast.last # Continue loop (TCO)
when :if
cond = EVAL(a1, env)
if not cond
return nil if a3 == nil
ast = a3 # Continue loop (TCO)
else
ast = a2 # Continue loop (TCO)
end
when :"fn*"
return Function.new(a2, env, a1) {|*args|
EVAL(a2, Env.new(env, a1, List.new(args)))
}
else
el = eval_ast(ast, env)
f = el[0]
if f.class == Function
ast = f.ast
env = f.gen_env(el.drop(1)) # Continue loop (TCO)
else
return f[*el.drop(1)]
end
end
end
end
# print
def PRINT(exp)
return _pr_str(exp, true)
end
# repl
repl_env = Env.new
RE = lambda {|str| EVAL(READ(str), repl_env) }
REP = lambda {|str| PRINT(EVAL(READ(str), repl_env)) }
# core.rb: defined using ruby
$core_ns.each do |k,v| repl_env.set(k,v) end
repl_env.set(:eval, lambda {|ast| EVAL(ast, repl_env)})
repl_env.set(:"*ARGV*", List.new(ARGV.slice(1,ARGV.length) || []))
# core.mal: defined using the language itself
RE["(def! not (fn* (a) (if a false true)))"]
RE["(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"]
if ARGV.size > 0
RE["(load-file \"" + ARGV[0] + "\")"]
exit 0
end
# repl loop
while line = _readline("user> ")
begin
puts REP[line]
rescue Exception => e
puts "Error: #{e}"
puts "\t#{e.backtrace.join("\n\t")}"
end
end