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