Merge pull request #440 from aasimk2000/add-nil-if-test
[jackhill/mal.git] / crystal / step3_env.cr
1 #! /usr/bin/env crystal run
2
3 require "readline"
4 require "./reader"
5 require "./printer"
6 require "./types"
7 require "./env"
8
9 # Note:
10 # Employed downcase names because Crystal prohibits uppercase names for methods
11
12 def eval_error(msg)
13 raise Mal::EvalException.new msg
14 end
15
16 def num_func(func)
17 ->(args : Array(Mal::Type)) {
18 x, y = args[0].unwrap, args[1].unwrap
19 eval_error "invalid arguments" unless x.is_a?(Int64) && y.is_a?(Int64)
20 Mal::Type.new func.call(x, y)
21 }
22 end
23
24 REPL_ENV = Mal::Env.new nil
25 REPL_ENV.set("+", Mal::Type.new num_func(->(x : Int64, y : Int64) { x + y }))
26 REPL_ENV.set("-", Mal::Type.new num_func(->(x : Int64, y : Int64) { x - y }))
27 REPL_ENV.set("*", Mal::Type.new num_func(->(x : Int64, y : Int64) { x * y }))
28 REPL_ENV.set("/", Mal::Type.new num_func(->(x : Int64, y : Int64) { x / y }))
29
30 module Mal
31 extend self
32
33 def eval_ast(a, env)
34 return a.map { |n| eval(n, env) } if a.is_a? Array
35
36 Mal::Type.new case ast = a.unwrap
37 when Mal::Symbol
38 if e = env.get(ast.str)
39 e
40 else
41 eval_error "'#{ast.str}' not found"
42 end
43 when Mal::List
44 ast.each_with_object(Mal::List.new) { |n, l| l << eval(n, env) }
45 when Mal::Vector
46 ast.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) }
47 when Mal::HashMap
48 new_map = Mal::HashMap.new
49 ast.each { |k, v| new_map[k] = eval(v, env) }
50 new_map
51 else
52 ast
53 end
54 end
55
56 def read(str)
57 read_str str
58 end
59
60 def eval(t, env)
61 ast = t.unwrap
62
63 return eval_ast(t, env) unless ast.is_a?(Mal::List)
64 return gen_type Mal::List if ast.empty?
65
66 sym = ast.first.unwrap
67 eval_error "first element of list must be a symbol" unless sym.is_a?(Mal::Symbol)
68
69 Mal::Type.new case sym.str
70 when "def!"
71 eval_error "wrong number of argument for 'def!'" unless ast.size == 3
72 a1 = ast[1].unwrap
73 eval_error "1st argument of 'def!' must be symbol" unless a1.is_a?(Mal::Symbol)
74 env.set(a1.str, eval(ast[2], env).as(Mal::Type))
75 when "let*"
76 eval_error "wrong number of argument for 'def!'" unless ast.size == 3
77
78 bindings = ast[1].unwrap
79 eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a?(Array)
80 eval_error "size of binding list must be even" unless bindings.size.even?
81
82 new_env = Mal::Env.new env
83 bindings.each_slice(2) do |binding|
84 name, value = binding[0].unwrap, binding[1]
85 eval_error "name of binding must be specified as symbol" unless name.is_a?(Mal::Symbol)
86 new_env.set(name.str, eval(value, new_env))
87 end
88
89 eval(ast[2], new_env)
90 else
91 f = eval_ast(ast.first, env)
92 ast.shift(1)
93 args = eval_ast(ast, env)
94
95 if f.is_a?(Mal::Type) && (f2 = f.unwrap).is_a?(Mal::Func)
96 f2.call(args.as(Array(Mal::Type)))
97 else
98 eval_error "expected function symbol as the first symbol of list"
99 end
100 end
101 end
102
103 def print(result)
104 pr_str(result, true)
105 end
106
107 def rep(str)
108 print(eval(read(str), REPL_ENV))
109 end
110 end
111
112 while line = Readline.readline("user> ", true)
113 begin
114 puts Mal.rep(line)
115 rescue e : Mal::RuntimeException
116 STDERR.puts "Error: #{pr_str(e.thrown, true)}"
117 rescue e
118 STDERR.puts "Error: #{e}"
119 end
120 end