use std::rc::Rc; use std::collections::BTreeMap; use std::fmt; use std::cell::RefCell; use anyhow::{anyhow,bail,Result}; // This first Simple Lisp really is // // No fexprs, no mutation, no continuations, no macros, no strings. // Int/Bool/Nil/Pair/Symbol/Closure/Prim. // // Figuring out GC between a JIT and Rust will be tricky. // Can start with a like tracing-JIT-into-bytecode // Replcing Env with pairs or somesuch would make JIT interop easier I think, because we wouldn't // have to deal with refcell, but then we would again for mutation. // Maybe doing all allocation on the Rust side with #[no_mangle] functions would make things easier // mmmm no let's make our own Box, Rc, maybe Arc // rustonomicon // What if we're cute and use the ID // like we will eventually use value tagging // like, use the same encoding // interned symbols and all #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)] pub struct ID { id: i64 } impl fmt::Display for ID { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.id) } } #[derive(Debug)] pub enum Form { Nil, Int(i32), Bool(bool), Symbol(String, RefCell>), Pair(Rc
, Rc, RefCell>), Closure(Vec, Rc>, Rc, ID), Prim(Prim), } #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Prim { Add, Sub, Mul, Div, Mod, Eq, Cons, Car, Cdr, } impl Form { fn my_eq(&self, o: &Rc) -> bool { match self { Form::Nil => o.is_nil(), Form::Int(i) => if let Ok(oi) = o.int() { *i == oi } else { false }, Form::Bool(b) => if let Ok(ob) = o.bool() { *b == ob } else { false }, Form::Symbol(s, _id) => if let Ok(os) = o.sym() { s == os } else { false }, Form::Pair(a,b,_id) => if let Ok((oa,ob)) = o.pair() { a.my_eq(&oa) && b.my_eq(&ob) } else { false }, Form::Closure(_, _, _, _) => false, Form::Prim(p) => match &**o { Form::Prim(op) => p == op, _ => false }, } } fn new_pair(car: Rc, cdr: Rc) -> Rc { Rc::new(Form::Pair(car, cdr, RefCell::new(None))) } fn new_nil() -> Rc { Rc::new(Form::Nil) } fn new_int(i: i32) -> Rc { Rc::new(Form::Int(i)) } fn new_bool(b: bool) -> Rc { Rc::new(Form::Bool(b)) } fn new_closure(params: Vec, env: Rc>, body: Rc, ctx: &mut Ctx) -> Rc { Rc::new(Form::Closure(params, env, body, ctx.alloc_id())) } fn truthy(&self) -> bool { match self { Form::Bool(b) => *b, Form::Nil => false, _ => true, } } fn bool(&self) -> Result { match self { Form::Bool(b) => Ok(*b), _ => Err(anyhow!("bool on not a bool")), } } fn int(&self) -> Result { match self { Form::Int(i) => Ok(*i), _ => Err(anyhow!("int on not a int")), } } fn prim(&self) -> Result { match self { Form::Prim(p) => Ok(*p), _ => Err(anyhow!("prim on not a prim")), } } fn sym(&self) -> Result<&str> { match self { Form::Symbol(s, _id) => Ok(s), _ => Err(anyhow!("sym on not a sym")), } } fn pair(&self) -> Result<(Rc,Rc)> { match self { Form::Pair(car, cdr, _id) => Ok((Rc::clone(car),Rc::clone(cdr))), _ => Err(anyhow!("pair on not a pair")), } } fn car(&self) -> Result> { match self { Form::Pair(car, _cdr, _id) => Ok(Rc::clone(car)), _ => Err(anyhow!("car on not a pair")), } } fn cdr(&self) -> Result> { match self { Form::Pair(_car, cdr, _id) => Ok(Rc::clone(cdr)), _ => Err(anyhow!("cdr on not a pair")), } } fn is_nil(&self) -> bool { match self { Form::Nil => true, _ => false, } } pub fn append(&self, x: Rc) -> Result> { match self { Form::Pair(car, cdr, _id) => cdr.append(x).map(|x| Rc::new(Form::Pair(Rc::clone(car), x, RefCell::new(None)))), Form::Nil => Ok(Rc::new(Form::Pair(x, Rc::new(Form::Nil), RefCell::new(None)))), _ => Err(anyhow!("append to not a pair")), } } } #[derive(Debug)] pub struct Env { u: Option>>, // split this into // BTreeMap // Vec so that traced code can refer by index m: BTreeMap> } impl Env { pub fn root_env() -> Rc> { Rc::new(RefCell::new(Env { u: None, m: [ ("+", Rc::new(Form::Prim(Prim::Add))), ("-", Rc::new(Form::Prim(Prim::Sub))), ("*", Rc::new(Form::Prim(Prim::Mul))), ("/", Rc::new(Form::Prim(Prim::Div))), ("%", Rc::new(Form::Prim(Prim::Mod))), ("cons", Rc::new(Form::Prim(Prim::Cons))), ("cdr", Rc::new(Form::Prim(Prim::Cdr))), ("car", Rc::new(Form::Prim(Prim::Car))), ("=", Rc::new(Form::Prim(Prim::Eq))), ("nil", Form::new_nil()), ].into_iter().map(|(s,p)| (s.to_owned(), p)).collect() })) } pub fn chain(o: &Rc>) -> Rc> { Rc::new(RefCell::new(Env { u: Some(Rc::clone(o)), m: BTreeMap::new(), })) } pub fn lookup(&self, s: &str) -> Result> { if let Some(r) = self.m.get(s) { Ok(Rc::clone(r)) } else if let Some(u) = &self.u { u.borrow().lookup(s) } else { bail!("lookup of {s} failed") } } pub fn define(&mut self, s: String, v: Rc) { // no mutation, shadowing in inner scope ok assert!(!self.m.contains_key(&s)); self.m.insert(s, v); } } #[derive(Debug)] enum Op { Guard { const_value: Rc, side: (Option>, Rc) }, Debug, Define { sym: String }, Const { con: Rc }, Lookup { sym: String }, Call(Vec), Loop(Vec), Return, } impl fmt::Display for Op { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Op::Guard { const_value, side } => write!(f, "Guard({const_value})"), Op::Debug => write!(f, "Debug"), Op::Define { sym } => write!(f, "Define({sym})"), Op::Const { con } => write!(f, "Const_{con}"), Op::Lookup { sym } => write!(f, "Lookup({sym})"), Op::Call(params) => write!(f, "Call({:?})", params), Op::Loop(params) => write!(f, "Loop({:?})", params), Op::Return => write!(f, "Return"), } } } #[derive(Debug)] struct Trace { id: ID, // needs to track which are constants ops: Vec, param_stack: Vec, } impl Trace { fn new(id: ID) -> Self { Trace { id, ops: vec![], param_stack: vec![] } } } impl fmt::Display for Trace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Trace for {} [", self.id)?; for op in &self.ops { write!(f, " {}", op)?; } write!(f, " ]")?; if !self.param_stack.is_empty() { write!(f, "[")?; for s in &self.param_stack { write!(f, " {}", s)?; } write!(f, " ]")?; } Ok(()) } } #[derive(Debug)] struct Ctx { id_counter: i64, func_calls: BTreeMap, tracing: Option, traces: BTreeMap, } impl fmt::Display for Ctx { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Ctx") } } impl Ctx { fn new() -> Ctx { Ctx { id_counter: 0, func_calls: BTreeMap::new(), tracing: None, traces: BTreeMap::new(), } } fn alloc_id(&mut self) -> ID { self.id_counter += 1; ID { id: self.id_counter } } fn trace_running(&self) -> bool { self.tracing.is_some() } fn trace_call_bit(&mut self) { if let Some(trace) = &mut self.tracing { trace.param_stack.push(trace.ops.len()-1); } } // Though I guess that means call start should recieve the parameters // also, for like variables, it should guard on what function // if dynamic, interacts with the constant tracking // 7 options // - not tracing, closure - do stats // - not tracing, prim - do nothing // - tracing, Constant Prim - inline prim // - tracing, Constant Closure - inline call // - tracing, Static, tail-self - emit loop // - tracing, Static,nontail-self- emit call // - tracing, Dynamic, other - emit call // // inline call is slightly tricky, have to add our own Env accounting // emit call is trickier, because we either have to stop or postpone tracing // use return stack, and count the post-return as it's own trace? // weirder, but would eventually jive with continuations better? // eh for now use trace stack in ctx and cont stack out, have them match? fn trace_call_start(&mut self, arg_len: usize, id: Option) { // Needs to take and use parameters for mid-trace // needs to guard on function called if non-constant if let Some(id) = id { let entry = self.func_calls.entry(id).or_insert(0); println!("tracing call start for {id}, has been called {} times so far", *entry); *entry += 1; if *entry > 1 && self.tracing.is_none() && self.traces.get(&id).is_none() { self.tracing = Some(Trace::new(id)); return; // don't record self, of course } } if let Some(trace) = &mut self.tracing { let f_params = trace.param_stack.split_off(trace.param_stack.len()-arg_len-1); // include function if let Some(id) = id { if trace.id == id { // check for tail recursion if trace.param_stack.is_empty() { trace.ops.push(Op::Loop(f_params)); println!("Ending trace at tail recursive call!"); println!("\t{}", trace); self.traces.insert(id, self.tracing.take().unwrap()); return; } else { // call, and also we have to suspend tracing? // can treat same as dynamic call if suspend, same thing really } } } // either inline (prim/closure) or dynamic call trace.ops.push(Op::Call(f_params)); } } fn trace_call_end(&mut self, id: ID) { // associate with it or something println!("tracing call end for {id}"); if let Some(trace) = &mut self.tracing { if trace.id == id { trace.ops.push(Op::Return); println!("Ending trace at end of call!"); println!("\t{}", trace); self.traces.insert(id, self.tracing.take().unwrap()); } } } fn trace_guard + std::fmt::Debug >(&mut self, value: T, other: impl Fn()->(Option>,Rc)) { println!("Tracing guard {value:?}"); if let Some(trace) = &mut self.tracing { trace.ops.push(Op::Guard { const_value: Rc::new(value.into()), side: other() }); } } fn trace_debug(&mut self) { if let Some(trace) = &mut self.tracing { trace.ops.push(Op::Debug); } } fn trace_define(&mut self, sym: &str) { if let Some(trace) = &mut self.tracing { trace.ops.push(Op::Define { sym: sym.to_owned() }); } } fn trace_lookup(&mut self, s: &str) { if let Some(trace) = &mut self.tracing { trace.ops.push(Op::Lookup { sym: s.to_owned() }); // constant depends on which env } } fn trace_constant(&mut self, c: &Rc) { if let Some(trace) = &mut self.tracing { trace.ops.push(Op::Const { con: Rc::clone(c) }); } } fn trace_lambda(&mut self, params: &[String], e: &Rc>, body: &Rc) { if let Some(trace) = &mut self.tracing { // TODO } } } #[derive(Clone,Debug)] enum Cont { MetaRet, Ret { e: Rc>, id: ID, c: Rc }, Eval { c: Rc }, Prim { s: &'static str, to_go: Rc, c: Rc }, Call { evaled: Vec>, to_go: Rc, c: Rc }, } pub fn eval(f: Rc) -> Result> { let mut ctx = Ctx::new(); let mut f = f; let mut e = Env::root_env(); let mut c = Cont::Eval { c: Rc::new(Cont::MetaRet) }; loop { match c { Cont::MetaRet => { println!("Ctx was {ctx}"); assert!(!ctx.trace_running()); return Ok(f); } Cont::Ret { e: ne, id, c: nc } => { ctx.trace_call_end(id); e = ne; c = (*nc).clone(); }, Cont::Prim { s, to_go, c: nc } => { match s { "if" => { let thn = to_go.car()?; let els = to_go.cdr()?.car()?; if f.truthy() { ctx.trace_guard(true, || (Some(Rc::clone(&els)), Rc::new(Cont::Eval { c: Rc::clone(&nc) }))); f = thn; } else { ctx.trace_guard(false, ||(Some(Rc::clone(&thn)), Rc::new(Cont::Eval { c: Rc::clone(&nc) }))); f = els; } c = Cont::Eval { c: nc }; }, "or" => { let other = to_go.car()?; if !f.truthy() { ctx.trace_guard(false, || (None, nc.clone())); f = other; c = Cont::Eval { c: nc }; } else { ctx.trace_guard(true, || (Some(Rc::clone(&other)), Rc::new(Cont::Eval { c: Rc::clone(&nc) }))); c = (*nc).clone(); } }, "and" => { let other = to_go.car()?; if f.truthy() { ctx.trace_guard(true, || (None, nc.clone())); f = other; c = Cont::Eval { c: nc }; } else { ctx.trace_guard(false, || (Some(Rc::clone(&other)), Rc::new(Cont::Eval { c: Rc::clone(&nc) }))); c = (*nc).clone(); } }, "begin" => { if to_go.is_nil() { c = (*nc).clone(); } else { f = to_go.car()?; c = Cont::Eval { c: Rc::new(Cont::Prim { s: "begin", to_go: to_go.cdr()?, c: nc }) }; } }, "debug" => { println!("Debug: {f}"); ctx.trace_debug(); c = (*nc).clone(); }, "define" => { let sym = to_go.sym()?.to_string(); ctx.trace_define(&sym); e.borrow_mut().define(sym, Rc::clone(&f)); c = (*nc).clone(); }, _ => { panic!("bad prim {s}"); } } }, Cont::Call { mut evaled, to_go, c: nc } => { ctx.trace_call_bit(); evaled.push(f); if to_go.is_nil() { // do call let arg_len = evaled.len() - 1; let mut evaled_iter = evaled.into_iter(); let comb = evaled_iter.next().unwrap(); match &*comb { Form::Closure(ps, ie, b, id) => { if ps.len() != arg_len { bail!("arguments length doesn't match"); } let new_env = Env::chain(&ie); for (name, value) in ps.iter().zip(evaled_iter) { new_env.borrow_mut().define(name.to_string(), value); } ctx.trace_call_start(arg_len, Some(*id)); c = Cont::Eval { c: Rc::new(Cont::Ret { e: Rc::clone(&e), id: *id, c: nc }) }; f = Rc::clone(&b); e = new_env; }, Form::Prim(p) => { ctx.trace_call_start(arg_len, None); let a = evaled_iter.next().unwrap(); f = match comb.prim().unwrap() { Prim::Car => a.car()?, Prim::Cdr => a.cdr()?, _ => { let b = evaled_iter.next().unwrap(); match comb.prim().unwrap() { Prim::Add => Form::new_int(a.int()? + b.int()?), Prim::Sub => Form::new_int(a.int()? - b.int()?), Prim::Mul => Form::new_int(a.int()? * b.int()?), Prim::Div => Form::new_int(a.int()? / b.int()?), Prim::Mod => Form::new_int(a.int()? % b.int()?), Prim::Cons => Form::new_pair(a, b), Prim::Eq => Form::new_bool(a.my_eq(&b)), _ => unreachable!(), } } }; c = (*nc).clone(); }, _ => { bail!("tried to call a non-comb {}", comb) }, } } else { f = to_go.car()?; c = Cont::Eval { c: Rc::new(Cont::Call { evaled, to_go: to_go.cdr()?, c: nc }) }; } } Cont::Eval { c: nc } => { let tmp = f; match &*tmp { Form::Symbol(s, _id) => { ctx.trace_lookup(s); f = e.borrow().lookup(s)?; c = (*nc).clone(); }, Form::Pair(car, cdr, _id) => { match &**car { Form::Symbol(s, _id) if s == "if" => { f = cdr.car()?; c = Cont::Eval { c: Rc::new(Cont::Prim { s: "if", to_go: cdr.cdr()?, c: nc }) }; } // and/or has to short-circut, so special form // just like Scheme (bad ;) ) Form::Symbol(s, _id) if s == "or" => { f = cdr.car()?; c = Cont::Eval { c: Rc::new(Cont::Prim { s: "or", to_go: cdr.cdr()?, c: nc }) }; } Form::Symbol(s, _id) if s == "and" => { f = cdr.car()?; c = Cont::Eval { c: Rc::new(Cont::Prim { s: "and", to_go: cdr.cdr()?, c: nc }) }; } Form::Symbol(s, _id) if s == "begin" => { f = cdr.car()?; c = Cont::Eval { c: Rc::new(Cont::Prim { s: "begin", to_go: cdr.cdr()?, c: nc }) }; } Form::Symbol(s, _id) if s == "debug" => { f = cdr.car()?; c = Cont::Eval { c: Rc::new(Cont::Prim { s: "debug", to_go: cdr.cdr()?, c: nc }) }; } // This is a fast and loose ~simple lisp~, so just go for it // and can have convention that this is always top levelish Form::Symbol(s, _id) if s == "define" => { // note the swap, evaluating the second not the first (define a value..) f = cdr.cdr()?.car()?; c = Cont::Eval { c: Rc::new(Cont::Prim { s: "define", to_go: cdr.car()?, c: nc }) }; } Form::Symbol(s, _id) if s == "quote" => { f = cdr.car()?; ctx.trace_constant(&f); c = (*nc).clone(); } // (lambda (a b) body) Form::Symbol(s, _id) if s == "lambda" => { let mut params_vec = vec![]; let mut params = cdr.car()?; while let Ok((ncar, ncdr)) = params.pair() { params_vec.push(ncar.sym()?.to_string()); params = ncdr; } let body = cdr.cdr()?.car()?; // Later on, the id of the closure should maybe be augmented // or replaced with the id of the code it was made out of? ctx.trace_lambda(¶ms_vec, &e, &body); f = Form::new_closure(params_vec, Rc::clone(&e), body, &mut ctx); c = (*nc).clone(); } _ => { f = Rc::clone(car); c = Cont::Eval { c: Rc::new(Cont::Call { evaled: vec![], to_go: Rc::clone(cdr), c: nc }) }; } } }, _ => { // value, no eval f = tmp; ctx.trace_constant(&f); c = (*nc).clone(); } } } } } } // optimized as a function based off side table of id keyed -> opt // that id might be nice for debugging too // Symbol ID's could actually be used for environment lookups // this is just interning // todo, strings not symbols? impl From for Form { fn from(item: String) -> Self { Form::Symbol(item, RefCell::new(None)) } } impl From<&str> for Form { fn from(item: &str) -> Self { Form::Symbol(item.to_owned(), RefCell::new(None)) } } impl From for Form { fn from(item: i32) -> Self { Form::Int(item) } } impl From for Form { fn from(item: bool) -> Self { Form::Bool(item) } } impl, B: Into> From<(A, B)> for Form { fn from(item: (A, B)) -> Self { Form::Pair(Rc::new(item.0.into()), Rc::new(item.1.into()), RefCell::new(None)) } } impl fmt::Display for Form { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Form::Nil => write!(f, "nil"), Form::Int(i) => write!(f, "{i}"), Form::Bool(b) => write!(f, "{b}"), Form::Symbol(s, _id) => write!(f, "'{s}"), Form::Pair(car, cdr, _id) => { write!(f, "({}", car)?; let mut traverse: Rc = Rc::clone(cdr); loop { match &*traverse { Form::Pair(ref carp, ref cdrp, ref _id) => { write!(f, " {}", carp)?; traverse = Rc::clone(cdrp); }, Form::Nil => { write!(f, ")")?; return Ok(()); }, x => { write!(f, ". {x})")?; return Ok(()); }, } } }, Form::Closure(params, inner_env, code, id) => { write!(f, "", id, params) } Form::Prim(p) => { match p { Prim::Add => write!(f, "+"), Prim::Sub => write!(f, "-"), Prim::Mul => write!(f, "*"), Prim::Div => write!(f, "/"), Prim::Mod => write!(f, "%"), Prim::Cons => write!(f, "cons"), Prim::Car => write!(f, "car"), Prim::Cdr => write!(f, "cdr"), Prim::Eq => write!(f, "="), } } } } }