Commit | Line | Data |
---|---|---|
4ef4b17c JM |
1 | #![allow(non_snake_case)] |
2 | ||
3 | use std::rc::Rc; | |
4 | //use std::collections::HashMap; | |
5 | use fnv::FnvHashMap; | |
6 | use itertools::Itertools; | |
7 | ||
8 | #[macro_use] | |
9 | extern crate lazy_static; | |
4ef4b17c | 10 | extern crate fnv; |
4a649b37 RF |
11 | extern crate itertools; |
12 | extern crate regex; | |
4ef4b17c JM |
13 | |
14 | extern crate rustyline; | |
15 | use rustyline::error::ReadlineError; | |
16 | use rustyline::Editor; | |
17 | ||
18 | #[macro_use] | |
19 | mod types; | |
c9b18677 GL |
20 | use crate::types::MalErr::{ErrMalVal, ErrString}; |
21 | use crate::types::MalVal::{Bool, Func, Hash, List, MalFunc, Nil, Str, Sym, Vector}; | |
22 | use crate::types::{error, format_error, MalArgs, MalErr, MalRet, MalVal}; | |
4ef4b17c | 23 | mod env; |
4a649b37 RF |
24 | mod printer; |
25 | mod reader; | |
c9b18677 | 26 | use crate::env::{env_bind, env_find, env_get, env_new, env_set, env_sets, Env}; |
4ef4b17c JM |
27 | #[macro_use] |
28 | mod core; | |
29 | ||
30 | // read | |
31 | fn read(str: &str) -> MalRet { | |
4a649b37 | 32 | reader::read_str(str.to_string()) |
4ef4b17c JM |
33 | } |
34 | ||
35 | // eval | |
36 | fn quasiquote(ast: &MalVal) -> MalVal { | |
4a649b37 RF |
37 | match ast { |
38 | List(ref v, _) | Vector(ref v, _) if v.len() > 0 => { | |
39 | let a0 = &v[0]; | |
40 | match a0 { | |
41 | Sym(ref s) if s == "unquote" => v[1].clone(), | |
42 | _ => match a0 { | |
43 | List(ref v0, _) | Vector(ref v0, _) if v0.len() > 0 => match v0[0] { | |
44 | Sym(ref s) if s == "splice-unquote" => list![ | |
45 | Sym("concat".to_string()), | |
46 | v0[1].clone(), | |
47 | quasiquote(&list!(v[1..].to_vec())) | |
48 | ], | |
49 | _ => list![ | |
50 | Sym("cons".to_string()), | |
51 | quasiquote(a0), | |
52 | quasiquote(&list!(v[1..].to_vec())) | |
53 | ], | |
54 | }, | |
55 | _ => list![ | |
56 | Sym("cons".to_string()), | |
4ef4b17c | 57 | quasiquote(a0), |
4a649b37 RF |
58 | quasiquote(&list!(v[1..].to_vec())) |
59 | ], | |
4ef4b17c | 60 | }, |
4ef4b17c | 61 | } |
4ef4b17c | 62 | } |
4a649b37 RF |
63 | _ => list![Sym("quote".to_string()), ast.clone()], |
64 | } | |
4ef4b17c JM |
65 | } |
66 | ||
4a649b37 RF |
67 | fn is_macro_call(ast: &MalVal, env: &Env) -> Option<(MalVal, MalArgs)> { |
68 | match ast { | |
69 | List(v, _) => match v[0] { | |
70 | Sym(ref s) => match env_find(env, s) { | |
71 | Some(e) => match env_get(&e, &v[0]) { | |
72 | Ok(f @ MalFunc { is_macro: true, .. }) => Some((f, v[1..].to_vec())), | |
73 | _ => None, | |
4ef4b17c JM |
74 | }, |
75 | _ => None, | |
4ef4b17c JM |
76 | }, |
77 | _ => None, | |
4ef4b17c JM |
78 | }, |
79 | _ => None, | |
4a649b37 | 80 | } |
4ef4b17c JM |
81 | } |
82 | ||
83 | fn macroexpand(mut ast: MalVal, env: &Env) -> (bool, MalRet) { | |
4a649b37 RF |
84 | let mut was_expanded = false; |
85 | while let Some((mf, args)) = is_macro_call(&ast, env) { | |
86 | //println!("macroexpand 1: {:?}", ast); | |
87 | ast = match mf.apply(args) { | |
88 | Err(e) => return (false, Err(e)), | |
89 | Ok(a) => a, | |
90 | }; | |
91 | //println!("macroexpand 2: {:?}", ast); | |
92 | was_expanded = true; | |
93 | } | |
94 | ((was_expanded, Ok(ast))) | |
4ef4b17c JM |
95 | } |
96 | ||
97 | fn eval_ast(ast: &MalVal, env: &Env) -> MalRet { | |
4a649b37 RF |
98 | match ast { |
99 | Sym(_) => Ok(env_get(&env, &ast)?), | |
100 | List(v, _) => { | |
101 | let mut lst: MalArgs = vec![]; | |
102 | for a in v.iter() { | |
103 | lst.push(eval(a.clone(), env.clone())?) | |
104 | } | |
105 | Ok(list!(lst)) | |
106 | } | |
107 | Vector(v, _) => { | |
108 | let mut lst: MalArgs = vec![]; | |
109 | for a in v.iter() { | |
110 | lst.push(eval(a.clone(), env.clone())?) | |
111 | } | |
112 | Ok(vector!(lst)) | |
113 | } | |
114 | Hash(hm, _) => { | |
115 | let mut new_hm: FnvHashMap<String, MalVal> = FnvHashMap::default(); | |
116 | for (k, v) in hm.iter() { | |
117 | new_hm.insert(k.to_string(), eval(v.clone(), env.clone())?); | |
118 | } | |
119 | Ok(Hash(Rc::new(new_hm), Rc::new(Nil))) | |
120 | } | |
121 | _ => Ok(ast.clone()), | |
122 | } | |
4ef4b17c JM |
123 | } |
124 | ||
125 | fn eval(mut ast: MalVal, mut env: Env) -> MalRet { | |
4a649b37 | 126 | let ret: MalRet; |
4ef4b17c | 127 | |
4a649b37 RF |
128 | 'tco: loop { |
129 | ret = match ast.clone() { | |
130 | List(l, _) => { | |
131 | if l.len() == 0 { | |
132 | return Ok(ast); | |
133 | } | |
134 | match macroexpand(ast.clone(), &env) { | |
135 | (true, Ok(new_ast)) => { | |
136 | ast = new_ast; | |
137 | continue 'tco; | |
138 | } | |
139 | (_, Err(e)) => return Err(e), | |
140 | _ => (), | |
141 | } | |
4ef4b17c | 142 | |
4a649b37 RF |
143 | if l.len() == 0 { |
144 | return Ok(ast); | |
145 | } | |
146 | let a0 = &l[0]; | |
147 | match a0 { | |
148 | Sym(ref a0sym) if a0sym == "def!" => { | |
149 | env_set(&env, l[1].clone(), eval(l[2].clone(), env.clone())?) | |
150 | } | |
151 | Sym(ref a0sym) if a0sym == "let*" => { | |
152 | env = env_new(Some(env.clone())); | |
153 | let (a1, a2) = (l[1].clone(), l[2].clone()); | |
154 | match a1 { | |
155 | List(ref binds, _) | Vector(ref binds, _) => { | |
156 | for (b, e) in binds.iter().tuples() { | |
157 | match b { | |
158 | Sym(_) => { | |
159 | let _ = env_set( | |
160 | &env, | |
161 | b.clone(), | |
162 | eval(e.clone(), env.clone())?, | |
163 | ); | |
164 | } | |
165 | _ => { | |
166 | return error("let* with non-Sym binding"); | |
167 | } | |
168 | } | |
169 | } | |
170 | } | |
171 | _ => { | |
172 | return error("let* with non-List bindings"); | |
173 | } | |
174 | }; | |
175 | ast = a2; | |
176 | continue 'tco; | |
177 | } | |
178 | Sym(ref a0sym) if a0sym == "quote" => Ok(l[1].clone()), | |
179 | Sym(ref a0sym) if a0sym == "quasiquote" => { | |
180 | ast = quasiquote(&l[1]); | |
181 | continue 'tco; | |
182 | } | |
183 | Sym(ref a0sym) if a0sym == "defmacro!" => { | |
184 | let (a1, a2) = (l[1].clone(), l[2].clone()); | |
185 | let r = eval(a2, env.clone())?; | |
186 | match r { | |
187 | MalFunc { | |
188 | eval, | |
189 | ast, | |
190 | env, | |
191 | params, | |
192 | .. | |
193 | } => Ok(env_set( | |
194 | &env, | |
195 | a1.clone(), | |
196 | MalFunc { | |
197 | eval: eval, | |
198 | ast: ast.clone(), | |
199 | env: env.clone(), | |
200 | params: params.clone(), | |
201 | is_macro: true, | |
202 | meta: Rc::new(Nil), | |
203 | }, | |
204 | )?), | |
205 | _ => error("set_macro on non-function"), | |
206 | } | |
207 | } | |
208 | Sym(ref a0sym) if a0sym == "macroexpand" => { | |
209 | match macroexpand(l[1].clone(), &env) { | |
210 | (_, Ok(new_ast)) => Ok(new_ast), | |
211 | (_, e) => return e, | |
212 | } | |
213 | } | |
214 | Sym(ref a0sym) if a0sym == "try*" => match eval(l[1].clone(), env.clone()) { | |
215 | Err(ref e) if l.len() >= 3 => { | |
216 | let exc = match e { | |
217 | ErrMalVal(mv) => mv.clone(), | |
218 | ErrString(s) => Str(s.to_string()), | |
219 | }; | |
220 | match l[2].clone() { | |
221 | List(c, _) => { | |
222 | let catch_env = env_bind( | |
223 | Some(env.clone()), | |
224 | list!(vec![c[1].clone()]), | |
225 | vec![exc], | |
226 | )?; | |
227 | eval(c[2].clone(), catch_env) | |
228 | } | |
229 | _ => error("invalid catch block"), | |
230 | } | |
231 | } | |
232 | res => res, | |
233 | }, | |
234 | Sym(ref a0sym) if a0sym == "do" => { | |
235 | match eval_ast(&list!(l[1..l.len() - 1].to_vec()), &env)? { | |
236 | List(_, _) => { | |
237 | ast = l.last().unwrap_or(&Nil).clone(); | |
238 | continue 'tco; | |
239 | } | |
240 | _ => error("invalid do form"), | |
241 | } | |
242 | } | |
243 | Sym(ref a0sym) if a0sym == "if" => { | |
244 | let cond = eval(l[1].clone(), env.clone())?; | |
245 | match cond { | |
246 | Bool(false) | Nil if l.len() >= 4 => { | |
247 | ast = l[3].clone(); | |
248 | continue 'tco; | |
249 | } | |
250 | Bool(false) | Nil => Ok(Nil), | |
251 | _ if l.len() >= 3 => { | |
252 | ast = l[2].clone(); | |
253 | continue 'tco; | |
254 | } | |
255 | _ => Ok(Nil), | |
256 | } | |
257 | } | |
258 | Sym(ref a0sym) if a0sym == "fn*" => { | |
259 | let (a1, a2) = (l[1].clone(), l[2].clone()); | |
260 | Ok(MalFunc { | |
261 | eval: eval, | |
262 | ast: Rc::new(a2), | |
263 | env: env, | |
264 | params: Rc::new(a1), | |
265 | is_macro: false, | |
266 | meta: Rc::new(Nil), | |
267 | }) | |
268 | } | |
269 | Sym(ref a0sym) if a0sym == "eval" => { | |
270 | ast = eval(l[1].clone(), env.clone())?; | |
271 | while let Some(ref e) = env.clone().outer { | |
272 | env = e.clone(); | |
273 | } | |
274 | continue 'tco; | |
275 | } | |
276 | _ => match eval_ast(&ast, &env)? { | |
277 | List(ref el, _) => { | |
278 | let ref f = el[0].clone(); | |
279 | let args = el[1..].to_vec(); | |
280 | match f { | |
281 | Func(_, _) => f.apply(args), | |
282 | MalFunc { | |
283 | ast: mast, | |
284 | env: menv, | |
285 | params, | |
286 | .. | |
287 | } => { | |
288 | let a = &**mast; | |
289 | let p = &**params; | |
290 | env = env_bind(Some(menv.clone()), p.clone(), args)?; | |
291 | ast = a.clone(); | |
292 | continue 'tco; | |
293 | } | |
294 | _ => error("attempt to call non-function"), | |
295 | } | |
296 | } | |
297 | _ => error("expected a list"), | |
298 | }, | |
4ef4b17c | 299 | } |
4ef4b17c | 300 | } |
4a649b37 RF |
301 | _ => eval_ast(&ast, &env), |
302 | }; | |
4ef4b17c | 303 | |
4a649b37 RF |
304 | break; |
305 | } // end 'tco loop | |
4ef4b17c | 306 | |
4a649b37 | 307 | ret |
4ef4b17c JM |
308 | } |
309 | ||
310 | ||
311 | fn print(ast: &MalVal) -> String { | |
4a649b37 | 312 | ast.pr_str(true) |
4ef4b17c JM |
313 | } |
314 | ||
4a649b37 RF |
315 | fn rep(str: &str, env: &Env) -> Result<String, MalErr> { |
316 | let ast = read(str)?; | |
317 | let exp = eval(ast, env.clone())?; | |
318 | Ok(print(&exp)) | |
4ef4b17c JM |
319 | } |
320 | ||
321 | fn main() { | |
4a649b37 RF |
322 | let mut args = std::env::args(); |
323 | let arg1 = args.nth(1); | |
4ef4b17c | 324 | |
4a649b37 RF |
325 | // `()` can be used when no completer is required |
326 | let mut rl = Editor::<()>::new(); | |
327 | if rl.load_history(".mal-history").is_err() { | |
328 | eprintln!("No previous history."); | |
329 | } | |
4ef4b17c | 330 | |
4a649b37 RF |
331 | // core.rs: defined using rust |
332 | let repl_env = env_new(None); | |
333 | for (k, v) in core::ns() { | |
334 | env_sets(&repl_env, k, v); | |
335 | } | |
336 | env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect())); | |
4ef4b17c | 337 | |
4a649b37 RF |
338 | // core.mal: defined using the language itself |
339 | let _ = rep("(def! *host-language* \"rust\")", &repl_env); | |
340 | let _ = rep("(def! not (fn* (a) (if a false true)))", &repl_env); | |
341 | let _ = rep( | |
342 | "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", | |
343 | &repl_env, | |
344 | ); | |
345 | let _ = rep("(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)))))))", &repl_env); | |
4ef4b17c | 346 | |
4a649b37 RF |
347 | // Invoked with arguments |
348 | if let Some(f) = arg1 { | |
349 | match rep(&format!("(load-file \"{}\")", f), &repl_env) { | |
350 | Ok(_) => std::process::exit(0), | |
351 | Err(e) => { | |
352 | println!("Error: {}", format_error(e)); | |
353 | std::process::exit(1); | |
354 | } | |
355 | } | |
4ef4b17c | 356 | } |
4ef4b17c | 357 | |
4a649b37 RF |
358 | // main repl loop |
359 | let _ = rep("(println (str \"Mal [\" *host-language* \"]\"))", &repl_env); | |
360 | loop { | |
361 | let readline = rl.readline("user> "); | |
362 | match readline { | |
363 | Ok(line) => { | |
364 | rl.add_history_entry(&line); | |
365 | rl.save_history(".mal-history").unwrap(); | |
366 | if line.len() > 0 { | |
367 | match rep(&line, &repl_env) { | |
368 | Ok(out) => println!("{}", out), | |
369 | Err(e) => println!("Error: {}", format_error(e)), | |
370 | } | |
371 | } | |
372 | } | |
373 | Err(ReadlineError::Interrupted) => continue, | |
374 | Err(ReadlineError::Eof) => break, | |
375 | Err(err) => { | |
376 | println!("Error: {:?}", err); | |
377 | break; | |
378 | } | |
4ef4b17c | 379 | } |
4ef4b17c | 380 | } |
4ef4b17c | 381 | } |