Provide Position to debug.

This commit is contained in:
Stephen Chung 2020-12-12 11:47:18 +08:00
parent 5443368359
commit 40b6a014ae
7 changed files with 59 additions and 25 deletions

View File

@ -14,11 +14,13 @@ Breaking changes
---------------- ----------------
* `Engine::on_progress` now takes `u64` instead of `&u64`. * `Engine::on_progress` now takes `u64` instead of `&u64`.
* The closure for `Engine::on_debug` now takes an additional `Position` parameter.
Enhancements Enhancements
------------ ------------
* Capturing a constant variable in a closure is now supported, with no cloning. * Capturing a constant variable in a closure is now supported, with no cloning.
* Provides position info for `debug` statements.
Version 0.19.7 Version 0.19.7

View File

@ -22,10 +22,12 @@ When embedding Rhai into an application, it is usually necessary to trap `print`
(for logging into a tracking log, for example) with the `Engine::on_print` and `Engine::on_debug` methods: (for logging into a tracking log, for example) with the `Engine::on_print` and `Engine::on_debug` methods:
```rust ```rust
// Any function or closure that takes an '&str' argument can be used to override // Any function or closure that takes an '&str' argument can be used to override 'print'.
// 'print' and 'debug'
engine.on_print(|x| println!("hello: {}", x)); engine.on_print(|x| println!("hello: {}", x));
engine.on_debug(|x| println!("DEBUG: {}", x));
// Any function or closure that takes a '&str' and a 'Position' argument can be used to
// override 'debug'.
engine.on_debug(|x, pos| println!("DEBUG at {:?}: {}", pos, x));
// Example: quick-'n-dirty logging // Example: quick-'n-dirty logging
let logbook = Arc::new(RwLock::new(Vec::<String>::new())); let logbook = Arc::new(RwLock::new(Vec::<String>::new()));
@ -35,7 +37,9 @@ let log = logbook.clone();
engine.on_print(move |s| log.write().unwrap().push(format!("entry: {}", s))); engine.on_print(move |s| log.write().unwrap().push(format!("entry: {}", s)));
let log = logbook.clone(); let log = logbook.clone();
engine.on_debug(move |s| log.write().unwrap().push(format!("DEBUG: {}", s))); engine.on_debug(move |s, pos| log.write().unwrap().push(
format!("DEBUG at {:?}: {}", pos, s)
));
// Evaluate script // Evaluate script
engine.eval::<()>(script)?; engine.eval::<()>(script)?;

View File

@ -4,7 +4,8 @@ use crate::ast::{Expr, FnCallExpr, Ident, IdentX, ReturnType, Stmt};
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
use crate::fn_call::run_builtin_op_assignment; use crate::fn_call::run_builtin_op_assignment;
use crate::fn_native::{ use crate::fn_native::{
CallableFunction, IteratorFn, OnPrintCallback, OnProgressCallback, OnVarCallback, CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback,
OnVarCallback,
}; };
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
@ -626,7 +627,7 @@ pub struct Engine {
/// Callback closure for implementing the `print` command. /// Callback closure for implementing the `print` command.
pub(crate) print: OnPrintCallback, pub(crate) print: OnPrintCallback,
/// Callback closure for implementing the `debug` command. /// Callback closure for implementing the `debug` command.
pub(crate) debug: OnPrintCallback, pub(crate) debug: OnDebugCallback,
/// Callback closure for progress reporting. /// Callback closure for progress reporting.
pub(crate) progress: Option<OnProgressCallback>, pub(crate) progress: Option<OnProgressCallback>,
@ -677,7 +678,7 @@ pub fn is_anonymous_fn(fn_name: &str) -> bool {
fn_name.starts_with(FN_ANONYMOUS) fn_name.starts_with(FN_ANONYMOUS)
} }
/// Print/debug to stdout /// Print to stdout
#[inline(always)] #[inline(always)]
fn default_print(_s: &str) { fn default_print(_s: &str) {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
@ -685,6 +686,14 @@ fn default_print(_s: &str) {
println!("{}", _s); println!("{}", _s);
} }
/// Debug to stdout
#[inline(always)]
fn default_debug(_s: &str, _pos: Position) {
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
println!("{:?} | {}", _pos, _s);
}
/// Search for a module within an imports stack. /// Search for a module within an imports stack.
/// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards. /// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards.
pub fn search_imports( pub fn search_imports(
@ -741,7 +750,7 @@ impl Engine {
// default print/debug implementations // default print/debug implementations
print: Box::new(default_print), print: Box::new(default_print),
debug: Box::new(default_print), debug: Box::new(default_debug),
// progress callback // progress callback
progress: None, progress: None,
@ -797,7 +806,7 @@ impl Engine {
resolve_var: None, resolve_var: None,
print: Box::new(|_| {}), print: Box::new(|_| {}),
debug: Box::new(|_| {}), debug: Box::new(|_, _| {}),
progress: None, progress: None,
optimization_level: if cfg!(feature = "no_optimize") { optimization_level: if cfg!(feature = "no_optimize") {

View File

@ -1808,16 +1808,21 @@ impl Engine {
/// ///
/// // Override action of 'print' function /// // Override action of 'print' function
/// let logger = result.clone(); /// let logger = result.clone();
/// engine.on_debug(move |s| logger.write().unwrap().push_str(s)); /// engine.on_debug(move |s, pos| logger.write().unwrap().push_str(
/// &format!("{:?} > {}", pos, s)
/// ));
/// ///
/// engine.consume(r#"debug("hello");"#)?; /// engine.consume(r#"let x = "hello"; debug(x);"#)?;
/// ///
/// assert_eq!(*result.read().unwrap(), r#""hello""#); /// assert_eq!(*result.read().unwrap(), r#"1:18 > "hello""#);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn on_debug(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self { pub fn on_debug(
&mut self,
callback: impl Fn(&str, Position) + SendSync + 'static,
) -> &mut Self {
self.debug = Box::new(callback); self.debug = Box::new(callback);
self self
} }

View File

@ -211,13 +211,16 @@ impl Engine {
false, false,
), ),
KEYWORD_DEBUG => ( KEYWORD_DEBUG => (
(self.debug)(result.as_str().map_err(|typ| { (self.debug)(
result.as_str().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(), self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(), typ.into(),
pos, pos,
) )
})?) })?,
pos,
)
.into(), .into(),
false, false,
), ),

View File

@ -356,7 +356,14 @@ pub type OnProgressCallback = Box<dyn Fn(u64) -> Option<Dynamic> + Send + Sync +
pub type OnPrintCallback = Box<dyn Fn(&str) + 'static>; pub type OnPrintCallback = Box<dyn Fn(&str) + 'static>;
/// A standard callback function for printing. /// A standard callback function for printing.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnPrintCallback<T, R> = Box<dyn Fn(&str) + Send + Sync + 'static>; pub type OnPrintCallback = Box<dyn Fn(&str) + Send + Sync + 'static>;
/// A standard callback function for debugging.
#[cfg(not(feature = "sync"))]
pub type OnDebugCallback = Box<dyn Fn(&str, Position) + 'static>;
/// A standard callback function for debugging.
#[cfg(feature = "sync")]
pub type OnDebugCallback = Box<dyn Fn(&str, Position) + Send + Sync + 'static>;
/// A standard callback function for variable access. /// A standard callback function for variable access.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, RegisterFn, INT};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
#[test] #[test]
fn test_print() -> Result<(), Box<EvalAltResult>> { fn test_print_debug() -> Result<(), Box<EvalAltResult>> {
let logbook = Arc::new(RwLock::new(Vec::<String>::new())); let logbook = Arc::new(RwLock::new(Vec::<String>::new()));
// Redirect print/debug output to 'log' // Redirect print/debug output to 'log'
@ -13,16 +13,20 @@ fn test_print() -> Result<(), Box<EvalAltResult>> {
engine engine
.on_print(move |s| log1.write().unwrap().push(format!("entry: {}", s))) .on_print(move |s| log1.write().unwrap().push(format!("entry: {}", s)))
.on_debug(move |s| log2.write().unwrap().push(format!("DEBUG: {}", s))); .on_debug(move |s, pos| {
log2.write()
.unwrap()
.push(format!("DEBUG at {:?}: {}", pos, s))
});
// Evaluate script // Evaluate script
engine.eval::<()>("print(40 + 2)")?; engine.eval::<()>("print(40 + 2)")?;
engine.eval::<()>(r#"debug("hello!")"#)?; engine.eval::<()>(r#"let x = "hello!"; debug(x)"#)?;
// 'logbook' captures all the 'print' and 'debug' output // 'logbook' captures all the 'print' and 'debug' output
assert_eq!(logbook.read().unwrap().len(), 2); assert_eq!(logbook.read().unwrap().len(), 2);
assert_eq!(logbook.read().unwrap()[0], "entry: 42"); assert_eq!(logbook.read().unwrap()[0], "entry: 42");
assert_eq!(logbook.read().unwrap()[1], r#"DEBUG: "hello!""#); assert_eq!(logbook.read().unwrap()[1], r#"DEBUG at 1:19: "hello!""#);
for entry in logbook.read().unwrap().iter() { for entry in logbook.read().unwrap().iter() {
println!("{}", entry); println!("{}", entry);