Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to gather print statements #179

Merged
merged 1 commit into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions src/builtins/debugging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("print", (print, MAX_ARGS));
}

// Symbol analyzer must ensure that vars used by print are defined before
// the print statement. Scheduler must ensure the above constraint.
// Additionally interpreter must allow undefined inputs to print.
fn print(span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
pub fn print_to_string(
span: &Span,
_params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<String> {
if args.len() > MAX_ARGS as usize {
bail!(span.error("print supports up to 100 arguments"));
}
Expand All @@ -34,6 +36,15 @@ fn print(span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -> R
};
}

Ok(msg)
}

// Symbol analyzer must ensure that vars used by print are defined before
// the print statement. Scheduler must ensure the above constraint.
// Additionally interpreter must allow undefined inputs to print.
fn print(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
let msg = print_to_string(span, params, args, strict)?;

if !msg.is_empty() {
eprintln!("{}", &msg[1..]);
}
Expand Down
2 changes: 2 additions & 0 deletions src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ use lazy_static::lazy_static;

pub type BuiltinFcn = (fn(&Span, &[Ref<Expr>], &[Value], bool) -> Result<Value>, u8);

pub use debugging::print_to_string;

#[cfg(feature = "deprecated")]
pub use deprecated::DEPRECATED;

Expand Down
39 changes: 39 additions & 0 deletions src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,4 +626,43 @@ impl Engine {
pub fn clear_coverage_data(&mut self) {
self.interpreter.clear_coverage_data()
}

/// Gather output from print statements instead of emiting to stderr.
///
/// See [`Engine::take_prints`].
pub fn set_gather_prints(&mut self, b: bool) {
self.interpreter.set_gather_prints(b);
}

/// Take the gathered output of print statements.
///
/// ```rust
/// # use regorus::*;
/// # use anyhow::{bail, Result};
/// # fn main() -> Result<()> {
/// let mut engine = Engine::new();
///
/// // Print to stderr.
/// engine.eval_query("print(\"Hello\")".to_string(), false)?;
///
/// // Configure gathering print statements.
/// engine.set_gather_prints(true);
///
/// // Execute query.
/// engine.eval_query("print(\"Hello\")".to_string(), false)?;
///
/// // Take and clear prints.
/// let prints = engine.take_prints()?;
/// assert_eq!(prints.len(), 1);
/// assert!(prints[0].contains("Hello"));
///
/// for p in prints {
/// println!("{p}");
/// }
/// # Ok(())
/// # }
/// ```
pub fn take_prints(&mut self) -> Result<Vec<String>> {
self.interpreter.take_prints()
}
}
32 changes: 31 additions & 1 deletion src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ pub struct Interpreter {
coverage: HashMap<Source, Vec<bool>>,
#[cfg(feature = "coverage")]
enable_coverage: bool,

gather_prints: bool,
prints: Vec<String>,
}

impl Default for Interpreter {
Expand Down Expand Up @@ -190,6 +193,9 @@ impl Interpreter {
coverage: HashMap::new(),
#[cfg(feature = "coverage")]
enable_coverage: false,

gather_prints: false,
prints: Vec::default(),
}
}

Expand Down Expand Up @@ -2052,7 +2058,8 @@ impl Interpreter {
params: &[ExprRef],
) -> Result<Value> {
let mut args = vec![];
let allow_undefined = name == "print"; // TODO: with modifier
let is_print = name == "print"; // TODO: with modifier
let allow_undefined = is_print;
for p in params {
match self.eval_expr(p)? {
// If any argument is undefined, then the call is undefined.
Expand All @@ -2061,6 +2068,17 @@ impl Interpreter {
}
}

if is_print && self.gather_prints {
// Do not print to stderr. Instead, gather.
let msg =
builtins::print_to_string(span, params, &args[..], self.strict_builtin_errors)?;

// Prefix location information.
self.prints
.push(format!("{}:{}: {msg}", span.source.file(), span.line));
return Ok(Value::Bool(true));
}

let cache = builtins::must_cache(name);
if let Some(name) = &cache {
if let Some(v) = self.builtins_cache.get(&(name, args.clone())) {
Expand Down Expand Up @@ -3705,4 +3723,16 @@ impl Interpreter {
pub fn clear_coverage_data(&mut self) {
self.coverage = HashMap::new();
}

pub fn set_gather_prints(&mut self, b: bool) {
if b != self.gather_prints {
// Clear existing prints.
std::mem::take(&mut self.prints);
}
self.gather_prints = b;
}

pub fn take_prints(&mut self) -> Result<Vec<String>> {
Ok(std::mem::take(&mut self.prints))
}
}
Loading