Commit | Line | Data |
---|---|---|
6e7390b7 | 1 | require "time" |
2 | ||
5185c56e | 3 | require "readline" |
b31653d5 | 4 | require "./types" |
2c76c2ff | 5 | require "./error" |
51be5007 | 6 | require "./printer" |
d21ef941 | 7 | require "./reader" |
b31653d5 | 8 | |
9 | module Mal | |
5185c56e OR |
10 | macro calc_op(op) |
11 | -> (args : Array(Mal::Type)) { | |
12 | x, y = args[0].unwrap, args[1].unwrap | |
13 | eval_error "invalid arguments for binary operator {{op.id}}" unless x.is_a?(Int64) && y.is_a?(Int64) | |
14 | Mal::Type.new(x {{op.id}} y) | |
15 | } | |
16 | end | |
b31653d5 | 17 | |
5185c56e OR |
18 | def self.list(args) |
19 | args.to_mal | |
20 | end | |
b31653d5 | 21 | |
5185c56e OR |
22 | def self.list?(args) |
23 | args.first.unwrap.is_a? Mal::List | |
24 | end | |
b31653d5 | 25 | |
5185c56e OR |
26 | def self.empty?(args) |
27 | a = args.first.unwrap | |
28 | a.is_a?(Array) ? a.empty? : false | |
29 | end | |
b31653d5 | 30 | |
5185c56e OR |
31 | def self.count(args) |
32 | a = args.first.unwrap | |
33 | case a | |
34 | when Array | |
35 | a.size.to_i64 | |
36 | when Nil | |
37 | 0i64 | |
38 | else | |
39 | eval_error "invalid argument for function 'count'" | |
40 | end | |
51be5007 | 41 | end |
51be5007 | 42 | |
5185c56e OR |
43 | def self.pr_str_(args) |
44 | args.map { |a| pr_str(a) }.join(" ") | |
45 | end | |
51be5007 | 46 | |
5185c56e OR |
47 | def self.str(args) |
48 | args.map { |a| pr_str(a, false) }.join | |
49 | end | |
51be5007 | 50 | |
5185c56e OR |
51 | def self.prn(args) |
52 | puts self.pr_str_(args) | |
53 | nil | |
54 | end | |
51be5007 | 55 | |
5185c56e OR |
56 | def self.println(args) |
57 | puts args.map { |a| pr_str(a, false) }.join(" ") | |
58 | nil | |
59 | end | |
b31653d5 | 60 | |
5185c56e OR |
61 | def self.read_string(args) |
62 | head = args.first.unwrap | |
63 | eval_error "argument of read-str must be string" unless head.is_a? String | |
64 | read_str head | |
65 | end | |
d21ef941 | 66 | |
5185c56e OR |
67 | def self.slurp(args) |
68 | head = args.first.unwrap | |
69 | eval_error "argument of slurp must be string" unless head.is_a? String | |
70 | begin | |
71 | File.read head | |
72 | rescue e : Errno | |
73 | eval_error "no such file" | |
74 | end | |
d21ef941 | 75 | end |
d21ef941 | 76 | |
5185c56e OR |
77 | def self.cons(args) |
78 | head, tail = args[0].as(Mal::Type), args[1].unwrap | |
79 | eval_error "2nd arg of cons must be list" unless tail.is_a? Array | |
80 | ([head] + tail).to_mal | |
81 | end | |
ce0696d5 | 82 | |
5185c56e OR |
83 | def self.concat(args) |
84 | args.each_with_object(Mal::List.new) do |arg, list| | |
85 | a = arg.unwrap | |
86 | eval_error "arguments of concat must be list" unless a.is_a?(Array) | |
87 | a.each { |e| list << e } | |
88 | end | |
ce0696d5 | 89 | end |
ce0696d5 | 90 | |
5185c56e OR |
91 | def self.nth(args) |
92 | a0, a1 = args[0].unwrap, args[1].unwrap | |
93 | eval_error "1st argument of nth must be list or vector" unless a0.is_a? Array | |
94 | eval_error "2nd argument of nth must be integer" unless a1.is_a? Int64 | |
95 | a0[a1] | |
96 | end | |
9bbb8ccc | 97 | |
5185c56e OR |
98 | def self.first(args) |
99 | a0 = args[0].unwrap | |
9bbb8ccc | 100 | |
5185c56e OR |
101 | return nil if a0.nil? |
102 | eval_error "1st argument of first must be list or vector or nil" unless a0.is_a? Array | |
103 | a0.empty? ? nil : a0.first | |
104 | end | |
9bbb8ccc | 105 | |
5185c56e OR |
106 | def self.rest(args) |
107 | a0 = args[0].unwrap | |
9bbb8ccc | 108 | |
5185c56e OR |
109 | return Mal::List.new if a0.nil? |
110 | eval_error "1st argument of first must be list or vector or nil" unless a0.is_a? Array | |
111 | return Mal::List.new if a0.empty? | |
112 | a0[1..-1].to_mal | |
113 | end | |
9bbb8ccc | 114 | |
5185c56e OR |
115 | def self.apply(args) |
116 | eval_error "apply must take at least 2 arguments" unless args.size >= 2 | |
1ec34655 | 117 | |
5185c56e OR |
118 | head = args.first.unwrap |
119 | last = args.last.unwrap | |
1ec34655 | 120 | |
5185c56e | 121 | eval_error "last argument of apply must be list or vector" unless last.is_a? Array |
1ec34655 | 122 | |
5185c56e OR |
123 | case head |
124 | when Mal::Closure | |
125 | head.fn.call(args[1..-2] + last) | |
126 | when Mal::Func | |
127 | head.call(args[1..-2] + last) | |
128 | else | |
129 | eval_error "1st argument of apply must be function or closure" | |
130 | end | |
1ec34655 | 131 | end |
1ec34655 | 132 | |
5185c56e OR |
133 | def self.map(args) |
134 | func = args.first.unwrap | |
135 | list = args[1].unwrap | |
1ec34655 | 136 | |
5185c56e | 137 | eval_error "2nd argument of map must be list or vector" unless list.is_a? Array |
1ec34655 | 138 | |
5185c56e OR |
139 | f = case func |
140 | when Mal::Closure then func.fn | |
141 | when Mal::Func then func | |
142 | else eval_error "1st argument of map must be function" | |
143 | end | |
1ec34655 | 144 | |
5185c56e OR |
145 | list.each_with_object(Mal::List.new) do |elem, mapped| |
146 | mapped << f.call([elem]) | |
147 | end | |
1ec34655 | 148 | end |
1ec34655 | 149 | |
5185c56e OR |
150 | def self.nil_value?(args) |
151 | args.first.unwrap.nil? | |
152 | end | |
1ec34655 | 153 | |
5185c56e OR |
154 | def self.true?(args) |
155 | a = args.first.unwrap | |
156 | a.is_a?(Bool) && a | |
157 | end | |
1ec34655 | 158 | |
5185c56e OR |
159 | def self.false?(args) |
160 | a = args.first.unwrap | |
161 | a.is_a?(Bool) && !a | |
162 | end | |
13b2ac96 | 163 | |
5185c56e OR |
164 | def self.symbol?(args) |
165 | args.first.unwrap.is_a?(Mal::Symbol) | |
166 | end | |
1ec34655 | 167 | |
5185c56e OR |
168 | def self.symbol(args) |
169 | head = args.first.unwrap | |
170 | eval_error "1st argument of symbol function must be string" unless head.is_a? String | |
171 | Mal::Symbol.new head | |
172 | end | |
1ec34655 | 173 | |
5185c56e OR |
174 | def self.string?(args) |
175 | head = args.first.unwrap | |
176 | head.is_a?(String) && (head.empty? || head[0] != '\u029e') | |
177 | end | |
51796fd8 | 178 | |
5185c56e OR |
179 | def self.keyword(args) |
180 | head = args.first.unwrap | |
181 | eval_error "1st argument of symbol function must be string" unless head.is_a? String | |
182 | "\u029e" + head | |
183 | end | |
51796fd8 | 184 | |
5185c56e OR |
185 | def self.keyword?(args) |
186 | head = args.first.unwrap | |
187 | head.is_a?(String) && !head.empty? && head[0] == '\u029e' | |
188 | end | |
51796fd8 | 189 | |
5185c56e OR |
190 | def self.number?(args) |
191 | args.first.unwrap.is_a?(Int64) | |
192 | end | |
1ec34655 | 193 | |
5185c56e OR |
194 | def self.fn?(args) |
195 | return false if args.first.macro? | |
196 | head = args.first.unwrap | |
197 | head.is_a?(Mal::Func) || head.is_a?(Mal::Closure) | |
198 | end | |
1ec34655 | 199 | |
5185c56e OR |
200 | def self.macro?(args) |
201 | args.first.macro? | |
1ec34655 | 202 | end |
1ec34655 | 203 | |
5185c56e OR |
204 | def self.vector(args) |
205 | args.to_mal(Mal::Vector) | |
206 | end | |
1ec34655 | 207 | |
5185c56e OR |
208 | def self.vector?(args) |
209 | args.first.unwrap.is_a? Mal::Vector | |
210 | end | |
1ec34655 | 211 | |
5185c56e OR |
212 | def self.hash_map(args) |
213 | eval_error "hash-map must take even number of arguments" unless args.size.even? | |
214 | map = Mal::HashMap.new | |
215 | args.each_slice(2) do |kv| | |
216 | k = kv[0].unwrap | |
217 | eval_error "key must be string" unless k.is_a? String | |
218 | map[k] = kv[1] | |
219 | end | |
220 | map | |
221 | end | |
1ec34655 | 222 | |
5185c56e OR |
223 | def self.map?(args) |
224 | args.first.unwrap.is_a? Mal::HashMap | |
1ec34655 | 225 | end |
226 | ||
5185c56e OR |
227 | def self.assoc(args) |
228 | head = args.first.unwrap | |
229 | eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap | |
230 | eval_error "assoc must take a list and even number of arguments" unless (args.size - 1).even? | |
1ec34655 | 231 | |
5185c56e OR |
232 | map = Mal::HashMap.new |
233 | head.each { |k, v| map[k] = v } | |
1ec34655 | 234 | |
5185c56e OR |
235 | args[1..-1].each_slice(2) do |kv| |
236 | k = kv[0].unwrap | |
237 | eval_error "key must be string" unless k.is_a? String | |
238 | map[k] = kv[1] | |
239 | end | |
1ec34655 | 240 | |
5185c56e | 241 | map |
1ec34655 | 242 | end |
243 | ||
5185c56e OR |
244 | def self.dissoc(args) |
245 | head = args.first.unwrap | |
246 | eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap | |
1ec34655 | 247 | |
5185c56e OR |
248 | map = Mal::HashMap.new |
249 | head.each { |k, v| map[k] = v } | |
1ec34655 | 250 | |
5185c56e OR |
251 | args[1..-1].each do |arg| |
252 | key = arg.unwrap | |
253 | eval_error "key must be string" unless key.is_a? String | |
254 | map.delete key | |
255 | end | |
1ec34655 | 256 | |
5185c56e OR |
257 | map |
258 | end | |
1ec34655 | 259 | |
5185c56e OR |
260 | def self.get(args) |
261 | a0, a1 = args[0].unwrap, args[1].unwrap | |
262 | return nil unless a0.is_a? Mal::HashMap | |
263 | eval_error "2nd argument of get must be string" unless a1.is_a? String | |
1ec34655 | 264 | |
5185c56e OR |
265 | # a0[a1]? isn't available because type ofa0[a1] is infered NoReturn |
266 | a0.has_key?(a1) ? a0[a1] : nil | |
267 | end | |
1ec34655 | 268 | |
5185c56e OR |
269 | def self.contains?(args) |
270 | a0, a1 = args[0].unwrap, args[1].unwrap | |
271 | eval_error "1st argument of get must be hashmap" unless a0.is_a? Mal::HashMap | |
272 | eval_error "2nd argument of get must be string" unless a1.is_a? String | |
273 | a0.has_key? a1 | |
274 | end | |
1ec34655 | 275 | |
5185c56e OR |
276 | def self.keys(args) |
277 | head = args.first.unwrap | |
278 | eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap | |
279 | head.keys.each_with_object(Mal::List.new) { |e, l| l << Mal::Type.new(e) } | |
280 | end | |
6e7390b7 | 281 | |
5185c56e OR |
282 | def self.vals(args) |
283 | head = args.first.unwrap | |
284 | eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap | |
285 | head.values.to_mal | |
286 | end | |
6e7390b7 | 287 | |
5185c56e OR |
288 | def self.sequential?(args) |
289 | args.first.unwrap.is_a? Array | |
290 | end | |
6e7390b7 | 291 | |
5185c56e OR |
292 | def self.readline(args) |
293 | head = args.first.unwrap | |
294 | eval_error "1st argument of readline must be string" unless head.is_a? String | |
295 | Readline.readline(head, true) | |
296 | end | |
6e7390b7 | 297 | |
5185c56e OR |
298 | def self.meta(args) |
299 | m = args.first.meta | |
300 | m.nil? ? nil : m | |
301 | end | |
6e7390b7 | 302 | |
5185c56e OR |
303 | def self.with_meta(args) |
304 | t = args.first.dup | |
305 | t.meta = args[1] | |
306 | t | |
307 | end | |
6e7390b7 | 308 | |
5185c56e OR |
309 | def self.atom(args) |
310 | Mal::Atom.new args.first | |
311 | end | |
6e7390b7 | 312 | |
5185c56e OR |
313 | def self.atom?(args) |
314 | args.first.unwrap.is_a? Mal::Atom | |
315 | end | |
6e7390b7 | 316 | |
5185c56e OR |
317 | def self.deref(args) |
318 | head = args.first.unwrap | |
319 | eval_error "1st argument of deref must be atom" unless head.is_a? Mal::Atom | |
320 | head.val | |
321 | end | |
6e7390b7 | 322 | |
5185c56e OR |
323 | def self.reset!(args) |
324 | head = args.first.unwrap | |
325 | eval_error "1st argument of reset! must be atom" unless head.is_a? Mal::Atom | |
326 | head.val = args[1] | |
6e7390b7 | 327 | end |
6e7390b7 | 328 | |
5185c56e OR |
329 | def self.swap!(args) |
330 | atom = args.first.unwrap | |
331 | eval_error "1st argument of swap! must be atom" unless atom.is_a? Mal::Atom | |
332 | ||
333 | a = [atom.val] + args[2..-1] | |
334 | ||
335 | func = args[1].unwrap | |
336 | case func | |
337 | when Mal::Func | |
338 | atom.val = func.call a | |
339 | when Mal::Closure | |
340 | atom.val = func.fn.call a | |
341 | else | |
342 | eval_error "2nd argumetn of swap! must be function" | |
343 | end | |
6e7390b7 | 344 | end |
6e7390b7 | 345 | |
5185c56e OR |
346 | def self.conj(args) |
347 | seq = args.first.unwrap | |
348 | case seq | |
349 | when Mal::List | |
350 | (args[1..-1].reverse + seq).to_mal | |
351 | when Mal::Vector | |
352 | (seq + args[1..-1]).to_mal(Mal::Vector) | |
353 | else | |
354 | eval_error "1st argument of conj must be list or vector" | |
355 | end | |
13b2ac96 | 356 | end |
13b2ac96 | 357 | |
5185c56e OR |
358 | def self.seq(args) |
359 | obj = args.first.unwrap | |
360 | case obj | |
361 | when nil | |
362 | nil | |
363 | when Mal::List | |
364 | return nil if obj.empty? | |
365 | obj | |
366 | when Mal::Vector | |
367 | return nil if obj.empty? | |
368 | obj.to_mal | |
369 | when String | |
370 | return nil if obj.empty? | |
371 | obj.split("").each_with_object(Mal::List.new) { |e, l| l << Mal::Type.new(e) } | |
372 | else | |
373 | eval_error "argument of seq must be list or vector or string or nil" | |
374 | end | |
375 | end | |
6e7390b7 | 376 | |
5185c56e OR |
377 | def self.time_ms(args) |
378 | Time.now.epoch_ms.to_i64 | |
379 | end | |
b31653d5 | 380 | |
5185c56e OR |
381 | # Note: |
382 | # Simply using ->self.some_func doesn't work | |
383 | macro func(name) | |
384 | -> (args : Array(Mal::Type)) { Mal::Type.new self.{{name.id}}(args) } | |
385 | end | |
b31653d5 | 386 | |
5185c56e OR |
387 | macro rel_op(op) |
388 | -> (args : Array(Mal::Type)) { Mal::Type.new (args[0] {{op.id}} args[1]) } | |
389 | end | |
b31653d5 | 390 | |
5185c56e OR |
391 | NS = { |
392 | "+" => calc_op(:+), | |
393 | "-" => calc_op(:-), | |
394 | "*" => calc_op(:*), | |
395 | "/" => calc_op(:/), | |
396 | "list" => func(:list), | |
397 | "list?" => func(:list?), | |
398 | "empty?" => func(:empty?), | |
399 | "count" => func(:count), | |
400 | "=" => rel_op(:==), | |
401 | "<" => rel_op(:<), | |
402 | ">" => rel_op(:>), | |
403 | "<=" => rel_op(:<=), | |
404 | ">=" => rel_op(:>=), | |
405 | "pr-str" => func(:pr_str_), | |
406 | "str" => func(:str), | |
407 | "prn" => func(:prn), | |
408 | "println" => func(:println), | |
409 | "read-string" => func(:read_string), | |
410 | "slurp" => func(:slurp), | |
411 | "cons" => func(:cons), | |
412 | "concat" => func(:concat), | |
413 | "nth" => func(:nth), | |
414 | "first" => func(:first), | |
415 | "rest" => func(:rest), | |
416 | "throw" => ->(args : Array(Mal::Type)) { raise Mal::RuntimeException.new args[0] }, | |
417 | "apply" => func(:apply), | |
418 | "map" => func(:map), | |
419 | "nil?" => func(:nil_value?), | |
420 | "true?" => func(:true?), | |
421 | "false?" => func(:false?), | |
422 | "symbol?" => func(:symbol?), | |
423 | "symbol" => func(:symbol), | |
424 | "string?" => func(:string?), | |
425 | "keyword" => func(:keyword), | |
426 | "keyword?" => func(:keyword?), | |
427 | "number?" => func(:number?), | |
428 | "fn?" => func(:fn?), | |
429 | "macro?" => func(:macro?), | |
430 | "vector" => func(:vector), | |
431 | "vector?" => func(:vector?), | |
432 | "hash-map" => func(:hash_map), | |
433 | "map?" => func(:map?), | |
434 | "assoc" => func(:assoc), | |
435 | "dissoc" => func(:dissoc), | |
436 | "get" => func(:get), | |
437 | "contains?" => func(:contains?), | |
438 | "keys" => func(:keys), | |
439 | "vals" => func(:vals), | |
440 | "sequential?" => func(:sequential?), | |
441 | "readline" => func(:readline), | |
442 | "meta" => func(:meta), | |
443 | "with-meta" => func(:with_meta), | |
444 | "atom" => func(:atom), | |
445 | "atom?" => func(:atom?), | |
446 | "deref" => func(:deref), | |
447 | "deref" => func(:deref), | |
448 | "reset!" => func(:reset!), | |
449 | "swap!" => func(:swap!), | |
450 | "conj" => func(:conj), | |
451 | "seq" => func(:seq), | |
452 | "time-ms" => func(:time_ms), | |
453 | } of String => Mal::Func | |
b31653d5 | 454 | end |