crystal: make core.cr and implement +, -, *, /, list, list?, empty?, count and =
[jackhill/mal.git] / crystal / step4_if_fn_do.cr
CommitLineData
095b73ea 1#! /usr/bin/env crystal run
2
3require "./readline"
4require "./reader"
5require "./printer"
6require "./types"
7require "./env"
b31653d5 8require "./core"
095b73ea 9
10# Note:
11# Employed downcase names because Crystal prohibits uppercase names for methods
12
13def eval_error(msg)
14 raise Mal::EvalException.new msg
15end
16
095b73ea 17def func_of(env, binds, body) : Mal::Type
18 -> (args : Array(Mal::Type)) {
19 new_env = Mal::Env.new(env, binds, args)
20 eval(body, new_env) as Mal::Type
21 } as Mal::Func
22end
23
095b73ea 24def eval_ast(ast, env)
25 case ast
26 when Mal::Symbol
27 if e = env.get(ast.val)
28 e
29 else
30 eval_error "'#{ast.val}' not found"
31 end
32 when Mal::List
33 ast.map{|n| eval(n, env) as Mal::Type}
34 when Mal::Vector
35 ast.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)}
36 when Mal::HashMap
37 ast.each{|k, v| ast[k] = eval(v, env)}
38 else
39 ast
40 end
41end
42
43def read(str)
44 read_str str
45end
46
47def eval(ast, env)
48 return eval_ast(ast, env) unless ast.is_a?(Mal::List)
49
b31653d5 50 return Mal::List.new if ast.empty?
095b73ea 51
bed63064 52 head = ast.first
53 case head
54 when Mal::Symbol
55 case head.val
56 when "def!"
57 eval_error "wrong number of argument for 'def!'" unless ast.size == 3
58 a1 = ast[1]
59 eval_error "1st argument of 'def!' must be symbol" unless a1.is_a?(Mal::Symbol)
60 env.set(a1.val, eval(ast[2], env) as Mal::Type)
61 when "let*"
62 eval_error "wrong number of argument for 'def!'" unless ast.size == 3
63
64 bindings = ast[1]
65 eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a?(Array)
66 eval_error "size of binding list must be even" unless bindings.size.even?
67
68 new_env = Mal::Env.new env
69 bindings.each_slice(2) do |binding|
70 name, value = binding
71 eval_error "name of binding must be specified as symbol" unless name.is_a?(Mal::Symbol)
72 new_env.set(name.val, eval(value, new_env))
73 end
74
75 eval(ast[2], new_env)
76 when "do"
77 ast.shift(1)
78 eval_ast(ast, env).last
79 when "if"
80 cond = eval(ast[1], env)
81 case cond
82 when Nil
83 ast.size >= 4 ? eval(ast[3], env) : nil
84 when false
85 ast.size >= 4 ? eval(ast[3], env) : nil
86 else
87 eval(ast[2], env)
88 end
89 when "fn*"
90 # Note:
91 # If writing lambda expression here directly, compiler will fail to infer type of 'ast'. (Error 'Nil for empty?')
92 func_of(env, ast[1], ast[2])
095b73ea 93 else
bed63064 94 f = eval_ast(head, env)
95 eval_error "expected function symbol as the first symbol of list" unless f.is_a?(Mal::Func)
96 ast.shift(1)
97 f.call eval_ast(ast, env).each_with_object([] of Mal::Type){|e, a| a << e}
095b73ea 98 end
095b73ea 99 else
bed63064 100 f = eval(head, env)
095b73ea 101 eval_error "expected function symbol as the first symbol of list" unless f.is_a?(Mal::Func)
102 ast.shift(1)
bed63064 103 f.call eval_ast(ast, env).each_with_object([] of Mal::Type){|e, a| a << e}
095b73ea 104 end
105end
106
107def print(result)
108 pr_str(result, true)
109end
110
111def rep(str)
112 print(eval(read(str), $repl_env))
113end
114
b31653d5 115$repl_env = Mal::Env.new nil
116Mal::NS.each{|k,v| $repl_env.set(k, v)}
117
095b73ea 118while line = my_readline("user> ")
119 begin
120 puts rep(line)
121 rescue e
122 STDERR.puts e
123 end
124end