Skip to content

Commit 8624da0

Browse files
cookrnkanaka
authored andcommitted
ruby.2 step 7
1 parent 516e56a commit 8624da0

File tree

2 files changed

+292
-0
lines changed

2 files changed

+292
-0
lines changed

impls/ruby.2/core.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,38 @@ def ns
225225
Types::Symbol.for("swap!") => Types::Builtin.new do |mal|
226226
atom, fn, *args = mal
227227
atom.value = fn.call(Types::List.new([atom.value, *args]))
228+
end,
229+
230+
Types::Symbol.for("cons") => Types::Builtin.new do |mal|
231+
val, list_or_vector = mal
232+
Types::List.new([val, *list_or_vector])
233+
end,
234+
235+
Types::Symbol.for("concat") => Types::Builtin.new do |mal|
236+
list = Types::List.new
237+
238+
mal.each do |l|
239+
list.concat(l)
240+
end
241+
242+
list
243+
end,
244+
245+
Types::Symbol.for("vec") => Types::Builtin.new do |mal|
246+
case mal.first
247+
when Types::List
248+
vec = Types::Vector.new
249+
250+
mal.first.each do |m|
251+
vec << m
252+
end
253+
254+
vec
255+
when Types::Vector
256+
mal.first
257+
else
258+
raise TypeError
259+
end
228260
end
229261
}
230262
end

impls/ruby.2/step7_quote.rb

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
require "readline"
2+
3+
require_relative "core"
4+
require_relative "env"
5+
require_relative "errors"
6+
require_relative "printer"
7+
require_relative "reader"
8+
9+
module Mal
10+
extend self
11+
12+
def boot_repl!
13+
@repl_env = Env.new
14+
15+
Core.ns.each do |k, v|
16+
@repl_env.set(k, v)
17+
end
18+
19+
@repl_env.set(
20+
Types::Symbol.for("eval"),
21+
22+
Types::Builtin.new do |mal|
23+
Mal.EVAL(mal.first, @repl_env)
24+
end
25+
)
26+
27+
Mal.rep("(def! not (fn* (a) (if a false true)))")
28+
Mal.rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))")
29+
Mal.rep("(def! *ARGV* (list))") if !run_application?
30+
end
31+
32+
def run_application?
33+
ARGV.any?
34+
end
35+
36+
def run!
37+
Mal.rep("(def! *ARGV* (list #{ARGV[1..].map(&:inspect).join(" ")}))")
38+
Mal.rep("(load-file #{ARGV.first.inspect})")
39+
end
40+
41+
def READ(input)
42+
read_str(input)
43+
end
44+
45+
def EVAL(ast, environment)
46+
loop do
47+
if Types::List === ast && ast.size > 0
48+
case ast.first
49+
when Types::Symbol.for("def!")
50+
_, sym, val = ast
51+
return environment.set(sym, EVAL(val, environment))
52+
when Types::Symbol.for("let*")
53+
e = Env.new(environment)
54+
_, bindings, val = ast
55+
56+
unless Types::List === bindings || Types::Vector === bindings
57+
raise InvalidLetBindingsError
58+
end
59+
60+
until bindings.empty?
61+
k, v = bindings.shift(2)
62+
63+
raise InvalidLetBindingsError if k.nil?
64+
v = Types::Nil.instance if v.nil?
65+
66+
e.set(k, EVAL(v, e))
67+
end
68+
69+
if !val.nil?
70+
# Continue loop
71+
ast = val
72+
environment = e
73+
else
74+
return Types::Nil.instance
75+
end
76+
when Types::Symbol.for("do")
77+
_, *values = ast
78+
79+
if !values.nil? && values.any?
80+
values[0...-1].each do |v|
81+
EVAL(v, environment)
82+
end
83+
84+
# Continue loop
85+
ast = values.last
86+
else
87+
return Types::Nil.instance
88+
end
89+
when Types::Symbol.for("if")
90+
_, condition, when_true, when_false = ast
91+
92+
case EVAL(condition, environment)
93+
when Types::False.instance, Types::Nil.instance
94+
if !when_false.nil?
95+
# Continue loop
96+
ast = when_false
97+
else
98+
return Types::Nil.instance
99+
end
100+
else
101+
if !when_true.nil?
102+
# Continue loop
103+
ast = when_true
104+
else
105+
raise InvalidIfExpressionError
106+
end
107+
end
108+
when Types::Symbol.for("fn*")
109+
_, binds, to_eval = ast
110+
111+
return Types::Function.new(to_eval, binds, environment) do |exprs|
112+
exprs =
113+
if exprs.is_a?(Types::List)
114+
exprs
115+
else
116+
Types::List.new([*exprs])
117+
end
118+
119+
EVAL(to_eval, Env.new(environment, binds, exprs))
120+
end
121+
when Types::Symbol.for("quote")
122+
_, ret = ast
123+
return ret
124+
when Types::Symbol.for("quasiquote")
125+
_, ast_rest = ast
126+
ast = quasiquote(ast_rest)
127+
when Types::Symbol.for("quasiquoteexpand")
128+
_, ast_rest = ast
129+
return quasiquote(ast_rest)
130+
else
131+
evaluated = eval_ast(ast, environment)
132+
maybe_callable = evaluated.first
133+
134+
if maybe_callable.respond_to?(:call) && maybe_callable.is_mal_fn?
135+
# Continue loop
136+
ast = maybe_callable.ast
137+
environment = Env.new(
138+
maybe_callable.env,
139+
maybe_callable.params,
140+
evaluated[1..],
141+
)
142+
elsif maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn?
143+
return maybe_callable.call(evaluated[1..])
144+
else
145+
raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable."
146+
end
147+
end
148+
elsif Types::List === ast && ast.size == 0
149+
return ast
150+
else
151+
return eval_ast(ast, environment)
152+
end
153+
end
154+
end
155+
156+
def PRINT(input)
157+
pr_str(input, true)
158+
end
159+
160+
def rep(input)
161+
PRINT(EVAL(READ(input), @repl_env))
162+
rescue InvalidHashmapKeyError => e
163+
"Error! Hashmap keys can only be strings or keywords."
164+
rescue NotCallableError => e
165+
e.message
166+
rescue SymbolNotFoundError => e
167+
e.message
168+
rescue UnbalancedEscapingError => e
169+
"Error! Detected unbalanced escaping. Check for matching '\\'."
170+
rescue UnbalancedHashmapError => e
171+
"Error! Detected unbalanced list. Check for matching '}'."
172+
rescue UnbalancedListError => e
173+
"Error! Detected unbalanced list. Check for matching ')'."
174+
rescue UnbalancedStringError => e
175+
"Error! Detected unbalanced string. Check for matching '\"'."
176+
rescue UnbalancedVectorError => e
177+
"Error! Detected unbalanced list. Check for matching ']'."
178+
rescue SkipCommentError
179+
nil
180+
end
181+
182+
def eval_ast(mal, environment)
183+
case mal
184+
when Types::Symbol
185+
environment.get(mal)
186+
when Types::List
187+
list = Types::List.new
188+
mal.each { |i| list << EVAL(i, environment) }
189+
list
190+
when Types::Vector
191+
vec = Types::Vector.new
192+
mal.each { |i| vec << EVAL(i, environment) }
193+
vec
194+
when Types::Hashmap
195+
hashmap = Types::Hashmap.new
196+
mal.each { |k, v| hashmap[k] = EVAL(v, environment) }
197+
hashmap
198+
else
199+
mal
200+
end
201+
end
202+
203+
def quasiquote_list(mal)
204+
result = Types::List.new
205+
206+
mal.reverse_each do |elt|
207+
if elt.is_a?(Types::List) && elt.first == Types::Symbol.for("splice-unquote")
208+
result = Types::List.new([
209+
Types::Symbol.for("concat"),
210+
elt[1],
211+
result
212+
])
213+
else
214+
result = Types::List.new([
215+
Types::Symbol.for("cons"),
216+
quasiquote(elt),
217+
result
218+
])
219+
end
220+
end
221+
222+
result
223+
end
224+
225+
def quasiquote(mal)
226+
case mal
227+
when Types::List
228+
if mal.first == Types::Symbol.for("unquote")
229+
mal[1]
230+
else
231+
quasiquote_list(mal)
232+
end
233+
when Types::Vector
234+
Types::List.new([
235+
Types::Symbol.for("vec"),
236+
quasiquote_list(mal)
237+
])
238+
when Types::Hashmap, Types::Symbol
239+
Types::List.new([
240+
Types::Symbol.for("quote"),
241+
mal
242+
])
243+
else
244+
mal
245+
end
246+
end
247+
end
248+
249+
Mal.boot_repl!
250+
251+
if Mal.run_application?
252+
Mal.run!
253+
else
254+
while input = Readline.readline("user> ")
255+
val = Mal.rep(input)
256+
puts val unless val.nil?
257+
end
258+
259+
puts
260+
end

0 commit comments

Comments
 (0)