diff --git a/examples/event_handler_js/main.rs b/examples/event_handler_js/main.rs index 770127ce..99118feb 100644 --- a/examples/event_handler_js/main.rs +++ b/examples/event_handler_js/main.rs @@ -17,24 +17,21 @@ struct Handler { } fn print_scope(scope: &Scope) { - scope - .iter_raw() - .enumerate() - .for_each(|(i, (name, constant, value))| { - #[cfg(not(feature = "no_closure"))] - let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; - #[cfg(feature = "no_closure")] - let value_is_shared = ""; + for (i, (name, constant, value)) in scope.iter_raw().enumerate() { + #[cfg(not(feature = "no_closure"))] + let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; + #[cfg(feature = "no_closure")] + let value_is_shared = ""; - println!( - "[{}] {}{}{} = {:?}", - i + 1, - if constant { "const " } else { "" }, - name, - value_is_shared, - *value.read_lock::().unwrap(), - ) - }); + println!( + "[{}] {}{}{} = {:?}", + i + 1, + if constant { "const " } else { "" }, + name, + value_is_shared, + *value.read_lock::().unwrap(), + ) + } println!(); } diff --git a/examples/event_handler_main/main.rs b/examples/event_handler_main/main.rs index 750d6742..76348974 100644 --- a/examples/event_handler_main/main.rs +++ b/examples/event_handler_main/main.rs @@ -13,24 +13,21 @@ struct Handler { } fn print_scope(scope: &Scope) { - scope - .iter_raw() - .enumerate() - .for_each(|(i, (name, constant, value))| { - #[cfg(not(feature = "no_closure"))] - let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; - #[cfg(feature = "no_closure")] - let value_is_shared = ""; + for (i, (name, constant, value)) in scope.iter_raw().enumerate() { + #[cfg(not(feature = "no_closure"))] + let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; + #[cfg(feature = "no_closure")] + let value_is_shared = ""; - println!( - "[{}] {}{}{} = {:?}", - i + 1, - if constant { "const " } else { "" }, - name, - value_is_shared, - *value.read_lock::().unwrap(), - ) - }); + println!( + "[{}] {}{}{} = {:?}", + i + 1, + if constant { "const " } else { "" }, + name, + value_is_shared, + *value.read_lock::().unwrap(), + ) + } println!(); } diff --git a/examples/event_handler_map/main.rs b/examples/event_handler_map/main.rs index 62e0a15f..7227fcc7 100644 --- a/examples/event_handler_map/main.rs +++ b/examples/event_handler_map/main.rs @@ -16,24 +16,21 @@ struct Handler { } fn print_scope(scope: &Scope) { - scope - .iter_raw() - .enumerate() - .for_each(|(i, (name, constant, value))| { - #[cfg(not(feature = "no_closure"))] - let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; - #[cfg(feature = "no_closure")] - let value_is_shared = ""; + for (i, (name, constant, value)) in scope.iter_raw().enumerate() { + #[cfg(not(feature = "no_closure"))] + let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; + #[cfg(feature = "no_closure")] + let value_is_shared = ""; - println!( - "[{}] {}{}{} = {:?}", - i + 1, - if constant { "const " } else { "" }, - name, - value_is_shared, - *value.read_lock::().unwrap(), - ) - }); + println!( + "[{}] {}{}{} = {:?}", + i + 1, + if constant { "const " } else { "" }, + name, + value_is_shared, + *value.read_lock::().unwrap(), + ) + } println!(); } diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index 003dcc67..e501c68d 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -154,10 +154,7 @@ impl Engine { arg_values: impl AsMut<[Dynamic]>, ) -> RhaiResult { let state = &mut EvalState::new(); - let global = &mut GlobalRuntimeState::new(); - - #[cfg(feature = "debugging")] - global.debugger.activate(self.debugger.is_some()); + let global = &mut GlobalRuntimeState::new(self); let statements = ast.statements(); diff --git a/src/api/eval.rs b/src/api/eval.rs index 11038263..7e6eb83c 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -184,10 +184,7 @@ impl Engine { scope: &mut Scope, ast: &AST, ) -> RhaiResultOf { - let global = &mut GlobalRuntimeState::new(); - - #[cfg(feature = "debugging")] - global.debugger.activate(self.debugger.is_some()); + let global = &mut GlobalRuntimeState::new(self); let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?; diff --git a/src/api/events.rs b/src/api/events.rs index 2a27310d..6fb5227c 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -260,12 +260,13 @@ impl Engine { self.debug = Some(Box::new(callback)); self } - /// _(debugging)_ Register a callback for debugging. + /// _(debugging)_ Register callbacks for debugging. /// Exported under the `debugging` feature only. #[cfg(feature = "debugging")] #[inline(always)] pub fn on_debugger( &mut self, + init: impl Fn() -> Dynamic + SendSync + 'static, callback: impl Fn( &mut EvalContext, crate::ast::ASTNode, @@ -275,7 +276,7 @@ impl Engine { + SendSync + 'static, ) -> &mut Self { - self.debugger = Some(Box::new(callback)); + self.debugger = Some((Box::new(init), Box::new(callback))); self } } diff --git a/src/api/register.rs b/src/api/register.rs index b489a2b8..717cc980 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -1037,9 +1037,9 @@ impl Engine { signatures.extend(self.global_namespace().gen_fn_signatures()); - self.global_sub_modules.iter().for_each(|(name, m)| { + for (name, m) in &self.global_sub_modules { signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f))) - }); + } signatures.extend( self.global_modules diff --git a/src/api/run.rs b/src/api/run.rs index 1160c3c9..7c556064 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -45,9 +45,7 @@ impl Engine { #[inline] pub fn run_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> { let state = &mut EvalState::new(); - let global = &mut GlobalRuntimeState::new(); - #[cfg(feature = "debugging")] - global.debugger.activate(self.debugger.is_some()); + let global = &mut GlobalRuntimeState::new(self); global.source = ast.source_raw().clone(); #[cfg(not(feature = "no_module"))] diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index 3d053950..8a37d15c 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -1,5 +1,5 @@ #[cfg(feature = "debugging")] -use rhai::{Dynamic, Engine, EvalAltResult, Position, Scope}; +use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, Position, Scope}; #[cfg(feature = "debugging")] use rhai::debugger::DebuggerCommand; @@ -121,34 +121,31 @@ fn print_scope(scope: &Scope, dedup: bool) { scope }; - scope - .iter_raw() - .enumerate() - .for_each(|(i, (name, constant, value))| { - #[cfg(not(feature = "no_closure"))] - let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; - #[cfg(feature = "no_closure")] - let value_is_shared = ""; + for (i, (name, constant, value)) in scope.iter_raw().enumerate() { + #[cfg(not(feature = "no_closure"))] + let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; + #[cfg(feature = "no_closure")] + let value_is_shared = ""; - if dedup { - println!( - "{}{}{} = {:?}", - if constant { "const " } else { "" }, - name, - value_is_shared, - *value.read_lock::().unwrap(), - ); - } else { - println!( - "[{}] {}{}{} = {:?}", - i + 1, - if constant { "const " } else { "" }, - name, - value_is_shared, - *value.read_lock::().unwrap(), - ); - } - }); + if dedup { + println!( + "{}{}{} = {:?}", + if constant { "const " } else { "" }, + name, + value_is_shared, + *value.read_lock::().unwrap(), + ); + } else { + println!( + "[{}] {}{}{} = {:?}", + i + 1, + if constant { "const " } else { "" }, + name, + value_is_shared, + *value.read_lock::().unwrap(), + ); + } + } println!(); } @@ -233,198 +230,221 @@ fn main() { // Hook up debugger let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect(); - #[cfg(not(feature = "sync"))] - let current_source = std::cell::RefCell::new(rhai::Identifier::new_const()); - #[cfg(feature = "sync")] - let current_source = std::sync::RwLock::new(rhai::Identifier::new_const()); + engine.on_debugger( + // Store the current source in the debugger state + || "".into(), + // Main debugging interface + move |context, node, source, pos| { + { + let current_source = &mut *context + .global_runtime_state_mut() + .debugger + .state_mut() + .write_lock::() + .unwrap(); - engine.on_debugger(move |context, node, source, pos| { - #[cfg(not(feature = "sync"))] - let current_source = &mut *current_source.borrow_mut(); - #[cfg(feature = "sync")] - let current_source = &mut *current_source.write().unwrap(); + let src = source.unwrap_or(""); - // Check source - if let Some(src) = source { - if src != current_source { - println!(">>> Source => {}", src); + // Check source + if src != current_source { + println!(">>> Source => {}", source.unwrap_or("main script")); + *current_source = src.into(); + } + + if !src.is_empty() { + // Print just a line number for imported modules + println!("{} @ {:?}", src, pos); + } else { + // Print the current source line + print_source(&lines, pos, 0); + } } - // Print just a line number for imported modules - println!("{} @ {:?}", src, pos); - } else { - // The root file has no source - if !current_source.is_empty() { - println!(">>> Source => main script."); - } - // Print the current source line - print_source(&lines, pos, 0); - } - *current_source = source.unwrap_or("").into(); - // Read stdin for commands - let mut input = String::new(); + // Read stdin for commands + let mut input = String::new(); - loop { - print!("rhai-dbg> "); - stdout().flush().expect("couldn't flush stdout"); + loop { + print!("rhai-dbg> "); + stdout().flush().expect("couldn't flush stdout"); - input.clear(); + input.clear(); - match stdin().read_line(&mut input) { - Ok(0) => break Ok(DebuggerCommand::Continue), - Ok(_) => match input - .trim() - .split_whitespace() - .collect::>() - .as_slice() - { - ["help", ..] => print_debug_help(), - ["exit", ..] | ["quit", ..] | ["kill", ..] => { - println!("Script terminated. Bye!"); - exit(0); - } - ["node", ..] => { - println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos); - println!(); - } - ["continue", ..] => break Ok(DebuggerCommand::Continue), - [] | ["step", ..] => break Ok(DebuggerCommand::StepInto), - ["over", ..] => break Ok(DebuggerCommand::StepOver), - ["next", ..] => break Ok(DebuggerCommand::Next), - ["scope", ..] => print_scope(context.scope(), false), - ["print", var_name, ..] => { - if let Some(value) = context.scope().get_value::(var_name) { - if value.is::<()>() { - println!("=> ()"); - } else { - println!("=> {}", value); - } - } else { - eprintln!("Variable not found: {}", var_name); + match stdin().read_line(&mut input) { + Ok(0) => break Ok(DebuggerCommand::Continue), + Ok(_) => match input + .trim() + .split_whitespace() + .collect::>() + .as_slice() + { + ["help", ..] => print_debug_help(), + ["exit", ..] | ["quit", ..] | ["kill", ..] => { + println!("Script terminated. Bye!"); + exit(0); } - } - ["print", ..] => print_scope(context.scope(), true), - ["imports", ..] => { - context - .global_runtime_state() - .scan_imports_raw() - .enumerate() - .for_each(|(i, (name, module))| { + ["node", ..] => { + println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos); + println!(); + } + ["continue", ..] => break Ok(DebuggerCommand::Continue), + [] | ["step", ..] => break Ok(DebuggerCommand::StepInto), + ["over", ..] => break Ok(DebuggerCommand::StepOver), + ["next", ..] => break Ok(DebuggerCommand::Next), + ["scope", ..] => print_scope(context.scope(), false), + ["print", var_name, ..] => { + if let Some(value) = context.scope().get_value::(var_name) { + if value.is::<()>() { + println!("=> ()"); + } else { + println!("=> {}", value); + } + } else { + eprintln!("Variable not found: {}", var_name); + } + } + ["print", ..] => print_scope(context.scope(), true), + ["imports", ..] => { + for (i, (name, module)) in context + .global_runtime_state() + .scan_imports_raw() + .enumerate() + { println!( "[{}] {} = {}", i + 1, name, module.id().unwrap_or("") ); - }); + } - println!(); - } - #[cfg(not(feature = "no_function"))] - ["backtrace", ..] => { - context - .global_runtime_state() - .debugger - .call_stack() - .iter() - .rev() - .for_each(|frame| println!("{}", frame)); - } - ["clear", ..] => { - context - .global_runtime_state_mut() - .debugger - .break_points_mut() - .clear(); - println!("All break-points cleared."); - } - ["breakpoints", ..] => context - .global_runtime_state() - .debugger - .break_points() - .iter() - .enumerate() - .for_each(|(i, bp)| match bp { - #[cfg(not(feature = "no_position"))] - rhai::debugger::BreakPoint::AtPosition { pos, .. } => { - let line_num = format!("[{}] line ", i + 1); - print!("{}", line_num); - print_source(&lines, *pos, line_num.len()); + println!(); + } + #[cfg(not(feature = "no_function"))] + ["backtrace", ..] => { + for frame in context + .global_runtime_state() + .debugger + .call_stack() + .iter() + .rev() + { + println!("{}", frame) } - _ => println!("[{}] {}", i + 1, bp), - }), - ["enable", n, ..] => { - if let Ok(n) = n.parse::() { - let range = 1..=context + } + ["clear", ..] => { + context .global_runtime_state_mut() .debugger + .break_points_mut() + .clear(); + println!("All break-points cleared."); + } + ["breakpoints", ..] => Iterator::for_each( + context + .global_runtime_state() + .debugger .break_points() - .len(); - if range.contains(&n) { + .iter() + .enumerate(), + |(i, bp)| match bp { + #[cfg(not(feature = "no_position"))] + rhai::debugger::BreakPoint::AtPosition { pos, .. } => { + let line_num = format!("[{}] line ", i + 1); + print!("{}", line_num); + print_source(&lines, *pos, line_num.len()); + } + _ => println!("[{}] {}", i + 1, bp), + }, + ), + ["enable", n, ..] => { + if let Ok(n) = n.parse::() { + let range = 1..=context + .global_runtime_state_mut() + .debugger + .break_points() + .len(); + if range.contains(&n) { + context + .global_runtime_state_mut() + .debugger + .break_points_mut() + .get_mut(n - 1) + .unwrap() + .enable(true); + println!("Break-point #{} enabled.", n) + } else { + eprintln!("Invalid break-point: {}", n); + } + } else { + eprintln!("Invalid break-point: '{}'", n); + } + } + ["disable", n, ..] => { + if let Ok(n) = n.parse::() { + let range = 1..=context + .global_runtime_state_mut() + .debugger + .break_points() + .len(); + if range.contains(&n) { + context + .global_runtime_state_mut() + .debugger + .break_points_mut() + .get_mut(n - 1) + .unwrap() + .enable(false); + println!("Break-point #{} disabled.", n) + } else { + eprintln!("Invalid break-point: {}", n); + } + } else { + eprintln!("Invalid break-point: '{}'", n); + } + } + ["delete", n, ..] => { + if let Ok(n) = n.parse::() { + let range = 1..=context + .global_runtime_state_mut() + .debugger + .break_points() + .len(); + if range.contains(&n) { + context + .global_runtime_state_mut() + .debugger + .break_points_mut() + .remove(n - 1); + println!("Break-point #{} deleted.", n) + } else { + eprintln!("Invalid break-point: {}", n); + } + } else { + eprintln!("Invalid break-point: '{}'", n); + } + } + ["break", fn_name, args, ..] => { + if let Ok(args) = args.parse::() { + let bp = rhai::debugger::BreakPoint::AtFunctionCall { + name: fn_name.trim().into(), + args, + enabled: true, + }; + println!("Break-point added for {}", bp); context .global_runtime_state_mut() .debugger .break_points_mut() - .get_mut(n - 1) - .unwrap() - .enable(true); - println!("Break-point #{} enabled.", n) + .push(bp); } else { - eprintln!("Invalid break-point: {}", n); + eprintln!("Invalid number of arguments: '{}'", args); } - } else { - eprintln!("Invalid break-point: '{}'", n); } - } - ["disable", n, ..] => { - if let Ok(n) = n.parse::() { - let range = 1..=context - .global_runtime_state_mut() - .debugger - .break_points() - .len(); - if range.contains(&n) { - context - .global_runtime_state_mut() - .debugger - .break_points_mut() - .get_mut(n - 1) - .unwrap() - .enable(false); - println!("Break-point #{} disabled.", n) - } else { - eprintln!("Invalid break-point: {}", n); - } - } else { - eprintln!("Invalid break-point: '{}'", n); - } - } - ["delete", n, ..] => { - if let Ok(n) = n.parse::() { - let range = 1..=context - .global_runtime_state_mut() - .debugger - .break_points() - .len(); - if range.contains(&n) { - context - .global_runtime_state_mut() - .debugger - .break_points_mut() - .remove(n - 1); - println!("Break-point #{} deleted.", n) - } else { - eprintln!("Invalid break-point: {}", n); - } - } else { - eprintln!("Invalid break-point: '{}'", n); - } - } - ["break", fn_name, args, ..] => { - if let Ok(args) = args.parse::() { - let bp = rhai::debugger::BreakPoint::AtFunctionCall { - name: fn_name.trim().into(), - args, + // Property name + #[cfg(not(feature = "no_object"))] + ["break", param] if param.starts_with('.') && param.len() > 1 => { + let bp = rhai::debugger::BreakPoint::AtProperty { + name: param[1..].into(), enabled: true, }; println!("Break-point added for {}", bp); @@ -433,38 +453,51 @@ fn main() { .debugger .break_points_mut() .push(bp); - } else { - eprintln!("Invalid number of arguments: '{}'", args); } - } - // Property name - #[cfg(not(feature = "no_object"))] - ["break", param] if param.starts_with('.') && param.len() > 1 => { - let bp = rhai::debugger::BreakPoint::AtProperty { - name: param[1..].into(), - enabled: true, - }; - println!("Break-point added for {}", bp); - context - .global_runtime_state_mut() - .debugger - .break_points_mut() - .push(bp); - } - // Numeric parameter - #[cfg(not(feature = "no_position"))] - ["break", param] if param.parse::().is_ok() => { - let n = param.parse::().unwrap(); - let range = if source.is_none() { - 1..=lines.len() - } else { - 1..=(u16::MAX as usize) - }; + // Numeric parameter + #[cfg(not(feature = "no_position"))] + ["break", param] if param.parse::().is_ok() => { + let n = param.parse::().unwrap(); + let range = if source.is_none() { + 1..=lines.len() + } else { + 1..=(u16::MAX as usize) + }; - if range.contains(&n) { + if range.contains(&n) { + let bp = rhai::debugger::BreakPoint::AtPosition { + source: source.unwrap_or("").into(), + pos: Position::new(n as u16, 0), + enabled: true, + }; + println!("Break-point added {}", bp); + context + .global_runtime_state_mut() + .debugger + .break_points_mut() + .push(bp); + } else { + eprintln!("Invalid line number: {}", n); + } + } + // Function name parameter + ["break", param] => { + let bp = rhai::debugger::BreakPoint::AtFunctionName { + name: param.trim().into(), + enabled: true, + }; + println!("Break-point added for {}", bp); + context + .global_runtime_state_mut() + .debugger + .break_points_mut() + .push(bp); + } + #[cfg(not(feature = "no_position"))] + ["break", ..] => { let bp = rhai::debugger::BreakPoint::AtPosition { source: source.unwrap_or("").into(), - pos: Position::new(n as u16, 0), + pos, enabled: true, }; println!("Break-point added {}", bp); @@ -473,52 +506,25 @@ fn main() { .debugger .break_points_mut() .push(bp); - } else { - eprintln!("Invalid line number: {}", n); } - } - // Function name parameter - ["break", param] => { - let bp = rhai::debugger::BreakPoint::AtFunctionName { - name: param.trim().into(), - enabled: true, - }; - println!("Break-point added for {}", bp); - context - .global_runtime_state_mut() - .debugger - .break_points_mut() - .push(bp); - } - #[cfg(not(feature = "no_position"))] - ["break", ..] => { - let bp = rhai::debugger::BreakPoint::AtPosition { - source: source.unwrap_or("").into(), - pos, - enabled: true, - }; - println!("Break-point added {}", bp); - context - .global_runtime_state_mut() - .debugger - .break_points_mut() - .push(bp); - } - ["throw"] => break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into()), - ["throw", _msg, ..] => { - let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or(""); - break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into()); - } - ["run", ..] => { - println!("Restarting script..."); - break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into()); - } - [cmd, ..] => eprintln!("Invalid debugger command: '{}'", cmd), - }, - Err(err) => panic!("input error: {}", err), + ["throw"] => { + break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into()) + } + ["throw", _msg, ..] => { + let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or(""); + break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into()); + } + ["run", ..] => { + println!("Restarting script..."); + break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into()); + } + [cmd, ..] => eprintln!("Invalid debugger command: '{}'", cmd), + }, + Err(err) => panic!("input error: {}", err), + } } - } - }); + }, + ); // Set a file module resolver without caching #[cfg(not(feature = "no_module"))] diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 2169b642..0ed79130 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -61,24 +61,21 @@ fn print_help() { /// Display the scope. fn print_scope(scope: &Scope) { - scope - .iter_raw() - .enumerate() - .for_each(|(i, (name, constant, value))| { - #[cfg(not(feature = "no_closure"))] - let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; - #[cfg(feature = "no_closure")] - let value_is_shared = ""; + for (i, (name, constant, value)) in scope.iter_raw().enumerate() { + #[cfg(not(feature = "no_closure"))] + let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; + #[cfg(feature = "no_closure")] + let value_is_shared = ""; - println!( - "[{}] {}{}{} = {:?}", - i + 1, - if constant { "const " } else { "" }, - name, - value_is_shared, - *value.read_lock::().unwrap(), - ) - }); + println!( + "[{}] {}{}{} = {:?}", + i + 1, + if constant { "const " } else { "" }, + name, + value_is_shared, + *value.read_lock::().unwrap(), + ) + } println!(); } @@ -282,13 +279,13 @@ fn main() { #[cfg(feature = "metadata")] "functions" => { // print a list of all registered functions - engine - .gen_fn_signatures(false) - .into_iter() - .for_each(|f| println!("{}", f)); + for f in engine.gen_fn_signatures(false) { + println!("{}", f) + } - #[cfg(not(feature = "no_function"))] - main_ast.iter_functions().for_each(|f| println!("{}", f)); + for f in main_ast.iter_functions() { + println!("{}", f) + } println!(); continue; diff --git a/src/engine.rs b/src/engine.rs index a60e59f4..bde0cbb8 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -140,7 +140,10 @@ pub struct Engine { /// Callback closure for debugging. #[cfg(feature = "debugging")] - pub(crate) debugger: Option>, + pub(crate) debugger: Option<( + Box, + Box, + )>, } impl fmt::Debug for Engine { diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index 8c5c31e9..b6c1e487 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -8,11 +8,18 @@ use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; -/// A standard callback function for debugging. +/// Callback function to initialize the debugger. +#[cfg(not(feature = "sync"))] +pub type OnDebuggingInit = dyn Fn() -> Dynamic; +/// Callback function to initialize the debugger. +#[cfg(feature = "sync")] +pub type OnDebuggingInit = dyn Fn() -> Dynamic + Send + Sync; + +/// Callback function for debugging. #[cfg(not(feature = "sync"))] pub type OnDebuggerCallback = dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf; -/// A standard callback function for debugging. +/// Callback function for debugging. #[cfg(feature = "sync")] pub type OnDebuggerCallback = dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf + Send @@ -192,6 +199,8 @@ impl fmt::Display for CallStackFrame { pub struct Debugger { /// The current status command. status: DebuggerCommand, + /// The current state. + state: Dynamic, /// The current set of break-points. break_points: Vec, /// The current function call stack. @@ -200,28 +209,44 @@ pub struct Debugger { } impl Debugger { - /// Create a new [`Debugger`]. - pub const fn new() -> Self { + /// Create a new [`Debugger`] based on an [`Engine`]. + #[inline(always)] + #[must_use] + pub fn new(engine: &Engine) -> Self { Self { - status: DebuggerCommand::Continue, + status: if engine.debugger.is_some() { + DebuggerCommand::StepInto + } else { + DebuggerCommand::Continue + }, + state: if let Some((ref init, _)) = engine.debugger { + init() + } else { + Dynamic::UNIT + }, break_points: Vec::new(), #[cfg(not(feature = "no_function"))] call_stack: Vec::new(), } } - /// Get the function call stack depth. - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] + /// Get a reference to the current state. #[inline(always)] - pub fn call_stack_len(&self) -> usize { - self.call_stack.len() + #[must_use] + pub fn state(&self) -> &Dynamic { + &self.state + } + /// Get a mutable reference to the current state. + #[inline(always)] + #[must_use] + pub fn state_mut(&mut self) -> &mut Dynamic { + &mut self.state } /// Get the current call stack. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline(always)] + #[must_use] pub fn call_stack(&self) -> &[CallStackFrame] { &self.call_stack } @@ -258,10 +283,11 @@ impl Debugger { pub fn status(&self) -> DebuggerCommand { self.status } - /// Set the status of this [`Debugger`]. + /// Get a mutable reference to the current status of this [`Debugger`]. #[inline(always)] - pub fn set_status(&mut self, status: DebuggerCommand) { - self.status = status; + #[must_use] + pub fn status_mut(&mut self) -> &mut DebuggerCommand { + &mut self.status } /// Set the status of this [`Debugger`]. #[inline(always)] @@ -270,17 +296,8 @@ impl Debugger { self.status = cmd; } } - /// Activate: set the status of this [`Debugger`] to [`DebuggerCommand::StepInto`]. - /// Deactivate: set the status of this [`Debugger`] to [`DebuggerCommand::Continue`]. - #[inline(always)] - pub fn activate(&mut self, active: bool) { - if active { - self.set_status(DebuggerCommand::StepInto); - } else { - self.set_status(DebuggerCommand::Continue); - } - } /// Does a particular [`AST` Node][ASTNode] trigger a break-point? + #[must_use] pub fn is_break_point(&self, src: &str, node: ASTNode) -> bool { let _src = src; @@ -347,7 +364,7 @@ impl Engine { if let Some(cmd) = self.run_debugger_with_reset(scope, global, state, lib, this_ptr, node, level)? { - global.debugger.set_status(cmd); + global.debugger.status = cmd; } Ok(()) @@ -375,7 +392,7 @@ impl Engine { node: impl Into>, level: usize, ) -> RhaiResultOf> { - if let Some(ref on_debugger) = self.debugger { + if let Some((_, ref on_debugger)) = self.debugger { let node = node.into(); // Skip transitive nodes @@ -415,19 +432,19 @@ impl Engine { match command { DebuggerCommand::Continue => { - global.debugger.set_status(DebuggerCommand::Continue); + global.debugger.status = DebuggerCommand::Continue; Ok(None) } DebuggerCommand::Next => { - global.debugger.set_status(DebuggerCommand::Continue); + global.debugger.status = DebuggerCommand::Continue; Ok(Some(DebuggerCommand::Next)) } DebuggerCommand::StepInto => { - global.debugger.set_status(DebuggerCommand::StepInto); + global.debugger.status = DebuggerCommand::StepInto; Ok(None) } DebuggerCommand::StepOver => { - global.debugger.set_status(DebuggerCommand::Continue); + global.debugger.status = DebuggerCommand::Continue; Ok(Some(DebuggerCommand::StepOver)) } } diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 33ff3cd8..60f81bf3 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -64,8 +64,8 @@ impl Engine { // Qualified variable access #[cfg(not(feature = "no_module"))] (_, Some((namespace, hash_var)), var_name) => { + // foo:bar::baz::VARIABLE if let Some(module) = self.search_imports(global, state, namespace) { - // foo:bar::baz::VARIABLE return match module.get_qualified_var(*hash_var) { Ok(target) => { let mut target = target.clone(); @@ -89,18 +89,16 @@ impl Engine { }; } + // global::VARIABLE #[cfg(not(feature = "no_function"))] if namespace.len() == 1 && namespace[0].name == crate::engine::KEYWORD_GLOBAL { - // global::VARIABLE - let global_constants = global.constants_mut(); + let mut guard = crate::func::native::locked_write(&global.constants); - if let Some(mut guard) = global_constants { - if let Some(value) = guard.get_mut(var_name) { - let mut target: Target = value.clone().into(); - // Module variables are constant - target.set_access_mode(AccessMode::ReadOnly); - return Ok((target.into(), *_var_pos)); - } + if let Some(value) = guard.get_mut(var_name) { + let mut target: Target = value.clone().into(); + // Module variables are constant + target.set_access_mode(AccessMode::ReadOnly); + return Ok((target.into(), *_var_pos)); } return Err(ERR::ErrorVariableNotFound( diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index 8e67096c..9a9ddc35 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -1,13 +1,13 @@ //! Global runtime state. use crate::func::{CallableFunction, IteratorFn}; -use crate::{Identifier, Module, Shared, StaticVec}; +use crate::{Engine, Identifier, Module, Shared, StaticVec}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::TypeId, fmt, - iter::{FromIterator, Rev, Zip}, + iter::{Rev, Zip}, marker::PhantomData, }; @@ -44,8 +44,7 @@ pub struct GlobalRuntimeState<'a> { /// Cache of globally-defined constants. #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] - constants: - Option>>>, + pub(crate) constants: crate::Locked>, /// Debugging interface. #[cfg(feature = "debugging")] pub debugger: super::Debugger, @@ -53,18 +52,11 @@ pub struct GlobalRuntimeState<'a> { dummy: PhantomData<&'a ()>, } -impl Default for GlobalRuntimeState<'_> { - #[inline(always)] - fn default() -> Self { - Self::new() - } -} - impl GlobalRuntimeState<'_> { - /// Create a new [`GlobalRuntimeState`]. + /// Create a new [`GlobalRuntimeState`] based on an [`Engine`]. #[inline(always)] #[must_use] - pub fn new() -> Self { + pub fn new(engine: &Engine) -> Self { Self { keys: StaticVec::new_const(), modules: StaticVec::new_const(), @@ -77,9 +69,9 @@ impl GlobalRuntimeState<'_> { fn_hash_indexing: (0, 0), #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] - constants: None, + constants: std::collections::BTreeMap::new().into(), #[cfg(feature = "debugging")] - debugger: crate::eval::Debugger::new(), + debugger: crate::eval::Debugger::new(engine), dummy: PhantomData::default(), } } @@ -193,33 +185,6 @@ impl GlobalRuntimeState<'_> { s => Some(s), } } - /// Get a mutable reference to the cache of globally-defined constants. - #[cfg(not(feature = "no_module"))] - #[cfg(not(feature = "no_function"))] - #[must_use] - pub(crate) fn constants_mut<'a>( - &'a mut self, - ) -> Option< - impl std::ops::DerefMut> + 'a, - > { - if let Some(ref global_constants) = self.constants { - Some(crate::func::native::shared_write_lock(global_constants)) - } else { - None - } - } - /// Set a constant into the cache of globally-defined constants. - #[cfg(not(feature = "no_module"))] - #[cfg(not(feature = "no_function"))] - pub(crate) fn set_constant(&mut self, name: impl Into, value: crate::Dynamic) { - if self.constants.is_none() { - let dict: crate::Locked<_> = std::collections::BTreeMap::new().into(); - self.constants = Some(dict.into()); - } - - crate::func::native::shared_write_lock(self.constants.as_mut().expect("`Some`")) - .insert(name.into(), value); - } /// Get the pre-calculated index getter hash. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[must_use] @@ -262,22 +227,13 @@ impl IntoIterator for GlobalRuntimeState<'_> { } } -impl, M: Into>> FromIterator<(K, M)> for GlobalRuntimeState<'_> { - #[inline] - fn from_iter>(iter: T) -> Self { - let mut lib = Self::new(); - lib.extend(iter); - lib - } -} - impl, M: Into>> Extend<(K, M)> for GlobalRuntimeState<'_> { #[inline] fn extend>(&mut self, iter: T) { - iter.into_iter().for_each(|(k, m)| { + for (k, m) in iter { self.keys.push(k.into()); self.modules.push(m.into()); - }) + } } } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index dcbfcc9d..b76619e4 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -14,7 +14,7 @@ pub use chaining::{ChainArgument, ChainType}; #[cfg(not(feature = "no_function"))] pub use debugger::CallStackFrame; #[cfg(feature = "debugging")] -pub use debugger::{BreakPoint, Debugger, DebuggerCommand, OnDebuggerCallback}; +pub use debugger::{BreakPoint, Debugger, DebuggerCommand, OnDebuggerCallback, OnDebuggingInit}; pub use eval_context::EvalContext; pub use eval_state::EvalState; pub use global_state::GlobalRuntimeState; diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 70dac04f..badfa8cc 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -818,8 +818,8 @@ impl Engine { && entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) { - // Add a global constant if at top level and there are functions - global.set_constant(var_name.clone(), value.clone()); + crate::func::native::locked_write(&global.constants) + .insert(var_name.clone(), value.clone()); } if export { @@ -869,23 +869,20 @@ impl Engine { if let Ok(path) = path_result { use crate::ModuleResolver; - let source = match global.source.as_str() { - "" => None, - s => Some(s), - }; let path_pos = expr.position(); - let module_result = global - .embedded_module_resolver + let resolver = global.embedded_module_resolver.clone(); + + let module_result = resolver .as_ref() - .and_then(|r| match r.resolve(self, source, &path, path_pos) { + .and_then(|r| match r.resolve_raw(self, global, &path, path_pos) { Err(err) if matches!(*err, ERR::ErrorModuleNotFound(_, _)) => None, result => Some(result), }) .or_else(|| { self.module_resolver .as_ref() - .map(|r| r.resolve(self, source, &path, path_pos)) + .map(|r| r.resolve_raw(self, global, &path, path_pos)) }) .unwrap_or_else(|| { Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into()) diff --git a/src/func/mod.rs b/src/func/mod.rs index dcae0482..f4779861 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -22,7 +22,7 @@ pub use hashing::{ combine_hashes, get_hasher, }; pub use native::{ - shared_make_mut, shared_take, shared_take_or_clone, shared_try_take, shared_write_lock, FnAny, + locked_write, shared_make_mut, shared_take, shared_take_or_clone, shared_try_take, FnAny, FnPlugin, IteratorFn, Locked, NativeCallContext, SendSync, Shared, }; pub use plugin::PluginFunction; diff --git a/src/func/native.rs b/src/func/native.rs index 29ac8a0a..1d85a9fc 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -286,23 +286,29 @@ impl<'a> NativeCallContext<'a> { is_method_call: bool, args: &mut [&mut Dynamic], ) -> RhaiResult { + let mut global = self + .global + .cloned() + .unwrap_or_else(|| GlobalRuntimeState::new(self.engine())); + let mut state = EvalState::new(); + let fn_name = fn_name.as_ref(); - let len = args.len(); + let args_len = args.len(); let hash = if is_method_call { FnCallHashes::from_all( #[cfg(not(feature = "no_function"))] - calc_fn_hash(fn_name, len - 1), - calc_fn_hash(fn_name, len), + calc_fn_hash(fn_name, args_len - 1), + calc_fn_hash(fn_name, args_len), ) } else { - calc_fn_hash(fn_name, len).into() + calc_fn_hash(fn_name, args_len).into() }; self.engine() .exec_fn_call( - &mut self.global.cloned().unwrap_or_else(GlobalRuntimeState::new), - &mut EvalState::new(), + &mut global, + &mut state, self.lib, fn_name, hash, @@ -353,11 +359,11 @@ pub fn shared_take(value: Shared) -> T { shared_try_take(value).ok().expect("not shared") } -/// Lock a [`Shared`] resource. +/// Lock a [`Locked`] resource. #[inline(always)] #[must_use] #[allow(dead_code)] -pub fn shared_write_lock<'a, T>(value: &'a Locked) -> LockGuard<'a, T> { +pub fn locked_write<'a, T>(value: &'a Locked) -> LockGuard<'a, T> { #[cfg(not(feature = "sync"))] return value.borrow_mut(); @@ -365,20 +371,20 @@ pub fn shared_write_lock<'a, T>(value: &'a Locked) -> LockGuard<'a, T> { return value.write().unwrap(); } -/// A general function trail object. +/// General function trail object. #[cfg(not(feature = "sync"))] pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult; -/// A general function trail object. +/// General function trail object. #[cfg(feature = "sync")] pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + Send + Sync; -/// A trail object for built-in functions. +/// Trail object for built-in functions. pub type FnBuiltin = fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult; -/// A standard function that gets an iterator from a type. +/// Function that gets an iterator from a type. #[cfg(not(feature = "sync"))] pub type IteratorFn = dyn Fn(Dynamic) -> Box>; -/// A standard function that gets an iterator from a type. +/// Function that gets an iterator from a type. #[cfg(feature = "sync")] pub type IteratorFn = dyn Fn(Dynamic) -> Box> + Send + Sync; @@ -387,40 +393,40 @@ pub type FnPlugin = dyn PluginFunction; #[cfg(feature = "sync")] pub type FnPlugin = dyn PluginFunction + Send + Sync; -/// A standard callback function for progress reporting. +/// Callback function for progress reporting. #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "sync"))] pub type OnProgressCallback = dyn Fn(u64) -> Option; -/// A standard callback function for progress reporting. +/// Callback function for progress reporting. #[cfg(not(feature = "unchecked"))] #[cfg(feature = "sync")] pub type OnProgressCallback = dyn Fn(u64) -> Option + Send + Sync; -/// A standard callback function for printing. +/// Callback function for printing. #[cfg(not(feature = "sync"))] pub type OnPrintCallback = dyn Fn(&str); -/// A standard callback function for printing. +/// Callback function for printing. #[cfg(feature = "sync")] pub type OnPrintCallback = dyn Fn(&str) + Send + Sync; -/// A standard callback function for debugging. +/// Callback function for debugging. #[cfg(not(feature = "sync"))] pub type OnDebugCallback = dyn Fn(&str, Option<&str>, Position); -/// A standard callback function for debugging. +/// Callback function for debugging. #[cfg(feature = "sync")] pub type OnDebugCallback = dyn Fn(&str, Option<&str>, Position) + Send + Sync; -/// A standard callback function for mapping tokens during parsing. +/// Callback function for mapping tokens during parsing. #[cfg(not(feature = "sync"))] pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token; -/// A standard callback function for mapping tokens during parsing. +/// Callback function for mapping tokens during parsing. #[cfg(feature = "sync")] pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token + Send + Sync; -/// A standard callback function for variable access. +/// Callback function for variable access. #[cfg(not(feature = "sync"))] pub type OnVarCallback = dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf>; -/// A standard callback function for variable access. +/// Callback function for variable access. #[cfg(feature = "sync")] pub type OnVarCallback = dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf> + Send + Sync; diff --git a/src/func/script.rs b/src/func/script.rs index 11875836..42a5caba 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -74,7 +74,7 @@ impl Engine { #[cfg(feature = "debugging")] #[cfg(not(feature = "no_function"))] - let orig_call_stack_len = global.debugger.call_stack_len(); + let orig_call_stack_len = global.debugger.call_stack().len(); // Put arguments into scope as variables scope.extend(fn_def.params.iter().cloned().zip(args.into_iter().map(|v| { @@ -115,10 +115,9 @@ impl Engine { #[cfg(not(feature = "no_module"))] if let Some(ref modules) = fn_def.global { - modules - .iter() - .cloned() - .for_each(|(n, m)| global.push_import(n, m)); + for (n, m) in modules.iter().cloned() { + global.push_import(n, m) + } } // Evaluate the function diff --git a/src/module/mod.rs b/src/module/mod.rs index 1cca5dfa..cd6c3430 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1401,9 +1401,9 @@ impl Module { /// Sub-modules are flattened onto the root [`Module`], with higher level overriding lower level. #[inline] pub fn combine_flatten(&mut self, other: Self) -> &mut Self { - other.modules.into_iter().for_each(|(_, m)| { + for (_, m) in other.modules.into_iter() { self.combine_flatten(shared_take_or_clone(m)); - }); + } self.variables.extend(other.variables.into_iter()); self.functions.extend(other.functions.into_iter()); self.type_iterators.extend(other.type_iterators.into_iter()); @@ -1419,22 +1419,22 @@ impl Module { /// Only items not existing in this [`Module`] are added. #[inline] pub fn fill_with(&mut self, other: &Self) -> &mut Self { - other.modules.iter().for_each(|(k, v)| { + for (k, v) in &other.modules { if !self.modules.contains_key(k) { self.modules.insert(k.clone(), v.clone()); } - }); - other.variables.iter().for_each(|(k, v)| { + } + for (k, v) in &other.variables { if !self.variables.contains_key(k) { self.variables.insert(k.clone(), v.clone()); } - }); - other.functions.iter().for_each(|(&k, v)| { + } + for (&k, v) in &other.functions { self.functions.entry(k).or_insert_with(|| v.clone()); - }); - other.type_iterators.iter().for_each(|(&k, v)| { + } + for (&k, v) in &other.type_iterators { self.type_iterators.entry(k).or_insert(v.clone()); - }); + } self.all_functions.clear(); self.all_variables.clear(); self.all_type_iterators.clear(); @@ -1455,12 +1455,11 @@ impl Module { other: &Self, _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool + Copy, ) -> &mut Self { - #[cfg(not(feature = "no_function"))] - other.modules.iter().for_each(|(k, v)| { + for (k, v) in &other.modules { let mut m = Self::new(); m.merge_filtered(v, _filter); self.set_sub_module(k.clone(), m); - }); + } #[cfg(feature = "no_function")] self.modules .extend(other.modules.iter().map(|(k, v)| (k.clone(), v.clone()))); @@ -1658,60 +1657,90 @@ impl Module { /// # } /// ``` #[cfg(not(feature = "no_module"))] + #[inline(always)] pub fn eval_ast_as_new( scope: crate::Scope, ast: &crate::AST, engine: &crate::Engine, + ) -> RhaiResultOf { + let global = &mut crate::eval::GlobalRuntimeState::new(engine); + + Self::eval_ast_as_new_raw(engine, scope, global, ast) + } + /// Create a new [`Module`] by evaluating an [`AST`][crate::AST]. + /// + /// The entire [`AST`][crate::AST] is encapsulated into each function, allowing functions + /// to cross-call each other. Functions in the global namespace, plus all functions + /// defined in the [`Module`], are _merged_ into a _unified_ namespace before each call. + /// Therefore, all functions will be found. + #[cfg(not(feature = "no_module"))] + pub(crate) fn eval_ast_as_new_raw( + engine: &crate::Engine, + scope: crate::Scope, + global: &mut crate::eval::GlobalRuntimeState, + ast: &crate::AST, ) -> RhaiResultOf { let mut scope = scope; - let mut global = crate::eval::GlobalRuntimeState::new(); - - #[cfg(feature = "debugging")] - global.debugger.activate(engine.debugger.is_some()); + // Save global state let orig_imports_len = global.num_imports(); + let orig_source = global.source.clone(); + let orig_constants = std::mem::take(&mut global.constants); // Run the script - engine.eval_ast_with_scope_raw(&mut scope, &mut global, &ast, 0)?; + let result = engine.eval_ast_with_scope_raw(&mut scope, global, &ast, 0); // Create new module - let mut module = - scope - .into_iter() - .fold(Module::new(), |mut module, (_, value, mut aliases)| { - // Variables with an alias left in the scope become module variables - match aliases.len() { - 0 => (), - 1 => { - let alias = aliases.pop().unwrap(); - module.set_var(alias, value); - } - _ => { - let last_alias = aliases.pop().unwrap(); - aliases.into_iter().for_each(|alias| { - module.set_var(alias, value.clone()); - }); - // Avoid cloning the last value - module.set_var(last_alias, value); - } - } - module - }); + let mut module = Module::new(); - // Extra modules left in the scope become sub-modules + // Extra modules left become sub-modules #[cfg(not(feature = "no_function"))] let mut func_global = None; - global.into_iter().skip(orig_imports_len).for_each(|kv| { - #[cfg(not(feature = "no_function"))] - if func_global.is_none() { - func_global = Some(StaticVec::new()); - } - #[cfg(not(feature = "no_function"))] - func_global.as_mut().expect("`Some`").push(kv.clone()); + if result.is_ok() { + global + .scan_imports_raw() + .skip(orig_imports_len) + .for_each(|(k, m)| { + #[cfg(not(feature = "no_function"))] + if func_global.is_none() { + func_global = Some(StaticVec::new()); + } + #[cfg(not(feature = "no_function"))] + func_global + .as_mut() + .expect("`Some`") + .push((k.clone(), m.clone())); - module.set_sub_module(kv.0, kv.1); - }); + module.set_sub_module(k.clone(), m.clone()); + }); + } + + // Restore global state + global.constants = orig_constants; + global.truncate_imports(orig_imports_len); + global.source = orig_source; + + result?; + + // Variables with an alias left in the scope become module variables + for (_, value, mut aliases) in scope { + match aliases.len() { + 0 => (), + 1 => { + let alias = aliases.pop().unwrap(); + module.set_var(alias, value); + } + _ => { + let last_alias = aliases.pop().unwrap(); + for alias in aliases { + module.set_var(alias, value.clone()); + } + // Avoid cloning the last value + module.set_var(last_alias, value); + } + } + } #[cfg(not(feature = "no_function"))] let func_global = func_global.map(|v| v.into_boxed_slice()); @@ -1773,29 +1802,29 @@ impl Module { ) -> bool { let mut contains_indexed_global_functions = false; - module.modules.iter().for_each(|(name, m)| { + for (name, m) in &module.modules { // Index all the sub-modules first. path.push(name); if index_module(m, path, variables, functions, type_iterators) { contains_indexed_global_functions = true; } path.pop(); - }); + } // Index all variables - module.variables.iter().for_each(|(var_name, value)| { + for (var_name, value) in &module.variables { let hash_var = crate::calc_qualified_var_hash(path.iter().copied(), var_name); variables.insert(hash_var, value.clone()); - }); + } // Index type iterators - module.type_iterators.iter().for_each(|(&type_id, func)| { + for (&type_id, func) in &module.type_iterators { type_iterators.insert(type_id, func.clone()); contains_indexed_global_functions = true; - }); + } // Index all Rust functions - module.functions.iter().for_each(|(&hash, f)| { + for (&hash, f) in &module.functions { match f.metadata.namespace { FnNamespace::Global => { // Flatten all functions with global namespace @@ -1806,7 +1835,7 @@ impl Module { } match f.metadata.access { FnAccess::Public => (), - FnAccess::Private => return, // Do not index private functions + FnAccess::Private => continue, // Do not index private functions } if !f.func.is_script() { @@ -1824,7 +1853,7 @@ impl Module { ); functions.insert(hash_qualified_script, f.func.clone()); } - }); + } contains_indexed_global_functions } diff --git a/src/module/resolvers/collection.rs b/src/module/resolvers/collection.rs index 6deb7c3d..8495fb8d 100644 --- a/src/module/resolvers/collection.rs +++ b/src/module/resolvers/collection.rs @@ -124,7 +124,7 @@ impl ModuleResolver for ModuleResolversCollection { path: &str, pos: Position, ) -> RhaiResultOf> { - for resolver in self.0.iter() { + for resolver in &self.0 { match resolver.resolve(engine, source_path, path, pos) { Ok(module) => return Ok(module), Err(err) => match *err { diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 71828d81..a79f12ab 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -1,9 +1,10 @@ #![cfg(not(feature = "no_std"))] #![cfg(not(target_family = "wasm"))] -use crate::func::native::shared_write_lock; +use crate::func::native::locked_write; use crate::{ - Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, ERR, + Engine, GlobalRuntimeState, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Scope, + Shared, ERR, }; use std::{ @@ -207,12 +208,12 @@ impl FileModuleResolver { let file_path = self.get_file_path(path.as_ref(), source_path); - shared_write_lock(&self.cache).contains_key(&file_path) + locked_write(&self.cache).contains_key(&file_path) } /// Empty the internal cache. #[inline] pub fn clear_cache(&mut self) -> &mut Self { - shared_write_lock(&self.cache).clear(); + locked_write(&self.cache).clear(); self } /// Remove the specified path from internal cache. @@ -227,7 +228,7 @@ impl FileModuleResolver { ) -> Option> { let file_path = self.get_file_path(path.as_ref(), source_path.as_ref().map(<_>::as_ref)); - shared_write_lock(&self.cache) + locked_write(&self.cache) .remove_entry(&file_path) .map(|(_, v)| v) } @@ -252,24 +253,25 @@ impl FileModuleResolver { file_path.set_extension(self.extension.as_str()); // Force extension file_path } -} -impl ModuleResolver for FileModuleResolver { - fn resolve( + /// Resolve a module based on a path. + fn impl_resolve( &self, engine: &Engine, - source_path: Option<&str>, + global: Option<&mut GlobalRuntimeState>, + source: Option<&str>, path: &str, pos: Position, - ) -> RhaiResultOf> { + ) -> Result, Box> { // Load relative paths from source if there is no base path specified - let source_path = - source_path.and_then(|p| Path::new(p).parent().map(|p| p.to_string_lossy())); + let source_path = global + .as_ref() + .and_then(|g| g.source()) + .or(source) + .and_then(|p| Path::new(p).parent().map(|p| p.to_string_lossy())); - // Construct the script file path let file_path = self.get_file_path(path, source_path.as_ref().map(|p| p.as_ref())); - // See if it is cached if self.is_cache_enabled() { #[cfg(not(feature = "sync"))] let c = self.cache.borrow(); @@ -281,7 +283,6 @@ impl ModuleResolver for FileModuleResolver { } } - // Load the script file and compile it let scope = Scope::new(); let mut ast = engine @@ -295,18 +296,43 @@ impl ModuleResolver for FileModuleResolver { ast.set_source(path); - // Make a module from the AST - let m: Shared = Module::eval_ast_as_new(scope, &ast, engine) - .map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))? - .into(); + let m: Shared = if let Some(global) = global { + Module::eval_ast_as_new_raw(engine, scope, global, &ast) + } else { + Module::eval_ast_as_new(scope, &ast, engine) + } + .map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))? + .into(); - // Put it into the cache if self.is_cache_enabled() { - shared_write_lock(&self.cache).insert(file_path, m.clone()); + locked_write(&self.cache).insert(file_path, m.clone()); } Ok(m) } +} + +impl ModuleResolver for FileModuleResolver { + fn resolve_raw( + &self, + engine: &Engine, + global: &mut GlobalRuntimeState, + path: &str, + pos: Position, + ) -> RhaiResultOf> { + self.impl_resolve(engine, Some(global), None, path, pos) + } + + #[inline(always)] + fn resolve( + &self, + engine: &Engine, + source: Option<&str>, + path: &str, + pos: Position, + ) -> RhaiResultOf> { + self.impl_resolve(engine, None, source, path, pos) + } /// Resolve an `AST` based on a path string. /// diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs index cf14c2b8..ef1c0d54 100644 --- a/src/module/resolvers/mod.rs +++ b/src/module/resolvers/mod.rs @@ -1,5 +1,5 @@ use crate::func::native::SendSync; -use crate::{Engine, Module, Position, RhaiResultOf, Shared, AST}; +use crate::{Engine, GlobalRuntimeState, Module, Position, RhaiResultOf, Shared, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -21,11 +21,26 @@ pub trait ModuleResolver: SendSync { fn resolve( &self, engine: &Engine, - source_path: Option<&str>, + source: Option<&str>, path: &str, pos: Position, ) -> RhaiResultOf>; + /// Resolve a module based on a path string, given a [`GlobalRuntimeState`]. + /// + /// # WARNING - Low Level API + /// + /// This function is very low level. + fn resolve_raw( + &self, + engine: &Engine, + global: &mut GlobalRuntimeState, + path: &str, + pos: Position, + ) -> RhaiResultOf> { + self.resolve(engine, global.source(), path, pos) + } + /// Resolve an `AST` based on a path string. /// /// Returns [`None`] (default) if such resolution is not supported @@ -40,7 +55,7 @@ pub trait ModuleResolver: SendSync { fn resolve_ast( &self, engine: &Engine, - source_path: Option<&str>, + source: Option<&str>, path: &str, pos: Position, ) -> Option> { diff --git a/src/optimizer.rs b/src/optimizer.rs index 2bf5da2c..5e4ec27d 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -138,7 +138,7 @@ impl<'a> OptimizerState<'a> { self.engine .call_native_fn( - &mut GlobalRuntimeState::new(), + &mut GlobalRuntimeState::new(&self.engine), &mut EvalState::new(), lib, fn_name, @@ -237,7 +237,7 @@ fn optimize_stmt_block( }); // Optimize each statement in the block - statements.iter_mut().for_each(|stmt| { + for stmt in statements.iter_mut() { match stmt { Stmt::Var(value_expr, x, options, _) => { if options.contains(AST_OPTION_CONSTANT) { @@ -260,7 +260,7 @@ fn optimize_stmt_block( // Optimize the statement _ => optimize_stmt(stmt, state, preserve_result), } - }); + } // Remove all pure statements except the last one let mut index = 0; @@ -640,7 +640,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // switch Stmt::Switch(match_expr, x, _) => { optimize_expr(match_expr, state, false); - x.cases.values_mut().for_each(|block| { + for block in x.cases.values_mut() { let statements = mem::take(&mut *block.statements); *block.statements = optimize_stmt_block(statements, state, preserve_result, true, false); @@ -652,7 +652,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b _ => block.condition = Some(condition), } } - }); + } // Remove false cases while let Some((&key, _)) = x.cases.iter().find(|(_, block)| match block.condition { @@ -1159,13 +1159,13 @@ fn optimize_top_level( ); // Add constants and variables from the scope - scope.iter().for_each(|(name, constant, value)| { + for (name, constant, value) in scope.iter() { if !constant { state.push_var(name, AccessMode::ReadWrite, None); } else { state.push_var(name, AccessMode::ReadOnly, Some(value)); } - }); + } statements = optimize_stmt_block(statements, &mut state, true, false, true); statements @@ -1191,9 +1191,8 @@ pub fn optimize_into_ast( // We only need the script library's signatures for optimization purposes let mut lib2 = crate::Module::new(); - functions - .iter() - .map(|fn_def| crate::ast::ScriptFnDef { + for fn_def in &functions { + lib2.set_script_fn(crate::ast::ScriptFnDef { name: fn_def.name.clone(), access: fn_def.access, body: crate::ast::StmtBlock::NONE, @@ -1204,33 +1203,25 @@ pub fn optimize_into_ast( #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] comments: None, - }) - .for_each(|fn_def| { - lib2.set_script_fn(fn_def); }); + } let lib2 = &[&lib2]; - functions - .into_iter() - .map(|fn_def| { - let mut fn_def = crate::func::native::shared_take_or_clone(fn_def); + for fn_def in functions { + let mut fn_def = crate::func::native::shared_take_or_clone(fn_def); - // Optimize the function body - let body = mem::take(&mut *fn_def.body); + // Optimize the function body + let body = mem::take(&mut *fn_def.body); - *fn_def.body = - optimize_top_level(body, engine, scope, lib2, optimization_level); + *fn_def.body = optimize_top_level(body, engine, scope, lib2, optimization_level); - fn_def - }) - .for_each(|fn_def| { - module.set_script_fn(fn_def); - }); - } else { - functions.into_iter().for_each(|fn_def| { module.set_script_fn(fn_def); - }); + } + } else { + for fn_def in functions { + module.set_script_fn(fn_def); + } } module diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index d0319a98..9e5073d7 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -189,7 +189,7 @@ fn collect_fn_metadata( .for_each(|(_, _, _, _, f)| { list.push(make_metadata(dict, Some(namespace.clone()), f).into()) }); - module.iter_sub_modules().for_each(|(ns, m)| { + for (ns, m) in module.iter_sub_modules() { let ns = format!( "{}{}{}", namespace, @@ -197,11 +197,12 @@ fn collect_fn_metadata( ns ); scan_module(list, dict, ns.into(), m.as_ref(), filter) - }); + } } - ctx.iter_imports_raw() - .for_each(|(ns, m)| scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter)); + for (ns, m) in ctx.iter_imports_raw() { + scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter) + } } list diff --git a/src/parser.rs b/src/parser.rs index c3b06e1a..d205b1dc 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3450,9 +3450,9 @@ impl Engine { { let mut m = crate::Module::new(); - _lib.into_iter().for_each(|fn_def| { + for fn_def in _lib { m.set_script_fn(fn_def); - }); + } return Ok(AST::new(statements, m)); } diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index ded1e362..38c6f721 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -206,9 +206,9 @@ impl Engine { let _ast = ast; let mut global = ModuleMetadata::new(); - self.global_sub_modules.iter().for_each(|(name, m)| { + for (name, m) in &self.global_sub_modules { global.modules.insert(name, m.as_ref().into()); - }); + } self.global_modules .iter() @@ -221,11 +221,11 @@ impl Engine { }); #[cfg(not(feature = "no_function"))] - _ast.shared_lib().iter_fn().for_each(|f| { + for f in _ast.shared_lib().iter_fn() { let mut meta: FnMetadata = f.into(); meta.namespace = FnNamespace::Global; global.functions.push(meta); - }); + } global.functions.sort(); diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index d61cd612..0b8fdf80 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1119,18 +1119,18 @@ impl Dynamic { #[cfg(not(feature = "no_index"))] Union::Array(ref mut a, _, ref mut access) => { *access = typ; - a.iter_mut().for_each(|v| { + for v in a.iter_mut() { v.set_access_mode(typ); - }); + } } #[cfg(not(feature = "no_index"))] Union::Blob(_, _, ref mut access) => *access = typ, #[cfg(not(feature = "no_object"))] Union::Map(ref mut m, _, ref mut access) => { *access = typ; - m.values_mut().for_each(|v| { + for v in m.values_mut() { v.set_access_mode(typ); - }); + } } #[cfg(not(feature = "no_std"))] Union::TimeStamp(_, _, ref mut access) => *access = typ, @@ -1708,14 +1708,14 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, _, _) => { - let value = crate::func::native::shared_write_lock(cell); + let guard = crate::func::native::locked_write(cell); - if (*value).type_id() != TypeId::of::() + if (*guard).type_id() != TypeId::of::() && TypeId::of::() != TypeId::of::() { return None; } else { - return Some(DynamicWriteLock(DynamicWriteLockInner::Guard(value))); + return Some(DynamicWriteLock(DynamicWriteLockInner::Guard(guard))); } } _ => (), diff --git a/src/types/scope.rs b/src/types/scope.rs index e6b0de89..2f275b00 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -611,9 +611,9 @@ impl Scope<'_> { impl> Extend<(K, Dynamic)> for Scope<'_> { #[inline] fn extend>(&mut self, iter: T) { - iter.into_iter().for_each(|(name, value)| { + for (name, value) in iter { self.push_dynamic_value(name, AccessMode::ReadWrite, value); - }); + } } } @@ -629,7 +629,7 @@ impl> FromIterator<(K, Dynamic)> for Scope<'_> { impl> Extend<(K, bool, Dynamic)> for Scope<'_> { #[inline] fn extend>(&mut self, iter: T) { - iter.into_iter().for_each(|(name, is_constant, value)| { + for (name, is_constant, value) in iter { self.push_dynamic_value( name, if is_constant { @@ -639,7 +639,7 @@ impl> Extend<(K, bool, Dynamic)> for Scope<'_> { }, value, ); - }); + } } } diff --git a/tests/debugging.rs b/tests/debugging.rs index 55b03786..a329a533 100644 --- a/tests/debugging.rs +++ b/tests/debugging.rs @@ -1,9 +1,12 @@ #![cfg(feature = "debugging")] -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Dynamic, Engine, EvalAltResult, INT}; #[cfg(not(feature = "no_index"))] use rhai::Array; +#[cfg(not(feature = "no_object"))] +use rhai::Map; + #[test] fn test_debugging() -> Result<(), Box> { let engine = Engine::new(); @@ -32,3 +35,44 @@ fn test_debugging() -> Result<(), Box> { Ok(()) } + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_debugger_state() -> Result<(), Box> { + let mut engine = Engine::new(); + + engine.on_debugger( + || { + // Say, use an object map for the debugger state + let mut state = Map::new(); + // Initialize properties + state.insert("hello".into(), (42 as INT).into()); + state.insert("foo".into(), false.into()); + Dynamic::from_map(state) + }, + |context, _, _, _| { + // Get global runtime state + let global = context.global_runtime_state_mut(); + + // Get debugger + let debugger = &mut global.debugger; + + // Print debugger state - which is an object map + println!("Current state = {}", debugger.state()); + + // Modify state + let mut state = debugger.state_mut().write_lock::().unwrap(); + let hello = state.get("hello").unwrap().as_int().unwrap(); + state.insert("hello".into(), (hello + 1).into()); + state.insert("foo".into(), true.into()); + state.insert("something_new".into(), "hello, world!".into()); + + // Continue with debugging + Ok(rhai::debugger::DebuggerCommand::StepInto) + }, + ); + + engine.run("let x = 42;")?; + + Ok(()) +}