perl: Avoid excessive copying in Mal::Sequence::rest and ::slice.
[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,_) => {
90ca8485 138 if l.len() == 0 { return Ok(ast); }
4ef4b17c
JM
139 match macroexpand(ast.clone(), &env) {
140 (true, Ok(new_ast)) => {
141 ast = new_ast;
142 continue 'tco;
143 }
144 (_, Err(e)) => return Err(e),
145 _ => (),
146 }
147
148 if l.len() == 0 { return Ok(ast); }
149 let a0 = &l[0];
150 match a0 {
151 Sym(ref a0sym) if a0sym == "def!" => {
152 env_set(&env, l[1].clone(), eval(l[2].clone(), env.clone())?)
153 },
154 Sym(ref a0sym) if a0sym == "let*" => {
155 env = env_new(Some(env.clone()));
156 let (a1, a2) = (l[1].clone(), l[2].clone());
157 match a1 {
158 List(ref binds,_) | Vector(ref binds,_) => {
159 for (b, e) in binds.iter().tuples() {
160 match b {
161 Sym(_) => {
162 let _ = env_set(&env, b.clone(),
163 eval(e.clone(), env.clone())?);
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" => {
179 Ok(l[1].clone())
180 },
181 Sym(ref a0sym) if a0sym == "quasiquote" => {
182 ast = quasiquote(&l[1]);
183 continue 'tco;
184 },
185 Sym(ref a0sym) if a0sym == "defmacro!" => {
186 let (a1, a2) = (l[1].clone(), l[2].clone());
187 let r = eval(a2, env.clone())?;
188 match r {
189 MalFunc{eval, ast, env, params, ..} => {
190 Ok(env_set(&env, a1.clone(),
191 MalFunc{eval: eval, ast: ast.clone(), env: env.clone(),
192 params: params.clone(), is_macro: true,
193 meta: Rc::new(Nil)})?)
194 },
195 _ => error("set_macro on non-function"),
196 }
197 },
198 Sym(ref a0sym) if a0sym == "macroexpand" => {
199 match macroexpand(l[1].clone(), &env) {
200 (_, Ok(new_ast)) => Ok(new_ast),
201 (_, e) => return e,
202 }
203 },
204 Sym(ref a0sym) if a0sym == "try*" => {
205 match eval(l[1].clone(), env.clone()) {
206 Err(ref e) if l.len() >= 3 => {
207 let exc = match e {
208 ErrMalVal(mv) => mv.clone(),
209 ErrString(s) => Str(s.to_string()),
210 };
211 match l[2].clone() {
212 List(c,_) => {
213 let catch_env = env_bind(Some(env.clone()),
214 list!(vec![c[1].clone()]),
215 vec![exc])?;
216 eval(c[2].clone(), catch_env)
217 },
218 _ => error("invalid catch block"),
219 }
220 },
221 res => res,
222 }
223 },
224 Sym(ref a0sym) if a0sym == "do" => {
225 match eval_ast(&list!(l[1..l.len()-1].to_vec()), &env)? {
226 List(_,_) => {
227 ast = l.last().unwrap_or(&Nil).clone();
228 continue 'tco;
229 },
230 _ => error("invalid do form"),
231 }
232 },
233 Sym(ref a0sym) if a0sym == "if" => {
234 let cond = eval(l[1].clone(), env.clone())?;
235 match cond {
236 Bool(false) | Nil if l.len() >= 4 => {
237 ast = l[3].clone();
238 continue 'tco;
239 },
240 Bool(false) | Nil => Ok(Nil),
241 _ if l.len() >= 3 => {
242 ast = l[2].clone();
243 continue 'tco;
244 },
245 _ => Ok(Nil)
246 }
247 },
248 Sym(ref a0sym) if a0sym == "fn*" => {
249 let (a1, a2) = (l[1].clone(), l[2].clone());
250 Ok(MalFunc{eval: eval, ast: Rc::new(a2), env: env,
251 params: Rc::new(a1), is_macro: false,
252 meta: Rc::new(Nil)})
253 },
254 Sym(ref a0sym) if a0sym == "eval" => {
255 ast = eval(l[1].clone(), env.clone())?;
256 while let Some(ref e) = env.clone().outer {
257 env = e.clone();
258 }
259 continue 'tco;
260 },
261 _ => {
262 match eval_ast(&ast, &env)? {
263 List(ref el,_) => {
264 let ref f = el[0].clone();
265 let args = el[1..].to_vec();
266 match f {
267 Func(_,_) => f.apply(args),
268 MalFunc{ast: mast, env: menv, params, ..} => {
269 let a = &**mast;
270 let p = &**params;
271 env = env_bind(Some(menv.clone()), p.clone(), args)?;
272 ast = a.clone();
273 continue 'tco;
274 },
275 _ => error("attempt to call non-function"),
276 }
277 },
278 _ => {
279 error("expected a list")
280 }
281 }
282 }
283 }
284 },
285 _ => eval_ast(&ast, &env),
286 };
287
288 break;
289
290 } // end 'tco loop
291
292 ret
293}
294
295// print
296fn print(ast: &MalVal) -> String {
297 ast.pr_str(true)
298}
299
300fn rep(str: &str, env: &Env) -> Result<String,MalErr> {
301 let ast = read(str)?;
302 let exp = eval(ast, env.clone())?;
303 Ok(print(&exp))
304}
305
306fn main() {
307 let mut args = std::env::args();
308 let arg1 = args.nth(1);
309
310 // `()` can be used when no completer is required
311 let mut rl = Editor::<()>::new();
312 if rl.load_history(".mal-history").is_err() {
6c4cc8ad 313 eprintln!("No previous history.");
4ef4b17c
JM
314 }
315
316 // core.rs: defined using rust
317 let repl_env = env_new(None);
318 for (k, v) in core::ns() {
319 env_sets(&repl_env, k, v);
320 }
321 env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect()));
322
323 // core.mal: defined using the language itself
324 let _ = rep("(def! not (fn* (a) (if a false true)))", &repl_env);
e6d41de4 325 let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", &repl_env);
4ef4b17c 326 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
JM
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