Commit | Line | Data |
---|---|---|
107d9694 IJ |
1 | require_relative "mal_readline" |
2 | require_relative "types" | |
3 | require_relative "reader" | |
4 | require_relative "printer" | |
5 | require_relative "env" | |
6 | require_relative "core" | |
3a56f91a JM |
7 | |
8 | # read | |
9 | def READ(str) | |
10 | return read_str(str) | |
11 | end | |
12 | ||
13 | # eval | |
3e8a088f | 14 | def pair?(x) |
3a56f91a JM |
15 | return sequential?(x) && x.size > 0 |
16 | end | |
17 | ||
18 | def quasiquote(ast) | |
3e8a088f JM |
19 | if not pair?(ast) |
20 | return List.new [:quote, ast] | |
3a56f91a JM |
21 | elsif ast[0] == :unquote |
22 | return ast[1] | |
3e8a088f JM |
23 | elsif pair?(ast[0]) && ast[0][0] == :"splice-unquote" |
24 | return List.new [:concat, ast[0][1], quasiquote(ast.drop(1))] | |
3a56f91a | 25 | else |
3e8a088f | 26 | return List.new [:cons, quasiquote(ast[0]), quasiquote(ast.drop(1))] |
3a56f91a JM |
27 | end |
28 | end | |
29 | ||
30 | def macro_call?(ast, env) | |
31 | return (ast.is_a?(List) && | |
32 | ast[0].is_a?(Symbol) && | |
33 | env.find(ast[0]) && | |
34 | env.get(ast[0]).is_a?(Function) && | |
35 | env.get(ast[0]).is_macro) | |
36 | end | |
37 | ||
38 | def macroexpand(ast, env) | |
39 | while macro_call?(ast, env) | |
40 | mac = env.get(ast[0]) | |
41 | ast = mac[*ast.drop(1)] | |
42 | end | |
43 | return ast | |
44 | end | |
45 | ||
46 | def eval_ast(ast, env) | |
47 | return case ast | |
48 | when Symbol | |
49 | env.get(ast) | |
50 | when List | |
51 | List.new ast.map{|a| EVAL(a, env)} | |
52 | when Vector | |
53 | Vector.new ast.map{|a| EVAL(a, env)} | |
3e8a088f JM |
54 | when Hash |
55 | new_hm = {} | |
56 | ast.each{|k,v| new_hm[EVAL(k,env)] = EVAL(v, env)} | |
57 | new_hm | |
3a56f91a JM |
58 | else |
59 | ast | |
60 | end | |
61 | end | |
62 | ||
63 | def EVAL(ast, env) | |
64 | while true | |
65 | ||
66 | #puts "EVAL: #{_pr_str(ast, true)}" | |
67 | ||
68 | if not ast.is_a? List | |
69 | return eval_ast(ast, env) | |
70 | end | |
71 | ||
72 | # apply list | |
73 | ast = macroexpand(ast, env) | |
74 | return ast if not ast.is_a? List | |
75 | ||
76 | a0,a1,a2,a3 = ast | |
77 | case a0 | |
78 | when :def! | |
79 | return env.set(a1, EVAL(a2, env)) | |
80 | when :"let*" | |
81 | let_env = Env.new(env) | |
82 | a1.each_slice(2) do |a,e| | |
83 | let_env.set(a, EVAL(e, let_env)) | |
84 | end | |
6301e0b6 JM |
85 | env = let_env |
86 | ast = a2 # Continue loop (TCO) | |
3a56f91a JM |
87 | when :quote |
88 | return a1 | |
89 | when :quasiquote | |
6301e0b6 | 90 | ast = quasiquote(a1); # Continue loop (TCO) |
3a56f91a JM |
91 | when :defmacro! |
92 | func = EVAL(a2, env) | |
93 | func.is_macro = true | |
94 | return env.set(a1, func) | |
95 | when :macroexpand | |
96 | return macroexpand(a1, env) | |
3a56f91a JM |
97 | when :"try*" |
98 | begin | |
99 | return EVAL(a1, env) | |
100 | rescue Exception => exc | |
101 | if exc.is_a? MalException | |
102 | exc = exc.data | |
103 | else | |
104 | exc = exc.message | |
105 | end | |
106 | if a2 && a2[0] == :"catch*" | |
107 | return EVAL(a2[2], Env.new(env, [a2[1]], [exc])) | |
108 | else | |
109 | raise esc | |
110 | end | |
111 | end | |
112 | when :do | |
113 | eval_ast(ast[1..-2], env) | |
6301e0b6 | 114 | ast = ast.last # Continue loop (TCO) |
3a56f91a JM |
115 | when :if |
116 | cond = EVAL(a1, env) | |
117 | if not cond | |
118 | return nil if a3 == nil | |
6301e0b6 | 119 | ast = a3 # Continue loop (TCO) |
3a56f91a | 120 | else |
6301e0b6 | 121 | ast = a2 # Continue loop (TCO) |
3a56f91a JM |
122 | end |
123 | when :"fn*" | |
124 | return Function.new(a2, env, a1) {|*args| | |
125 | EVAL(a2, Env.new(env, a1, args)) | |
126 | } | |
127 | else | |
128 | el = eval_ast(ast, env) | |
129 | f = el[0] | |
130 | if f.class == Function | |
131 | ast = f.ast | |
6301e0b6 | 132 | env = f.gen_env(el.drop(1)) # Continue loop (TCO) |
3a56f91a JM |
133 | else |
134 | return f[*el.drop(1)] | |
135 | end | |
136 | end | |
137 | ||
138 | end | |
139 | end | |
140 | ||
141 | ||
142 | def PRINT(exp) | |
143 | return _pr_str(exp, true) | |
144 | end | |
145 | ||
146 | # repl | |
147 | repl_env = Env.new | |
148 | RE = lambda {|str| EVAL(READ(str), repl_env) } | |
149 | REP = lambda {|str| PRINT(EVAL(READ(str), repl_env)) } | |
3a56f91a | 150 | |
8cb5cda4 JM |
151 | # core.rb: defined using ruby |
152 | $core_ns.each do |k,v| repl_env.set(k,v) end | |
153 | repl_env.set(:eval, lambda {|ast| EVAL(ast, repl_env)}) | |
86b689f3 | 154 | repl_env.set(:"*ARGV*", List.new(ARGV.slice(1,ARGV.length) || [])) |
3a56f91a | 155 | |
8cb5cda4 | 156 | # core.mal: defined using the language itself |
3a56f91a | 157 | RE["(def! not (fn* (a) (if a false true)))"] |
8cb5cda4 | 158 | RE["(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"] |
3a56f91a JM |
159 | RE["(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"] |
160 | RE["(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"] | |
3a56f91a | 161 | |
3a56f91a | 162 | if ARGV.size > 0 |
86b689f3 | 163 | RE["(load-file \"" + ARGV[0] + "\")"] |
3a56f91a JM |
164 | exit 0 |
165 | end | |
86b689f3 JM |
166 | |
167 | # repl loop | |
718887c3 | 168 | while line = _readline("user> ") |
3a56f91a JM |
169 | begin |
170 | puts REP[line] | |
171 | rescue Exception => e | |
172 | puts "Error: #{e}" | |
173 | puts "\t#{e.backtrace.join("\n\t")}" | |
174 | end | |
175 | end |