1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-21 02:27:10 +03:00
mal/rust/step8_macros.rs
Joel Martin 4ef4b17cd0 rust: Update rust and update/refactor implementation
This rewrites the rust implementation to use many new features of the
current version of rust.

The refactor is much more concise (only 2/3rds the size) and switches
to using a lot of the more functional features (iterators, closures,
etc) that have been added or improved in rust.

Unfortunately, the implementation is a fair bit slower (about 30% on
perf3). It's not clear why this is the case but concision and being
more idiomatic wins over performance.
2018-07-13 17:21:00 -05:00

344 lines
9.6 KiB
Rust

use std::rc::Rc;
//use std::collections::HashMap;
use fnv::FnvHashMap;
use itertools::Itertools;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate itertools;
extern crate fnv;
extern crate rustyline;
use rustyline::error::ReadlineError;
use rustyline::Editor;
#[macro_use]
mod types;
use types::{MalVal,MalArgs,MalRet,MalErr,error,format_error};
use types::MalVal::{Nil,Bool,Str,Sym,List,Vector,Hash,Func,MalFunc};
mod reader;
mod printer;
mod env;
use env::{Env,env_new,env_bind,env_find,env_get,env_set,env_sets};
#[macro_use]
mod core;
// read
fn read(str: &str) -> MalRet {
reader::read_str(str.to_string())
}
// eval
fn quasiquote(ast: &MalVal) -> MalVal {
match ast {
List(ref v,_) | Vector(ref v,_) if v.len() > 0 => {
let a0 = &v[0];
match a0 {
Sym(ref s) if s == "unquote" => v[1].clone(),
_ => {
match a0 {
List(ref v0,_) | Vector(ref v0,_) if v0.len() > 0 => {
match v0[0] {
Sym(ref s) if s == "splice-unquote" => {
list![Sym("concat".to_string()),
v0[1].clone(),
quasiquote(&list!(v[1..].to_vec()))]
},
_ => {
list![Sym("cons".to_string()),
quasiquote(a0),
quasiquote(&list!(v[1..].to_vec()))]
},
}
},
_ => {
list![Sym("cons".to_string()),
quasiquote(a0),
quasiquote(&list!(v[1..].to_vec()))]
}
}
}
}
},
_ => list![Sym("quote".to_string()), ast.clone()]
}
}
fn is_macro_call(ast: &MalVal, env: &Env) -> Option<(MalVal,MalArgs)> {
match ast {
List(v,_) => {
match v[0] {
Sym(ref s) => {
match env_find(env, s) {
Some(e) => {
match env_get(&e, &v[0]) {
Ok(f @ MalFunc{is_macro: true, ..}) => {
Some((f, v[1..].to_vec()))
},
_ => None,
}
},
_ => None,
}
},
_ => None,
}
},
_ => None,
}
}
fn macroexpand(mut ast: MalVal, env: &Env) -> (bool, MalRet) {
let mut was_expanded = false;
while let Some((mf, args)) = is_macro_call(&ast, env) {
//println!("macroexpand 1: {:?}", ast);
ast = match mf.apply(args) {
Err(e) => return (false, Err(e)),
Ok(a) => a,
};
//println!("macroexpand 2: {:?}", ast);
was_expanded = true;
}
((was_expanded, Ok(ast)))
}
fn eval_ast(ast: &MalVal, env: &Env) -> MalRet {
match ast {
Sym(_) => Ok(env_get(&env, &ast)?),
List(v,_) => {
let mut lst: MalArgs = vec![];
for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) }
Ok(list!(lst))
},
Vector(v,_) => {
let mut lst: MalArgs = vec![];
for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) }
Ok(vector!(lst))
},
Hash(hm,_) => {
let mut new_hm: FnvHashMap<String,MalVal> = FnvHashMap::default();
for (k,v) in hm.iter() {
new_hm.insert(k.to_string(), eval(v.clone(), env.clone())?);
}
Ok(Hash(Rc::new(new_hm),Rc::new(Nil)))
},
_ => Ok(ast.clone()),
}
}
fn eval(mut ast: MalVal, mut env: Env) -> MalRet {
let ret: MalRet;
'tco: loop {
ret = match ast.clone() {
List(l,_) => {
match macroexpand(ast.clone(), &env) {
(true, Ok(new_ast)) => {
ast = new_ast;
continue 'tco;
}
(_, Err(e)) => return Err(e),
_ => (),
}
if l.len() == 0 { return Ok(ast); }
let a0 = &l[0];
match a0 {
Sym(ref a0sym) if a0sym == "def!" => {
env_set(&env, l[1].clone(), eval(l[2].clone(), env.clone())?)
},
Sym(ref a0sym) if a0sym == "let*" => {
env = env_new(Some(env.clone()));
let (a1, a2) = (l[1].clone(), l[2].clone());
match a1 {
List(ref binds,_) | Vector(ref binds,_) => {
for (b, e) in binds.iter().tuples() {
match b {
Sym(_) => {
let _ = env_set(&env, b.clone(),
eval(e.clone(), env.clone())?);
},
_ => {
return error("let* with non-Sym binding");
}
}
}
},
_ => {
return error("let* with non-List bindings");
}
};
ast = a2;
continue 'tco;
},
Sym(ref a0sym) if a0sym == "quote" => {
Ok(l[1].clone())
},
Sym(ref a0sym) if a0sym == "quasiquote" => {
ast = quasiquote(&l[1]);
continue 'tco;
},
Sym(ref a0sym) if a0sym == "defmacro!" => {
let (a1, a2) = (l[1].clone(), l[2].clone());
let r = eval(a2, env.clone())?;
match r {
MalFunc{eval, ast, env, params, ..} => {
Ok(env_set(&env, a1.clone(),
MalFunc{eval: eval, ast: ast.clone(), env: env.clone(),
params: params.clone(), is_macro: true,
meta: Rc::new(Nil)})?)
},
_ => error("set_macro on non-function"),
}
},
Sym(ref a0sym) if a0sym == "macroexpand" => {
match macroexpand(l[1].clone(), &env) {
(_, Ok(new_ast)) => Ok(new_ast),
(_, e) => return e,
}
},
Sym(ref a0sym) if a0sym == "do" => {
match eval_ast(&list!(l[1..l.len()-1].to_vec()), &env)? {
List(_,_) => {
ast = l.last().unwrap_or(&Nil).clone();
continue 'tco;
},
_ => error("invalid do form"),
}
},
Sym(ref a0sym) if a0sym == "if" => {
let cond = eval(l[1].clone(), env.clone())?;
match cond {
Bool(false) | Nil if l.len() >= 4 => {
ast = l[3].clone();
continue 'tco;
},
Bool(false) | Nil => Ok(Nil),
_ if l.len() >= 3 => {
ast = l[2].clone();
continue 'tco;
},
_ => Ok(Nil)
}
},
Sym(ref a0sym) if a0sym == "fn*" => {
let (a1, a2) = (l[1].clone(), l[2].clone());
Ok(MalFunc{eval: eval, ast: Rc::new(a2), env: env,
params: Rc::new(a1), is_macro: false,
meta: Rc::new(Nil)})
},
Sym(ref a0sym) if a0sym == "eval" => {
ast = eval(l[1].clone(), env.clone())?;
while let Some(ref e) = env.clone().outer {
env = e.clone();
}
continue 'tco;
},
_ => {
match eval_ast(&ast, &env)? {
List(ref el,_) => {
let ref f = el[0].clone();
let args = el[1..].to_vec();
match f {
Func(_,_) => f.apply(args),
MalFunc{ast: mast, env: menv, params, ..} => {
let a = &**mast;
let p = &**params;
env = env_bind(Some(menv.clone()), p.clone(), args)?;
ast = a.clone();
continue 'tco;
},
_ => error("attempt to call non-function"),
}
},
_ => {
error("expected a list")
}
}
}
}
},
_ => eval_ast(&ast, &env),
};
break;
} // end 'tco loop
ret
}
// print
fn print(ast: &MalVal) -> String {
ast.pr_str(true)
}
fn rep(str: &str, env: &Env) -> Result<String,MalErr> {
let ast = read(str)?;
let exp = eval(ast, env.clone())?;
Ok(print(&exp))
}
fn main() {
let mut args = std::env::args();
let arg1 = args.nth(1);
// `()` can be used when no completer is required
let mut rl = Editor::<()>::new();
if rl.load_history(".mal-history").is_err() {
println!("No previous history.");
}
// core.rs: defined using rust
let repl_env = env_new(None);
for (k, v) in core::ns() {
env_sets(&repl_env, k, v);
}
env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect()));
// core.mal: defined using the language itself
let _ = rep("(def! not (fn* (a) (if a false true)))", &repl_env);
let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", &repl_env);
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);
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);
// Invoked with arguments
if let Some(f) = arg1 {
match rep(&format!("(load-file \"{}\")",f), &repl_env) {
Ok(_) => std::process::exit(0),
Err(e) => {
println!("Error: {}", format_error(e));
std::process::exit(1);
}
}
}
// main repl loop
loop {
let readline = rl.readline("user> ");
match readline {
Ok(line) => {
rl.add_history_entry(&line);
rl.save_history(".mal-history").unwrap();
if line.len() > 0 {
match rep(&line, &repl_env) {
Ok(out) => println!("{}", out),
Err(e) => println!("Error: {}", format_error(e)),
}
}
},
Err(ReadlineError::Interrupted) => continue,
Err(ReadlineError::Eof) => break,
Err(err) => {
println!("Error: {:?}", err);
break
}
}
}
}
// vim: ts=2:sw=2:expandtab