Commit | Line | Data |
---|---|---|
4ef4b17c JM |
1 | use std::rc::Rc; |
2 | //use std::collections::HashMap; | |
3 | use fnv::FnvHashMap; | |
4 | use itertools::Itertools; | |
5 | ||
6 | #[macro_use] | |
7 | extern crate lazy_static; | |
8 | extern crate regex; | |
9 | extern crate itertools; | |
10 | extern crate fnv; | |
11 | ||
12 | extern crate rustyline; | |
13 | use rustyline::error::ReadlineError; | |
14 | use rustyline::Editor; | |
15 | ||
16 | #[macro_use] | |
17 | mod types; | |
18 | use types::{MalVal,MalArgs,MalRet,MalErr,error,format_error}; | |
19 | use types::MalVal::{Nil,Bool,Str,Sym,List,Vector,Hash,Func,MalFunc}; | |
20 | mod reader; | |
21 | mod printer; | |
22 | mod env; | |
23 | use env::{Env,env_new,env_bind,env_get,env_set,env_sets}; | |
24 | #[macro_use] | |
25 | mod core; | |
26 | ||
27 | // read | |
28 | fn read(str: &str) -> MalRet { | |
29 | reader::read_str(str.to_string()) | |
30 | } | |
31 | ||
32 | // eval | |
33 | fn quasiquote(ast: &MalVal) -> MalVal { | |
34 | match ast { | |
35 | List(ref v,_) | Vector(ref v,_) if v.len() > 0 => { | |
36 | let a0 = &v[0]; | |
37 | match a0 { | |
38 | Sym(ref s) if s == "unquote" => v[1].clone(), | |
39 | _ => { | |
40 | match a0 { | |
41 | List(ref v0,_) | Vector(ref v0,_) if v0.len() > 0 => { | |
42 | match v0[0] { | |
43 | Sym(ref s) if s == "splice-unquote" => { | |
44 | list![Sym("concat".to_string()), | |
45 | v0[1].clone(), | |
46 | quasiquote(&list!(v[1..].to_vec()))] | |
47 | }, | |
48 | _ => { | |
49 | list![Sym("cons".to_string()), | |
50 | quasiquote(a0), | |
51 | quasiquote(&list!(v[1..].to_vec()))] | |
52 | }, | |
53 | } | |
54 | }, | |
55 | _ => { | |
56 | list![Sym("cons".to_string()), | |
57 | quasiquote(a0), | |
58 | quasiquote(&list!(v[1..].to_vec()))] | |
59 | } | |
60 | } | |
61 | } | |
62 | } | |
63 | }, | |
64 | _ => list![Sym("quote".to_string()), ast.clone()] | |
65 | } | |
66 | } | |
67 | ||
68 | fn eval_ast(ast: &MalVal, env: &Env) -> MalRet { | |
69 | match ast { | |
70 | Sym(_) => Ok(env_get(&env, &ast)?), | |
71 | List(v,_) => { | |
72 | let mut lst: MalArgs = vec![]; | |
73 | for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } | |
74 | Ok(list!(lst)) | |
75 | }, | |
76 | Vector(v,_) => { | |
77 | let mut lst: MalArgs = vec![]; | |
78 | for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } | |
79 | Ok(vector!(lst)) | |
80 | }, | |
81 | Hash(hm,_) => { | |
82 | let mut new_hm: FnvHashMap<String,MalVal> = FnvHashMap::default(); | |
83 | for (k,v) in hm.iter() { | |
84 | new_hm.insert(k.to_string(), eval(v.clone(), env.clone())?); | |
85 | } | |
86 | Ok(Hash(Rc::new(new_hm),Rc::new(Nil))) | |
87 | }, | |
88 | _ => Ok(ast.clone()), | |
89 | } | |
90 | } | |
91 | ||
92 | fn eval(mut ast: MalVal, mut env: Env) -> MalRet { | |
93 | let ret: MalRet; | |
94 | ||
95 | 'tco: loop { | |
96 | ||
97 | ret = match ast.clone() { | |
98 | List(l,_) => { | |
99 | if l.len() == 0 { return Ok(ast); } | |
100 | let a0 = &l[0]; | |
101 | match a0 { | |
102 | Sym(ref a0sym) if a0sym == "def!" => { | |
103 | env_set(&env, l[1].clone(), eval(l[2].clone(), env.clone())?) | |
104 | }, | |
105 | Sym(ref a0sym) if a0sym == "let*" => { | |
106 | env = env_new(Some(env.clone())); | |
107 | let (a1, a2) = (l[1].clone(), l[2].clone()); | |
108 | match a1 { | |
109 | List(ref binds,_) | Vector(ref binds,_) => { | |
110 | for (b, e) in binds.iter().tuples() { | |
111 | match b { | |
112 | Sym(_) => { | |
113 | let _ = env_set(&env, b.clone(), | |
114 | eval(e.clone(), env.clone())?); | |
115 | }, | |
116 | _ => { | |
117 | return error("let* with non-Sym binding"); | |
118 | } | |
119 | } | |
120 | } | |
121 | }, | |
122 | _ => { | |
123 | return error("let* with non-List bindings"); | |
124 | } | |
125 | }; | |
126 | ast = a2; | |
127 | continue 'tco; | |
128 | }, | |
129 | Sym(ref a0sym) if a0sym == "quote" => { | |
130 | Ok(l[1].clone()) | |
131 | }, | |
132 | Sym(ref a0sym) if a0sym == "quasiquote" => { | |
133 | ast = quasiquote(&l[1]); | |
134 | continue 'tco; | |
135 | }, | |
136 | Sym(ref a0sym) if a0sym == "do" => { | |
137 | match eval_ast(&list!(l[1..l.len()-1].to_vec()), &env)? { | |
138 | List(_,_) => { | |
139 | ast = l.last().unwrap_or(&Nil).clone(); | |
140 | continue 'tco; | |
141 | }, | |
142 | _ => error("invalid do form"), | |
143 | } | |
144 | }, | |
145 | Sym(ref a0sym) if a0sym == "if" => { | |
146 | let cond = eval(l[1].clone(), env.clone())?; | |
147 | match cond { | |
148 | Bool(false) | Nil if l.len() >= 4 => { | |
149 | ast = l[3].clone(); | |
150 | continue 'tco; | |
151 | }, | |
152 | Bool(false) | Nil => Ok(Nil), | |
153 | _ if l.len() >= 3 => { | |
154 | ast = l[2].clone(); | |
155 | continue 'tco; | |
156 | }, | |
157 | _ => Ok(Nil) | |
158 | } | |
159 | }, | |
160 | Sym(ref a0sym) if a0sym == "fn*" => { | |
161 | let (a1, a2) = (l[1].clone(), l[2].clone()); | |
162 | Ok(MalFunc{eval: eval, ast: Rc::new(a2), env: env, | |
163 | params: Rc::new(a1), is_macro: false, | |
164 | meta: Rc::new(Nil)}) | |
165 | }, | |
166 | Sym(ref a0sym) if a0sym == "eval" => { | |
167 | ast = eval(l[1].clone(), env.clone())?; | |
168 | while let Some(ref e) = env.clone().outer { | |
169 | env = e.clone(); | |
170 | } | |
171 | continue 'tco; | |
172 | }, | |
173 | _ => { | |
174 | match eval_ast(&ast, &env)? { | |
175 | List(ref el,_) => { | |
176 | let ref f = el[0].clone(); | |
177 | let args = el[1..].to_vec(); | |
178 | match f { | |
179 | Func(_,_) => f.apply(args), | |
180 | MalFunc{ast: mast, env: menv, params, ..} => { | |
181 | let a = &**mast; | |
182 | let p = &**params; | |
183 | env = env_bind(Some(menv.clone()), p.clone(), args)?; | |
184 | ast = a.clone(); | |
185 | continue 'tco; | |
186 | }, | |
187 | _ => error("attempt to call non-function"), | |
188 | } | |
189 | }, | |
190 | _ => { | |
191 | error("expected a list") | |
192 | } | |
193 | } | |
194 | } | |
195 | } | |
196 | }, | |
197 | _ => eval_ast(&ast, &env), | |
198 | }; | |
199 | ||
200 | break; | |
201 | ||
202 | } // end 'tco loop | |
203 | ||
204 | ret | |
205 | } | |
206 | ||
207 | ||
208 | fn print(ast: &MalVal) -> String { | |
209 | ast.pr_str(true) | |
210 | } | |
211 | ||
212 | fn rep(str: &str, env: &Env) -> Result<String,MalErr> { | |
213 | let ast = read(str)?; | |
214 | let exp = eval(ast, env.clone())?; | |
215 | Ok(print(&exp)) | |
216 | } | |
217 | ||
218 | fn main() { | |
219 | let mut args = std::env::args(); | |
220 | let arg1 = args.nth(1); | |
221 | ||
222 | // `()` can be used when no completer is required | |
223 | let mut rl = Editor::<()>::new(); | |
224 | if rl.load_history(".mal-history").is_err() { | |
6c4cc8ad | 225 | eprintln!("No previous history."); |
4ef4b17c JM |
226 | } |
227 | ||
228 | // core.rs: defined using rust | |
229 | let repl_env = env_new(None); | |
230 | for (k, v) in core::ns() { | |
231 | env_sets(&repl_env, k, v); | |
232 | } | |
233 | env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect())); | |
234 | ||
235 | // core.mal: defined using the language itself | |
236 | let _ = rep("(def! not (fn* (a) (if a false true)))", &repl_env); | |
e6d41de4 | 237 | let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", &repl_env); |
4ef4b17c JM |
238 | |
239 | // Invoked with arguments | |
240 | if let Some(f) = arg1 { | |
241 | match rep(&format!("(load-file \"{}\")",f), &repl_env) { | |
242 | Ok(_) => std::process::exit(0), | |
243 | Err(e) => { | |
244 | println!("Error: {}", format_error(e)); | |
245 | std::process::exit(1); | |
246 | } | |
247 | } | |
248 | } | |
249 | ||
250 | // main repl loop | |
251 | loop { | |
252 | let readline = rl.readline("user> "); | |
253 | match readline { | |
254 | Ok(line) => { | |
255 | rl.add_history_entry(&line); | |
256 | rl.save_history(".mal-history").unwrap(); | |
257 | if line.len() > 0 { | |
258 | match rep(&line, &repl_env) { | |
259 | Ok(out) => println!("{}", out), | |
260 | Err(e) => println!("Error: {}", format_error(e)), | |
261 | } | |
262 | } | |
263 | }, | |
264 | Err(ReadlineError::Interrupted) => continue, | |
265 | Err(ReadlineError::Eof) => break, | |
266 | Err(err) => { | |
267 | println!("Error: {:?}", err); | |
268 | break | |
269 | } | |
270 | } | |
271 | } | |
272 | } | |
273 | ||
274 | // vim: ts=2:sw=2:expandtab |