diff --git a/src/cli/repl_lem.rs b/src/cli/repl_lem.rs index cf3fdce244..db2b95182f 100644 --- a/src/cli/repl_lem.rs +++ b/src/cli/repl_lem.rs @@ -207,9 +207,34 @@ impl ReplLEM { } } - #[inline] fn eval_expr(&mut self, expr_ptr: Ptr) -> Result<(Vec>, usize, Vec>)> { - evaluate_simple(expr_ptr, &mut self.store, self.limit) + let (ptrs, iterations, emitted) = evaluate_simple(expr_ptr, &mut self.store, self.limit)?; + match ptrs[2].tag() { + Tag::Cont(Terminal) => Ok((ptrs, iterations, emitted)), + t => { + let iterations_display = Self::pretty_iterations_display(iterations); + if t == &Tag::Cont(Error) { + bail!("Evaluation encountered an error after {iterations_display}") + } else { + bail!("Limit reached after {iterations_display}") + } + } + } + } + + fn eval_expr_allowing_error_continuation( + &mut self, + expr_ptr: Ptr, + ) -> Result<(Vec>, usize, Vec>)> { + let (ptrs, iterations, emitted) = evaluate_simple(expr_ptr, &mut self.store, self.limit)?; + if matches!(ptrs[2].tag(), Tag::Cont(Terminal) | Tag::Cont(Error)) { + Ok((ptrs, iterations, emitted)) + } else { + bail!( + "Limit reached after {}", + Self::pretty_iterations_display(iterations) + ) + } } fn eval_expr_and_memoize(&mut self, expr_ptr: Ptr) -> Result<(Vec>, usize)> { @@ -415,7 +440,7 @@ impl ReplLEM { } "assert-error" => { let first = self.peek1(cmd, args)?; - let (first_io, ..) = self.eval_expr(first)?; + let (first_io, ..) = self.eval_expr_allowing_error_continuation(first)?; if first_io[2].tag() != &Tag::Cont(Error) { eprintln!( "`assert-error` failed. {} doesn't result on evaluation error.", diff --git a/src/lem/eval.rs b/src/lem/eval.rs index 828e178bce..0f3ee65931 100644 --- a/src/lem/eval.rs +++ b/src/lem/eval.rs @@ -1,7 +1,7 @@ use anyhow::Result; use once_cell::sync::OnceCell; -use crate::{field::LurkField, func, tag::ContTag::*}; +use crate::{field::LurkField, func, state::initial_lurk_state, tag::ContTag::*}; use super::{interpreter::Frame, pointers::Ptr, store::Store, Func, Tag}; @@ -31,8 +31,20 @@ pub fn evaluate( let stop_cond = |output: &[Ptr]| { output[2] == Ptr::null(Tag::Cont(Terminal)) || output[2] == Ptr::null(Tag::Cont(Error)) }; + let state = initial_lurk_state(); + let log_fmt = |i: usize, ptrs: &[Ptr], store: &Store| { + format!( + "Frame: {}\n\tExpr: {}\n\tEnv: {}\n\tCont: {}", + i, + ptrs[0].fmt_to_string(store, state), + ptrs[1].fmt_to_string(store, state), + ptrs[2].fmt_to_string(store, state) + ) + }; + let input = &[expr, store.intern_nil(), Ptr::null(Tag::Cont(Outermost))]; - let (frames, iterations, _) = eval_step().call_until(input, store, stop_cond, limit)?; + let (frames, iterations, _) = + eval_step().call_until(input, store, stop_cond, limit, log_fmt)?; Ok((frames, iterations)) } @@ -1107,13 +1119,15 @@ mod tests { // Stop condition: the continuation is either terminal or error let stop_cond = |output: &[Ptr]| output[2] == terminal || output[2] == error; + let log_fmt = |_: usize, _: &[Ptr], _: &Store| String::default(); + let limit = 10000; let mut cs_prev = None; for (expr_in, expr_out) in pairs { let input = [expr_in, nil, outermost]; let (frames, _, paths) = eval_step - .call_until(&input, store, stop_cond, limit) + .call_until(&input, store, stop_cond, limit, log_fmt) .unwrap(); let last_frame = frames.last().expect("eval should add at least one frame"); assert_eq!(last_frame.output[0], expr_out); diff --git a/src/lem/interpreter.rs b/src/lem/interpreter.rs index 0a8ef6c032..181746d28f 100644 --- a/src/lem/interpreter.rs +++ b/src/lem/interpreter.rs @@ -487,12 +487,18 @@ impl Func { /// Calls a `Func` on an input until the stop contidion is satisfied, using the output of one /// iteration as the input of the next one. - pub fn call_until]) -> bool>( + pub fn call_until< + F: LurkField, + StopCond: Fn(&[Ptr]) -> bool, + LogFmt: Fn(usize, &[Ptr], &Store) -> String, + >( &self, args: &[Ptr], store: &mut Store, - stop_cond: Stop, + stop_cond: StopCond, limit: usize, + // TODO: make this argument optional + log_fmt: LogFmt, ) -> Result<(Vec>, usize, Vec)> { assert_eq!(self.input_params.len(), self.output_size); assert_eq!(self.input_params.len(), args.len()); @@ -502,34 +508,39 @@ impl Func { let mut frames = vec![]; let mut paths = vec![]; - for iterations in 0..limit { + let mut iterations = 0; + + log::info!("{}", &log_fmt(iterations, &input, store)); + + for _ in 0..limit { let preimages = Preimages::new_from_func(self); let (frame, path) = self.call(&input, store, preimages, &mut vec![])?; - let stop = stop_cond(&frame.output); - // Should frames take borrowed vectors instead, as to avoid cloning? - // Using AVec is a possibility, but to create a dynamic AVec, currently, - // requires 2 allocations since it must be created from a Vec and - // Vec -> Arc<[T]> uses `copy_from_slice`. input = frame.output.clone(); - frames.push(frame); - paths.push(path); - if stop { - // pushing a frame that can be padded to match the rc - let preimages = Preimages::new_from_func(self); - let (frame, path) = self.call(&input, store, preimages, &mut vec![])?; + iterations += 1; + log::info!("{}", &log_fmt(iterations, &input, store)); + if stop_cond(&frame.output) { frames.push(frame); paths.push(path); - return Ok((frames, iterations + 1, paths)); + break; } + frames.push(frame); + paths.push(path); } - bail!("Computation exceeded the limit of steps {limit}") + if iterations < limit { + // pushing a frame that can be padded + let preimages = Preimages::new_from_func(self); + let (frame, path) = self.call(&input, store, preimages, &mut vec![])?; + frames.push(frame); + paths.push(path); + } + Ok((frames, iterations, paths)) } - pub fn call_until_simple]) -> bool>( + pub fn call_until_simple]) -> bool>( &self, args: Vec>, store: &mut Store, - stop_cond: Stop, + stop_cond: StopCond, limit: usize, ) -> Result<(Vec>, usize, Vec>)> { assert_eq!(self.input_params.len(), self.output_size); @@ -538,13 +549,16 @@ impl Func { let mut input = args; let mut emitted = vec![]; - for iterations in 0..limit { + let mut iterations = 0; + + for _ in 0..limit { let (frame, _) = self.call(&input, store, Preimages::default(), &mut emitted)?; input = frame.output.clone(); + iterations += 1; if stop_cond(&frame.output) { - return Ok((input, iterations + 1, emitted)); + break; } } - bail!("Computation exceeded the limit of steps {limit}") + Ok((input, iterations, emitted)) } } diff --git a/src/lem/mod.rs b/src/lem/mod.rs index e3f05fe981..e53d6caf33 100644 --- a/src/lem/mod.rs +++ b/src/lem/mod.rs @@ -812,10 +812,14 @@ mod tests { let computed_num_constraints = func.num_constraints::(store); + let log_fmt = |_: usize, _: &[Ptr], _: &Store| String::default(); + let mut cs_prev = None; for input in inputs.into_iter() { let input = [input, nil, outermost]; - let (frames, ..) = func.call_until(&input, store, stop_cond, 10).unwrap(); + let (frames, ..) = func + .call_until(&input, store, stop_cond, 10, log_fmt) + .unwrap(); let mut cs; diff --git a/src/lem/store.rs b/src/lem/store.rs index 44868fc57a..52d22006ae 100644 --- a/src/lem/store.rs +++ b/src/lem/store.rs @@ -677,7 +677,7 @@ impl Ptr { _ => "".into(), }, Fun => match self.get_index3() { - None => "".into(), + None => "".into(), Some(idx) => { if let Some((arg, bod, _)) = store.fetch_3_ptrs(idx) { match bod.tag() { @@ -735,7 +735,7 @@ impl Ptr { None => "".into(), }, }, - Tag::Cont(_) => todo!(), + Tag::Cont(_) => "".into(), Tag::Ctrl(_) => unreachable!(), } }