diff --git a/src/eval.rs b/src/eval.rs index 62ef25099f..a3d2478c9f 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -7,7 +7,20 @@ use std::collections::HashMap; use std::rc::{Rc, Weak}; use term::{RichTerm, Term}; -pub type Enviroment = HashMap>>; +pub type Enviroment = HashMap>, IdentKind)>; +pub type CallStack = Vec; + +#[derive(Debug, PartialEq, Clone)] +pub enum StackElem { + App(Option<(usize, usize)>), + Var(IdentKind, Ident, Option<(usize, usize)>), +} + +#[derive(Debug, PartialEq, Clone)] +pub enum IdentKind { + Let(), + Lam(), +} #[derive(Clone, Debug, PartialEq)] pub struct Closure { @@ -26,7 +39,7 @@ impl Closure { #[derive(Debug, PartialEq)] pub enum EvalError { - BlameError(Label), + BlameError(Label, Option), TypeError(String), } @@ -40,13 +53,14 @@ pub fn eval(t0: RichTerm) -> Result { body: t0, env: empty_env, }; + let mut call_stack = CallStack::new(); let mut stack = Stack::new(); loop { let Closure { body: RichTerm { term: boxed_term, - pos: _, + pos, }, mut env, } = clos; @@ -54,11 +68,12 @@ pub fn eval(t0: RichTerm) -> Result { match term { // Var Term::Var(x) => { - let mut thunk = Rc::clone(env.get(&x).expect(&format!("Unbound variable {:?}", x))); + let (thunk, id_kind) = env.remove(&x).expect(&format!("Unbound variable {:?}", x)); std::mem::drop(env); // thunk may be a 1RC pointer if !is_value(&thunk.borrow().body.term) { stack.push_thunk(Rc::downgrade(&thunk)); } + call_stack.push(StackElem::Var(id_kind, x, pos)); match Rc::try_unwrap(thunk) { Ok(c) => { // thunk was the only strong ref to the closure @@ -72,10 +87,13 @@ pub fn eval(t0: RichTerm) -> Result { } // App Term::App(t1, t2) => { - stack.push_arg(Closure { - body: t2, - env: env.clone(), - }); + stack.push_arg( + Closure { + body: t2, + env: env.clone(), + }, + pos, + ); clos = Closure { body: t1, env }; } // Let @@ -84,32 +102,38 @@ pub fn eval(t0: RichTerm) -> Result { body: s, env: env.clone(), })); - env.insert(x, Rc::clone(&thunk)); + env.insert(x, (Rc::clone(&thunk), IdentKind::Let())); clos = Closure { body: t, env: env }; } // Unary Operation Term::Op1(op, t) => { - stack.push_op_cont(OperationCont::Op1(op)); + stack.push_op_cont(OperationCont::Op1(op), call_stack.len()); clos = Closure { body: t, env }; } // Binary Operation Term::Op2(op, fst, snd) => { - stack.push_op_cont(OperationCont::Op2First( - op, - Closure { - body: snd, - env: env.clone(), - }, - )); + stack.push_op_cont( + OperationCont::Op2First( + op, + Closure { + body: snd, + env: env.clone(), + }, + ), + call_stack.len(), + ); clos = Closure { body: fst, env }; } // Promise and Assume Term::Promise(ty, l, t) | Term::Assume(ty, l, t) => { - stack.push_arg(Closure { - body: t, - env: env.clone(), - }); - stack.push_arg(Closure::atomic_closure(RichTerm::new(Term::Lbl(l)))); + stack.push_arg( + Closure { + body: t, + env: env.clone(), + }, + None, + ); + stack.push_arg(Closure::atomic_closure(RichTerm::new(Term::Lbl(l))), None); clos = Closure { body: ty.contract(), env, @@ -129,20 +153,21 @@ pub fn eval(t0: RichTerm) -> Result { } } } else { - clos = continuate_operation( - stack.pop_op_cont().expect("Condition already checked"), - clos, - &mut stack, - )?; + let mut cont_result = continuate_operation(clos, &mut stack, &mut call_stack); + + if let Err(EvalError::BlameError(l, _)) = cont_result { + return Err(EvalError::BlameError(l, Some(call_stack))); + } + clos = cont_result?; } } // Call Term::Fun(x, t) => { if 0 < stack.count_args() { - let thunk = Rc::new(RefCell::new( - stack.pop_arg().expect("Condition already checked."), - )); - env.insert(x, thunk); + let (arg, pos) = stack.pop_arg().expect("Condition already checked."); + call_stack.push(StackElem::App(pos)); + let thunk = Rc::new(RefCell::new(arg)); + env.insert(x, (thunk, IdentKind::Lam())); clos = Closure { body: t, env } } else { return Ok(Term::Fun(x, t)); @@ -186,10 +211,13 @@ mod tests { polarity: false, path: TyPath::Nil(), }; - assert_eq!( - Err(EvalError::BlameError(label.clone())), - eval(Term::Op1(UnaryOp::Blame(), Term::Lbl(label).into()).into()) - ); + if let Err(EvalError::BlameError(l, _)) = + eval(Term::Op1(UnaryOp::Blame(), Term::Lbl(label.clone()).into()).into()) + { + assert_eq!(l, label); + } else { + panic!("This evaluation should've returned a BlameError!"); + } } #[test] diff --git a/src/examples/higherOrder.ncl b/src/examples/higherOrder.ncl index f517c8132a..3f2f89ed83 100644 --- a/src/examples/higherOrder.ncl +++ b/src/examples/higherOrder.ncl @@ -1,2 +1,4 @@ -let f = Assume((Num -> Num) -> Num, fun g => g 3) in -f (fun x => true) \ No newline at end of file +let f = fun g => Assume(Num, g true) in +let id = fun x => x in +let id2 = fun y => y in +id2 (id (f id)) diff --git a/src/examples/higherOrder2.ncl b/src/examples/higherOrder2.ncl new file mode 100644 index 0000000000..45d1ca023b --- /dev/null +++ b/src/examples/higherOrder2.ncl @@ -0,0 +1,4 @@ +let f = fun g => Assume(Num, g true) in +let id = fun x => x in +let id2 = fun y => id y in +id2 (f id) diff --git a/src/examples/lowerOrder.ncl b/src/examples/lowerOrder.ncl new file mode 100644 index 0000000000..7e82d8b048 --- /dev/null +++ b/src/examples/lowerOrder.ncl @@ -0,0 +1,3 @@ +let f = fun z => z + 3 in +let check = fun y => Assume(Bool, y) in +check (f 4) diff --git a/src/examples/trace.ncl b/src/examples/trace.ncl new file mode 100644 index 0000000000..5f56d96d60 --- /dev/null +++ b/src/examples/trace.ncl @@ -0,0 +1,2 @@ +let map = Assume((Num -> Num) -> Num, fun funky => (funky 3) + (funky false)) in +map (fun yak => yak + 1) diff --git a/src/examples/trace2.ncl b/src/examples/trace2.ncl new file mode 100644 index 0000000000..8e82fc3649 --- /dev/null +++ b/src/examples/trace2.ncl @@ -0,0 +1,5 @@ +let foo = fun x => Assume(Num, x) + 3 in +let id = fun x => x in +let bar = fun b => fun y => if b (id true) then foo y else -1 in +let k = fun y => fun x => Assume(Num, y) + (foo x) in +bar (k (id 4)) false diff --git a/src/examples/trace3.ncl b/src/examples/trace3.ncl new file mode 100644 index 0000000000..1d8cdbeb4e --- /dev/null +++ b/src/examples/trace3.ncl @@ -0,0 +1,4 @@ +let foo = 3 in +let bar = foo in +let bleh = bar in +Assume(Bool, bleh) diff --git a/src/operation.rs b/src/operation.rs index 314dd363e4..8bf3a6cc2a 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -1,4 +1,4 @@ -use eval::{Closure, EvalError}; +use eval::{CallStack, Closure, EvalError}; use label::TyPath; use stack::Stack; use term::{BinaryOp, RichTerm, Term, UnaryOp}; @@ -11,15 +11,17 @@ pub enum OperationCont { } pub fn continuate_operation( - cont: OperationCont, mut clos: Closure, stack: &mut Stack, + call_stack: &mut CallStack, ) -> Result { + let (cont, cs_len) = stack.pop_op_cont().expect("Condition already checked"); + call_stack.truncate(cs_len); match cont { OperationCont::Op1(u_op) => process_unary_operation(u_op, clos, stack), OperationCont::Op2First(b_op, mut snd_clos) => { std::mem::swap(&mut clos, &mut snd_clos); - stack.push_op_cont(OperationCont::Op2Second(b_op, snd_clos)); + stack.push_op_cont(OperationCont::Op2Second(b_op, snd_clos), cs_len); Ok(clos) } OperationCont::Op2Second(b_op, fst_clos) => { @@ -41,8 +43,8 @@ fn process_unary_operation( UnaryOp::Ite() => { if let Term::Bool(b) = *t { if stack.count_args() >= 2 { - let fst = stack.pop_arg().expect("Condition already checked."); - let snd = stack.pop_arg().expect("Condition already checked."); + let (fst, _) = stack.pop_arg().expect("Condition already checked."); + let (snd, _) = stack.pop_arg().expect("Condition already checked."); Ok(if b { fst } else { snd }) } else { @@ -83,7 +85,7 @@ fn process_unary_operation( } UnaryOp::Blame() => { if let Term::Lbl(l) = *t { - Err(EvalError::BlameError(l)) + Err(EvalError::BlameError(l, None)) } else { Err(EvalError::TypeError(format!( "Expected Label, got {:?}", @@ -171,7 +173,7 @@ fn process_binary_operation( #[cfg(test)] mod tests { use super::*; - use eval::Enviroment; + use eval::{CallStack, Enviroment}; use std::collections::HashMap; fn some_env() -> Enviroment { @@ -182,15 +184,18 @@ mod tests { fn ite_operation() { let cont = OperationCont::Op1(UnaryOp::Ite()); let mut stack = Stack::new(); - stack.push_arg(Closure::atomic_closure(Term::Num(5.0).into())); - stack.push_arg(Closure::atomic_closure(Term::Num(46.0).into())); + stack.push_arg(Closure::atomic_closure(Term::Num(5.0).into()), None); + stack.push_arg(Closure::atomic_closure(Term::Num(46.0).into()), None); let mut clos = Closure { body: Term::Bool(true).into(), env: some_env(), }; - clos = continuate_operation(cont, clos, &mut stack).unwrap(); + stack.push_op_cont(cont, 0); + let mut call_stack = CallStack::new(); + + clos = continuate_operation(clos, &mut stack, &mut call_stack).unwrap(); assert_eq!( clos, @@ -217,8 +222,10 @@ mod tests { env: some_env(), }; let mut stack = Stack::new(); + stack.push_op_cont(cont, 0); + let mut call_stack = CallStack::new(); - clos = continuate_operation(cont, clos, &mut stack).unwrap(); + clos = continuate_operation(clos, &mut stack, &mut call_stack).unwrap(); assert_eq!( clos, @@ -230,12 +237,15 @@ mod tests { assert_eq!(1, stack.count_conts()); assert_eq!( - OperationCont::Op2Second( - BinaryOp::Plus(), - Closure { - body: Term::Num(7.0).into(), - env: some_env(), - } + ( + OperationCont::Op2Second( + BinaryOp::Plus(), + Closure { + body: Term::Num(7.0).into(), + env: some_env(), + } + ), + 0 ), stack.pop_op_cont().expect("Condition already checked.") ); @@ -255,8 +265,10 @@ mod tests { env: some_env(), }; let mut stack = Stack::new(); + stack.push_op_cont(cont, 0); + let mut call_stack = CallStack::new(); - clos = continuate_operation(cont, clos, &mut stack).unwrap(); + clos = continuate_operation(clos, &mut stack, &mut call_stack).unwrap(); assert_eq!( clos, diff --git a/src/program.rs b/src/program.rs index e962d5056e..a6649b5ed7 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,4 +1,5 @@ -use eval::{eval, EvalError}; +use eval::{eval, CallStack, EvalError, IdentKind, StackElem}; +use identifier::Ident; use label::{Label, TyPath}; use parser; use std::fs; @@ -41,7 +42,7 @@ impl Program { let t = self.parse()?; match eval(t) { Ok(t) => Ok(t), - Err(EvalError::BlameError(l)) => Err(self.process_blame(l)), + Err(EvalError::BlameError(l, cs)) => Err(self.process_blame(l, cs)), Err(EvalError::TypeError(s)) => Err(s), } } @@ -89,23 +90,27 @@ impl Program { } } - fn process_blame(&mut self, l: Label) -> String { + fn process_blame(&mut self, l: Label, cs_opt: Option) -> String { let mut s = String::new(); s.push_str("Reached a blame label, some cast went terribly wrong\n"); s.push_str(" Tag:\n"); s.push_str(&l.tag); s.push_str("\n"); - let (linef, colf) = self.get_line_and_col(l.l); - let (linet, colt) = self.get_line_and_col(l.r); - if linef == linet { - s.push_str(&format!( - " Line: {} Columns: {} to {}\n", - linef, colf, colt - )); - } else { - s.push_str(&format!(" Line: {} Column: {}\n", linef, colf)); - s.push_str(&format!(" to Line: {} Column: {}\n", linet, colt)); + + let pos_from = self.get_line_and_col(l.l); + let pos_to = self.get_line_and_col(l.r); + if let (Some((linef, colf)), Some((linet, colt))) = (pos_from, pos_to) { + if linef == linet { + s.push_str(&format!( + " Line: {} Columns: {} to {}\n", + linef, colf, colt + )); + } else { + s.push_str(&format!(" Line: {} Column: {}\n", linef, colf)); + s.push_str(&format!(" to Line: {} Column: {}\n", linet, colt)); + } } + s.push_str(&format!(" Polarity: {}\n", l.polarity)); if l.polarity { s.push_str(" The blame is on the value (positive blame)\n"); @@ -115,15 +120,61 @@ impl Program { if l.path != TyPath::Nil() { s.push_str(&format!(" Path: {:?}\n", l.path)); } + + if let Some(cs) = cs_opt { + s.push_str("\nCallStack:\n=========\n"); + s = self.show_call_stack(s, cs); + } + s + } + + fn show_call_stack(&mut self, mut s: String, mut cs: CallStack) -> String { + for e in cs.drain(..).rev() { + match e { + StackElem::App(Some((_l, _r))) => { + // I'm not sure this App stack is really useful, + // will leave it hanging for now + // + // if let Some((linef, colf)) = self.get_line_and_col(l) { + // s.push_str(&format!( + // " Applied to a term on line: {} col: {}\n", + // linef, colf + // )); + // } + } + StackElem::Var(IdentKind::Let(), Ident(x), Some((l, _r))) => { + if let Some((linef, colf)) = self.get_line_and_col(l) { + s.push_str(&format!( + "On a call to {} on line: {} col: {}\n", + x, linef, colf + )); + } + } + StackElem::Var(IdentKind::Lam(), Ident(x), Some((l, _r))) => { + if let Some((linef, colf)) = self.get_line_and_col(l) { + s.push_str(&format!( + " Bound to {} on line: {} col: {}\n", + x, linef, colf + )); + } + } + _ => {} + } + } s } - fn get_line_and_col(&mut self, b: usize) -> (usize, usize) { + fn get_line_and_col(&mut self, b: usize) -> Option<(usize, usize)> { let buffer = self.read().unwrap(); let mut line = 1; let mut col = 1; let mut so_far = Self::contracts().len(); + if b <= so_far { + // TODO Right now we have some stuff that is just pasted on + // every file, this check should change and location should be more reliable + return None; + } for byte in buffer.bytes() { so_far = so_far + 1; col = col + 1; @@ -136,7 +187,7 @@ impl Program { } } - (line, col) + Some((line, col)) } fn contracts() -> String { @@ -255,5 +306,4 @@ Promise(Bool, 5) panic!("This expression should return an error!."); } } - } diff --git a/src/stack.rs b/src/stack.rs index 61d6eb4035..4fa23315fe 100644 --- a/src/stack.rs +++ b/src/stack.rs @@ -5,33 +5,33 @@ use std::rc::Weak; #[derive(Debug)] pub enum Marker { - Arg(Closure), + Arg(Closure, Option<(usize, usize)>), Thunk(Weak>), - Cont(OperationCont), + Cont(OperationCont, usize /*callStack size*/), } impl Marker { pub fn is_arg(&self) -> bool { match *self { - Marker::Arg(_) => true, + Marker::Arg(_, _) => true, Marker::Thunk(_) => false, - Marker::Cont(_) => false, + Marker::Cont(_, _) => false, } } pub fn is_thunk(&self) -> bool { match *self { - Marker::Arg(_) => false, + Marker::Arg(_, _) => false, Marker::Thunk(_) => true, - Marker::Cont(_) => false, + Marker::Cont(_, _) => false, } } pub fn is_cont(&self) -> bool { match *self { - Marker::Arg(_) => false, + Marker::Arg(_, _) => false, Marker::Thunk(_) => false, - Marker::Cont(_) => true, + Marker::Cont(_, _) => true, } } } @@ -81,21 +81,21 @@ impl Stack { Stack::count(self, Marker::is_cont) } - pub fn push_arg(&mut self, arg: Closure) { - self.0.push(Marker::Arg(arg)) + pub fn push_arg(&mut self, arg: Closure, pos: Option<(usize, usize)>) { + self.0.push(Marker::Arg(arg, pos)) } pub fn push_thunk(&mut self, thunk: Weak>) { self.0.push(Marker::Thunk(thunk)) } - pub fn push_op_cont(&mut self, cont: OperationCont) { - self.0.push(Marker::Cont(cont)) + pub fn push_op_cont(&mut self, cont: OperationCont, len: usize) { + self.0.push(Marker::Cont(cont, len)) } - pub fn pop_arg(&mut self) -> Option { + pub fn pop_arg(&mut self) -> Option<(Closure, Option<(usize, usize)>)> { match self.0.pop() { - Some(Marker::Arg(arg)) => Some(arg), + Some(Marker::Arg(arg, pos)) => Some((arg, pos)), Some(m) => { self.0.push(m); None @@ -115,9 +115,9 @@ impl Stack { } } - pub fn pop_op_cont(&mut self) -> Option { + pub fn pop_op_cont(&mut self) -> Option<(OperationCont, usize)> { match self.0.pop() { - Some(Marker::Cont(cont)) => Some(cont), + Some(Marker::Cont(cont, len)) => Some((cont, len)), Some(m) => { self.0.push(m); None @@ -142,7 +142,7 @@ mod tests { } fn some_arg_marker() -> Marker { - Marker::Arg(some_closure()) + Marker::Arg(some_closure(), None) } fn some_thunk_marker() -> Marker { @@ -151,7 +151,7 @@ mod tests { } fn some_cont_marker() -> Marker { - Marker::Cont(some_cont()) + Marker::Cont(some_cont(), 42) } #[test] @@ -166,10 +166,10 @@ mod tests { let mut s = Stack::new(); assert_eq!(0, s.count_args()); - s.push_arg(some_closure()); - s.push_arg(some_closure()); + s.push_arg(some_closure(), None); + s.push_arg(some_closure(), None); assert_eq!(2, s.count_args()); - assert_eq!(some_closure(), s.pop_arg().expect("Already checked")); + assert_eq!(some_closure(), s.pop_arg().expect("Already checked").0); assert_eq!(1, s.count_args()); } @@ -190,10 +190,10 @@ mod tests { let mut s = Stack::new(); assert_eq!(0, s.count_conts()); - s.push_op_cont(some_cont()); - s.push_op_cont(some_cont()); + s.push_op_cont(some_cont(), 3); + s.push_op_cont(some_cont(), 4); assert_eq!(2, s.count_conts()); - assert_eq!(some_cont(), s.pop_op_cont().expect("Already checked")); + assert_eq!((some_cont(), 4), s.pop_op_cont().expect("Already checked")); assert_eq!(1, s.count_conts()); }