mirror of
https://github.com/kanaka/mal.git
synced 2024-11-11 00:52:44 +03:00
4ef4b17cd0
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.
233 lines
6.2 KiB
Rust
233 lines
6.2 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_get,env_set,env_sets};
|
|
#[macro_use]
|
|
mod core;
|
|
|
|
// read
|
|
fn read(str: &str) -> MalRet {
|
|
reader::read_str(str.to_string())
|
|
}
|
|
|
|
// eval
|
|
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,_) => {
|
|
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 == "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);
|
|
|
|
// 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
|