Refactor and add state to debugger.

This commit is contained in:
Stephen Chung 2022-01-28 18:59:18 +08:00
parent 20baae71d4
commit 66af69aaff
30 changed files with 693 additions and 624 deletions

View File

@ -17,24 +17,21 @@ struct Handler {
} }
fn print_scope(scope: &Scope) { fn print_scope(scope: &Scope) {
scope for (i, (name, constant, value)) in scope.iter_raw().enumerate() {
.iter_raw() #[cfg(not(feature = "no_closure"))]
.enumerate() let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
.for_each(|(i, (name, constant, value))| { #[cfg(feature = "no_closure")]
#[cfg(not(feature = "no_closure"))] let value_is_shared = "";
let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")]
let value_is_shared = "";
println!( println!(
"[{}] {}{}{} = {:?}", "[{}] {}{}{} = {:?}",
i + 1, i + 1,
if constant { "const " } else { "" }, if constant { "const " } else { "" },
name, name,
value_is_shared, value_is_shared,
*value.read_lock::<Dynamic>().unwrap(), *value.read_lock::<Dynamic>().unwrap(),
) )
}); }
println!(); println!();
} }

View File

@ -13,24 +13,21 @@ struct Handler {
} }
fn print_scope(scope: &Scope) { fn print_scope(scope: &Scope) {
scope for (i, (name, constant, value)) in scope.iter_raw().enumerate() {
.iter_raw() #[cfg(not(feature = "no_closure"))]
.enumerate() let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
.for_each(|(i, (name, constant, value))| { #[cfg(feature = "no_closure")]
#[cfg(not(feature = "no_closure"))] let value_is_shared = "";
let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")]
let value_is_shared = "";
println!( println!(
"[{}] {}{}{} = {:?}", "[{}] {}{}{} = {:?}",
i + 1, i + 1,
if constant { "const " } else { "" }, if constant { "const " } else { "" },
name, name,
value_is_shared, value_is_shared,
*value.read_lock::<Dynamic>().unwrap(), *value.read_lock::<Dynamic>().unwrap(),
) )
}); }
println!(); println!();
} }

View File

@ -16,24 +16,21 @@ struct Handler {
} }
fn print_scope(scope: &Scope) { fn print_scope(scope: &Scope) {
scope for (i, (name, constant, value)) in scope.iter_raw().enumerate() {
.iter_raw() #[cfg(not(feature = "no_closure"))]
.enumerate() let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
.for_each(|(i, (name, constant, value))| { #[cfg(feature = "no_closure")]
#[cfg(not(feature = "no_closure"))] let value_is_shared = "";
let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")]
let value_is_shared = "";
println!( println!(
"[{}] {}{}{} = {:?}", "[{}] {}{}{} = {:?}",
i + 1, i + 1,
if constant { "const " } else { "" }, if constant { "const " } else { "" },
name, name,
value_is_shared, value_is_shared,
*value.read_lock::<Dynamic>().unwrap(), *value.read_lock::<Dynamic>().unwrap(),
) )
}); }
println!(); println!();
} }

View File

@ -154,10 +154,7 @@ impl Engine {
arg_values: impl AsMut<[Dynamic]>, arg_values: impl AsMut<[Dynamic]>,
) -> RhaiResult { ) -> RhaiResult {
let state = &mut EvalState::new(); let state = &mut EvalState::new();
let global = &mut GlobalRuntimeState::new(); let global = &mut GlobalRuntimeState::new(self);
#[cfg(feature = "debugging")]
global.debugger.activate(self.debugger.is_some());
let statements = ast.statements(); let statements = ast.statements();

View File

@ -184,10 +184,7 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> RhaiResultOf<T> { ) -> RhaiResultOf<T> {
let global = &mut GlobalRuntimeState::new(); let global = &mut GlobalRuntimeState::new(self);
#[cfg(feature = "debugging")]
global.debugger.activate(self.debugger.is_some());
let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?; let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?;

View File

@ -260,12 +260,13 @@ impl Engine {
self.debug = Some(Box::new(callback)); self.debug = Some(Box::new(callback));
self self
} }
/// _(debugging)_ Register a callback for debugging. /// _(debugging)_ Register callbacks for debugging.
/// Exported under the `debugging` feature only. /// Exported under the `debugging` feature only.
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
#[inline(always)] #[inline(always)]
pub fn on_debugger( pub fn on_debugger(
&mut self, &mut self,
init: impl Fn() -> Dynamic + SendSync + 'static,
callback: impl Fn( callback: impl Fn(
&mut EvalContext, &mut EvalContext,
crate::ast::ASTNode, crate::ast::ASTNode,
@ -275,7 +276,7 @@ impl Engine {
+ SendSync + SendSync
+ 'static, + 'static,
) -> &mut Self { ) -> &mut Self {
self.debugger = Some(Box::new(callback)); self.debugger = Some((Box::new(init), Box::new(callback)));
self self
} }
} }

View File

@ -1037,9 +1037,9 @@ impl Engine {
signatures.extend(self.global_namespace().gen_fn_signatures()); 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(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f)))
}); }
signatures.extend( signatures.extend(
self.global_modules self.global_modules

View File

@ -45,9 +45,7 @@ impl Engine {
#[inline] #[inline]
pub fn run_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> { pub fn run_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> {
let state = &mut EvalState::new(); let state = &mut EvalState::new();
let global = &mut GlobalRuntimeState::new(); let global = &mut GlobalRuntimeState::new(self);
#[cfg(feature = "debugging")]
global.debugger.activate(self.debugger.is_some());
global.source = ast.source_raw().clone(); global.source = ast.source_raw().clone();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]

View File

@ -1,5 +1,5 @@
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
use rhai::{Dynamic, Engine, EvalAltResult, Position, Scope}; use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, Position, Scope};
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
use rhai::debugger::DebuggerCommand; use rhai::debugger::DebuggerCommand;
@ -121,34 +121,31 @@ fn print_scope(scope: &Scope, dedup: bool) {
scope scope
}; };
scope for (i, (name, constant, value)) in scope.iter_raw().enumerate() {
.iter_raw() #[cfg(not(feature = "no_closure"))]
.enumerate() let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
.for_each(|(i, (name, constant, value))| { #[cfg(feature = "no_closure")]
#[cfg(not(feature = "no_closure"))] let value_is_shared = "";
let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")]
let value_is_shared = "";
if dedup { if dedup {
println!( println!(
"{}{}{} = {:?}", "{}{}{} = {:?}",
if constant { "const " } else { "" }, if constant { "const " } else { "" },
name, name,
value_is_shared, value_is_shared,
*value.read_lock::<Dynamic>().unwrap(), *value.read_lock::<Dynamic>().unwrap(),
); );
} else { } else {
println!( println!(
"[{}] {}{}{} = {:?}", "[{}] {}{}{} = {:?}",
i + 1, i + 1,
if constant { "const " } else { "" }, if constant { "const " } else { "" },
name, name,
value_is_shared, value_is_shared,
*value.read_lock::<Dynamic>().unwrap(), *value.read_lock::<Dynamic>().unwrap(),
); );
} }
}); }
println!(); println!();
} }
@ -233,198 +230,221 @@ fn main() {
// Hook up debugger // Hook up debugger
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect(); let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect();
#[cfg(not(feature = "sync"))] engine.on_debugger(
let current_source = std::cell::RefCell::new(rhai::Identifier::new_const()); // Store the current source in the debugger state
#[cfg(feature = "sync")] || "".into(),
let current_source = std::sync::RwLock::new(rhai::Identifier::new_const()); // Main debugging interface
move |context, node, source, pos| {
{
let current_source = &mut *context
.global_runtime_state_mut()
.debugger
.state_mut()
.write_lock::<ImmutableString>()
.unwrap();
engine.on_debugger(move |context, node, source, pos| { let src = source.unwrap_or("");
#[cfg(not(feature = "sync"))]
let current_source = &mut *current_source.borrow_mut();
#[cfg(feature = "sync")]
let current_source = &mut *current_source.write().unwrap();
// Check source // Check source
if let Some(src) = source { if src != current_source {
if src != current_source { println!(">>> Source => {}", source.unwrap_or("main script"));
println!(">>> Source => {}", src); *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 // Read stdin for commands
let mut input = String::new(); let mut input = String::new();
loop { loop {
print!("rhai-dbg> "); print!("rhai-dbg> ");
stdout().flush().expect("couldn't flush stdout"); stdout().flush().expect("couldn't flush stdout");
input.clear(); input.clear();
match stdin().read_line(&mut input) { match stdin().read_line(&mut input) {
Ok(0) => break Ok(DebuggerCommand::Continue), Ok(0) => break Ok(DebuggerCommand::Continue),
Ok(_) => match input Ok(_) => match input
.trim() .trim()
.split_whitespace() .split_whitespace()
.collect::<Vec<_>>() .collect::<Vec<_>>()
.as_slice() .as_slice()
{ {
["help", ..] => print_debug_help(), ["help", ..] => print_debug_help(),
["exit", ..] | ["quit", ..] | ["kill", ..] => { ["exit", ..] | ["quit", ..] | ["kill", ..] => {
println!("Script terminated. Bye!"); println!("Script terminated. Bye!");
exit(0); 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::<Dynamic>(var_name) {
if value.is::<()>() {
println!("=> ()");
} else {
println!("=> {}", value);
}
} else {
eprintln!("Variable not found: {}", var_name);
} }
} ["node", ..] => {
["print", ..] => print_scope(context.scope(), true), println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos);
["imports", ..] => { println!();
context }
.global_runtime_state() ["continue", ..] => break Ok(DebuggerCommand::Continue),
.scan_imports_raw() [] | ["step", ..] => break Ok(DebuggerCommand::StepInto),
.enumerate() ["over", ..] => break Ok(DebuggerCommand::StepOver),
.for_each(|(i, (name, module))| { ["next", ..] => break Ok(DebuggerCommand::Next),
["scope", ..] => print_scope(context.scope(), false),
["print", var_name, ..] => {
if let Some(value) = context.scope().get_value::<Dynamic>(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!( println!(
"[{}] {} = {}", "[{}] {} = {}",
i + 1, i + 1,
name, name,
module.id().unwrap_or("<unknown>") module.id().unwrap_or("<unknown>")
); );
}); }
println!(); println!();
} }
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
["backtrace", ..] => { ["backtrace", ..] => {
context for frame in context
.global_runtime_state() .global_runtime_state()
.debugger .debugger
.call_stack() .call_stack()
.iter() .iter()
.rev() .rev()
.for_each(|frame| println!("{}", 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!("[{}] {}", i + 1, bp), }
}), ["clear", ..] => {
["enable", n, ..] => { context
if let Ok(n) = n.parse::<usize>() {
let range = 1..=context
.global_runtime_state_mut() .global_runtime_state_mut()
.debugger .debugger
.break_points_mut()
.clear();
println!("All break-points cleared.");
}
["breakpoints", ..] => Iterator::for_each(
context
.global_runtime_state()
.debugger
.break_points() .break_points()
.len(); .iter()
if range.contains(&n) { .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::<usize>() {
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::<usize>() {
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::<usize>() {
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::<usize>() {
let bp = rhai::debugger::BreakPoint::AtFunctionCall {
name: fn_name.trim().into(),
args,
enabled: true,
};
println!("Break-point added for {}", bp);
context context
.global_runtime_state_mut() .global_runtime_state_mut()
.debugger .debugger
.break_points_mut() .break_points_mut()
.get_mut(n - 1) .push(bp);
.unwrap()
.enable(true);
println!("Break-point #{} enabled.", n)
} else { } else {
eprintln!("Invalid break-point: {}", n); eprintln!("Invalid number of arguments: '{}'", args);
} }
} else {
eprintln!("Invalid break-point: '{}'", n);
} }
} // Property name
["disable", n, ..] => { #[cfg(not(feature = "no_object"))]
if let Ok(n) = n.parse::<usize>() { ["break", param] if param.starts_with('.') && param.len() > 1 => {
let range = 1..=context let bp = rhai::debugger::BreakPoint::AtProperty {
.global_runtime_state_mut() name: param[1..].into(),
.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::<usize>() {
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::<usize>() {
let bp = rhai::debugger::BreakPoint::AtFunctionCall {
name: fn_name.trim().into(),
args,
enabled: true, enabled: true,
}; };
println!("Break-point added for {}", bp); println!("Break-point added for {}", bp);
@ -433,38 +453,51 @@ fn main() {
.debugger .debugger
.break_points_mut() .break_points_mut()
.push(bp); .push(bp);
} else {
eprintln!("Invalid number of arguments: '{}'", args);
} }
} // Numeric parameter
// Property name #[cfg(not(feature = "no_position"))]
#[cfg(not(feature = "no_object"))] ["break", param] if param.parse::<usize>().is_ok() => {
["break", param] if param.starts_with('.') && param.len() > 1 => { let n = param.parse::<usize>().unwrap();
let bp = rhai::debugger::BreakPoint::AtProperty { let range = if source.is_none() {
name: param[1..].into(), 1..=lines.len()
enabled: true, } else {
}; 1..=(u16::MAX as usize)
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::<usize>().is_ok() => {
let n = param.parse::<usize>().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 { let bp = rhai::debugger::BreakPoint::AtPosition {
source: source.unwrap_or("").into(), source: source.unwrap_or("").into(),
pos: Position::new(n as u16, 0), pos,
enabled: true, enabled: true,
}; };
println!("Break-point added {}", bp); println!("Break-point added {}", bp);
@ -473,52 +506,25 @@ fn main() {
.debugger .debugger
.break_points_mut() .break_points_mut()
.push(bp); .push(bp);
} else {
eprintln!("Invalid line number: {}", n);
} }
} ["throw"] => {
// Function name parameter break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into())
["break", param] => { }
let bp = rhai::debugger::BreakPoint::AtFunctionName { ["throw", _msg, ..] => {
name: param.trim().into(), let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or("");
enabled: true, break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
}; }
println!("Break-point added for {}", bp); ["run", ..] => {
context println!("Restarting script...");
.global_runtime_state_mut() break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
.debugger }
.break_points_mut() [cmd, ..] => eprintln!("Invalid debugger command: '{}'", cmd),
.push(bp); },
} Err(err) => panic!("input error: {}", err),
#[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),
} }
} },
}); );
// Set a file module resolver without caching // Set a file module resolver without caching
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]

View File

@ -61,24 +61,21 @@ fn print_help() {
/// Display the scope. /// Display the scope.
fn print_scope(scope: &Scope) { fn print_scope(scope: &Scope) {
scope for (i, (name, constant, value)) in scope.iter_raw().enumerate() {
.iter_raw() #[cfg(not(feature = "no_closure"))]
.enumerate() let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
.for_each(|(i, (name, constant, value))| { #[cfg(feature = "no_closure")]
#[cfg(not(feature = "no_closure"))] let value_is_shared = "";
let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")]
let value_is_shared = "";
println!( println!(
"[{}] {}{}{} = {:?}", "[{}] {}{}{} = {:?}",
i + 1, i + 1,
if constant { "const " } else { "" }, if constant { "const " } else { "" },
name, name,
value_is_shared, value_is_shared,
*value.read_lock::<Dynamic>().unwrap(), *value.read_lock::<Dynamic>().unwrap(),
) )
}); }
println!(); println!();
} }
@ -282,13 +279,13 @@ fn main() {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
"functions" => { "functions" => {
// print a list of all registered functions // print a list of all registered functions
engine for f in engine.gen_fn_signatures(false) {
.gen_fn_signatures(false) println!("{}", f)
.into_iter() }
.for_each(|f| println!("{}", f));
#[cfg(not(feature = "no_function"))] for f in main_ast.iter_functions() {
main_ast.iter_functions().for_each(|f| println!("{}", f)); println!("{}", f)
}
println!(); println!();
continue; continue;

View File

@ -140,7 +140,10 @@ pub struct Engine {
/// Callback closure for debugging. /// Callback closure for debugging.
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
pub(crate) debugger: Option<Box<crate::eval::OnDebuggerCallback>>, pub(crate) debugger: Option<(
Box<crate::eval::OnDebuggingInit>,
Box<crate::eval::OnDebuggerCallback>,
)>,
} }
impl fmt::Debug for Engine { impl fmt::Debug for Engine {

View File

@ -8,11 +8,18 @@ use std::fmt;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; 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"))] #[cfg(not(feature = "sync"))]
pub type OnDebuggerCallback = pub type OnDebuggerCallback =
dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>; dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>;
/// A standard callback function for debugging. /// Callback function for debugging.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnDebuggerCallback = dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand> pub type OnDebuggerCallback = dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
+ Send + Send
@ -192,6 +199,8 @@ impl fmt::Display for CallStackFrame {
pub struct Debugger { pub struct Debugger {
/// The current status command. /// The current status command.
status: DebuggerCommand, status: DebuggerCommand,
/// The current state.
state: Dynamic,
/// The current set of break-points. /// The current set of break-points.
break_points: Vec<BreakPoint>, break_points: Vec<BreakPoint>,
/// The current function call stack. /// The current function call stack.
@ -200,28 +209,44 @@ pub struct Debugger {
} }
impl Debugger { impl Debugger {
/// Create a new [`Debugger`]. /// Create a new [`Debugger`] based on an [`Engine`].
pub const fn new() -> Self { #[inline(always)]
#[must_use]
pub fn new(engine: &Engine) -> Self {
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(), break_points: Vec::new(),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
call_stack: Vec::new(), call_stack: Vec::new(),
} }
} }
/// Get the function call stack depth. /// Get a reference to the current state.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
pub fn call_stack_len(&self) -> usize { #[must_use]
self.call_stack.len() 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. /// Get the current call stack.
/// ///
/// Not available under `no_function`. /// Not available under `no_function`.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
#[must_use]
pub fn call_stack(&self) -> &[CallStackFrame] { pub fn call_stack(&self) -> &[CallStackFrame] {
&self.call_stack &self.call_stack
} }
@ -258,10 +283,11 @@ impl Debugger {
pub fn status(&self) -> DebuggerCommand { pub fn status(&self) -> DebuggerCommand {
self.status self.status
} }
/// Set the status of this [`Debugger`]. /// Get a mutable reference to the current status of this [`Debugger`].
#[inline(always)] #[inline(always)]
pub fn set_status(&mut self, status: DebuggerCommand) { #[must_use]
self.status = status; pub fn status_mut(&mut self) -> &mut DebuggerCommand {
&mut self.status
} }
/// Set the status of this [`Debugger`]. /// Set the status of this [`Debugger`].
#[inline(always)] #[inline(always)]
@ -270,17 +296,8 @@ impl Debugger {
self.status = cmd; 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? /// Does a particular [`AST` Node][ASTNode] trigger a break-point?
#[must_use]
pub fn is_break_point(&self, src: &str, node: ASTNode) -> bool { pub fn is_break_point(&self, src: &str, node: ASTNode) -> bool {
let _src = src; let _src = src;
@ -347,7 +364,7 @@ impl Engine {
if let Some(cmd) = if let Some(cmd) =
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, node, level)? self.run_debugger_with_reset(scope, global, state, lib, this_ptr, node, level)?
{ {
global.debugger.set_status(cmd); global.debugger.status = cmd;
} }
Ok(()) Ok(())
@ -375,7 +392,7 @@ impl Engine {
node: impl Into<ASTNode<'a>>, node: impl Into<ASTNode<'a>>,
level: usize, level: usize,
) -> RhaiResultOf<Option<DebuggerCommand>> { ) -> RhaiResultOf<Option<DebuggerCommand>> {
if let Some(ref on_debugger) = self.debugger { if let Some((_, ref on_debugger)) = self.debugger {
let node = node.into(); let node = node.into();
// Skip transitive nodes // Skip transitive nodes
@ -415,19 +432,19 @@ impl Engine {
match command { match command {
DebuggerCommand::Continue => { DebuggerCommand::Continue => {
global.debugger.set_status(DebuggerCommand::Continue); global.debugger.status = DebuggerCommand::Continue;
Ok(None) Ok(None)
} }
DebuggerCommand::Next => { DebuggerCommand::Next => {
global.debugger.set_status(DebuggerCommand::Continue); global.debugger.status = DebuggerCommand::Continue;
Ok(Some(DebuggerCommand::Next)) Ok(Some(DebuggerCommand::Next))
} }
DebuggerCommand::StepInto => { DebuggerCommand::StepInto => {
global.debugger.set_status(DebuggerCommand::StepInto); global.debugger.status = DebuggerCommand::StepInto;
Ok(None) Ok(None)
} }
DebuggerCommand::StepOver => { DebuggerCommand::StepOver => {
global.debugger.set_status(DebuggerCommand::Continue); global.debugger.status = DebuggerCommand::Continue;
Ok(Some(DebuggerCommand::StepOver)) Ok(Some(DebuggerCommand::StepOver))
} }
} }

View File

@ -64,8 +64,8 @@ impl Engine {
// Qualified variable access // Qualified variable access
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
(_, Some((namespace, hash_var)), var_name) => { (_, Some((namespace, hash_var)), var_name) => {
// foo:bar::baz::VARIABLE
if let Some(module) = self.search_imports(global, state, namespace) { if let Some(module) = self.search_imports(global, state, namespace) {
// foo:bar::baz::VARIABLE
return match module.get_qualified_var(*hash_var) { return match module.get_qualified_var(*hash_var) {
Ok(target) => { Ok(target) => {
let mut target = target.clone(); let mut target = target.clone();
@ -89,18 +89,16 @@ impl Engine {
}; };
} }
// global::VARIABLE
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if namespace.len() == 1 && namespace[0].name == crate::engine::KEYWORD_GLOBAL { if namespace.len() == 1 && namespace[0].name == crate::engine::KEYWORD_GLOBAL {
// global::VARIABLE let mut guard = crate::func::native::locked_write(&global.constants);
let global_constants = global.constants_mut();
if let Some(mut guard) = global_constants { if let Some(value) = guard.get_mut(var_name) {
if let Some(value) = guard.get_mut(var_name) { let mut target: Target = value.clone().into();
let mut target: Target = value.clone().into(); // Module variables are constant
// Module variables are constant target.set_access_mode(AccessMode::ReadOnly);
target.set_access_mode(AccessMode::ReadOnly); return Ok((target.into(), *_var_pos));
return Ok((target.into(), *_var_pos));
}
} }
return Err(ERR::ErrorVariableNotFound( return Err(ERR::ErrorVariableNotFound(

View File

@ -1,13 +1,13 @@
//! Global runtime state. //! Global runtime state.
use crate::func::{CallableFunction, IteratorFn}; use crate::func::{CallableFunction, IteratorFn};
use crate::{Identifier, Module, Shared, StaticVec}; use crate::{Engine, Identifier, Module, Shared, StaticVec};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
any::TypeId, any::TypeId,
fmt, fmt,
iter::{FromIterator, Rev, Zip}, iter::{Rev, Zip},
marker::PhantomData, marker::PhantomData,
}; };
@ -44,8 +44,7 @@ pub struct GlobalRuntimeState<'a> {
/// Cache of globally-defined constants. /// Cache of globally-defined constants.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
constants: pub(crate) constants: crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>,
Option<Shared<crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>>>,
/// Debugging interface. /// Debugging interface.
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
pub debugger: super::Debugger, pub debugger: super::Debugger,
@ -53,18 +52,11 @@ pub struct GlobalRuntimeState<'a> {
dummy: PhantomData<&'a ()>, dummy: PhantomData<&'a ()>,
} }
impl Default for GlobalRuntimeState<'_> {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl GlobalRuntimeState<'_> { impl GlobalRuntimeState<'_> {
/// Create a new [`GlobalRuntimeState`]. /// Create a new [`GlobalRuntimeState`] based on an [`Engine`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new(engine: &Engine) -> Self {
Self { Self {
keys: StaticVec::new_const(), keys: StaticVec::new_const(),
modules: StaticVec::new_const(), modules: StaticVec::new_const(),
@ -77,9 +69,9 @@ impl GlobalRuntimeState<'_> {
fn_hash_indexing: (0, 0), fn_hash_indexing: (0, 0),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
constants: None, constants: std::collections::BTreeMap::new().into(),
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
debugger: crate::eval::Debugger::new(), debugger: crate::eval::Debugger::new(engine),
dummy: PhantomData::default(), dummy: PhantomData::default(),
} }
} }
@ -193,33 +185,6 @@ impl GlobalRuntimeState<'_> {
s => Some(s), 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<Target = std::collections::BTreeMap<Identifier, crate::Dynamic>> + '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<Identifier>, 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. /// Get the pre-calculated index getter hash.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[must_use] #[must_use]
@ -262,22 +227,13 @@ impl IntoIterator for GlobalRuntimeState<'_> {
} }
} }
impl<K: Into<Identifier>, M: Into<Shared<Module>>> FromIterator<(K, M)> for GlobalRuntimeState<'_> {
#[inline]
fn from_iter<T: IntoIterator<Item = (K, M)>>(iter: T) -> Self {
let mut lib = Self::new();
lib.extend(iter);
lib
}
}
impl<K: Into<Identifier>, M: Into<Shared<Module>>> Extend<(K, M)> for GlobalRuntimeState<'_> { impl<K: Into<Identifier>, M: Into<Shared<Module>>> Extend<(K, M)> for GlobalRuntimeState<'_> {
#[inline] #[inline]
fn extend<T: IntoIterator<Item = (K, M)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (K, M)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(k, m)| { for (k, m) in iter {
self.keys.push(k.into()); self.keys.push(k.into());
self.modules.push(m.into()); self.modules.push(m.into());
}) }
} }
} }

View File

@ -14,7 +14,7 @@ pub use chaining::{ChainArgument, ChainType};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub use debugger::CallStackFrame; pub use debugger::CallStackFrame;
#[cfg(feature = "debugging")] #[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_context::EvalContext;
pub use eval_state::EvalState; pub use eval_state::EvalState;
pub use global_state::GlobalRuntimeState; pub use global_state::GlobalRuntimeState;

View File

@ -818,8 +818,8 @@ impl Engine {
&& entry_type == AccessMode::ReadOnly && entry_type == AccessMode::ReadOnly
&& lib.iter().any(|&m| !m.is_empty()) && lib.iter().any(|&m| !m.is_empty())
{ {
// Add a global constant if at top level and there are functions crate::func::native::locked_write(&global.constants)
global.set_constant(var_name.clone(), value.clone()); .insert(var_name.clone(), value.clone());
} }
if export { if export {
@ -869,23 +869,20 @@ impl Engine {
if let Ok(path) = path_result { if let Ok(path) = path_result {
use crate::ModuleResolver; use crate::ModuleResolver;
let source = match global.source.as_str() {
"" => None,
s => Some(s),
};
let path_pos = expr.position(); let path_pos = expr.position();
let module_result = global let resolver = global.embedded_module_resolver.clone();
.embedded_module_resolver
let module_result = resolver
.as_ref() .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, Err(err) if matches!(*err, ERR::ErrorModuleNotFound(_, _)) => None,
result => Some(result), result => Some(result),
}) })
.or_else(|| { .or_else(|| {
self.module_resolver self.module_resolver
.as_ref() .as_ref()
.map(|r| r.resolve(self, source, &path, path_pos)) .map(|r| r.resolve_raw(self, global, &path, path_pos))
}) })
.unwrap_or_else(|| { .unwrap_or_else(|| {
Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into()) Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into())

View File

@ -22,7 +22,7 @@ pub use hashing::{
combine_hashes, get_hasher, combine_hashes, get_hasher,
}; };
pub use native::{ 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, FnPlugin, IteratorFn, Locked, NativeCallContext, SendSync, Shared,
}; };
pub use plugin::PluginFunction; pub use plugin::PluginFunction;

View File

@ -286,23 +286,29 @@ impl<'a> NativeCallContext<'a> {
is_method_call: bool, is_method_call: bool,
args: &mut [&mut Dynamic], args: &mut [&mut Dynamic],
) -> RhaiResult { ) -> 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 fn_name = fn_name.as_ref();
let len = args.len(); let args_len = args.len();
let hash = if is_method_call { let hash = if is_method_call {
FnCallHashes::from_all( FnCallHashes::from_all(
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
calc_fn_hash(fn_name, len - 1), calc_fn_hash(fn_name, args_len - 1),
calc_fn_hash(fn_name, len), calc_fn_hash(fn_name, args_len),
) )
} else { } else {
calc_fn_hash(fn_name, len).into() calc_fn_hash(fn_name, args_len).into()
}; };
self.engine() self.engine()
.exec_fn_call( .exec_fn_call(
&mut self.global.cloned().unwrap_or_else(GlobalRuntimeState::new), &mut global,
&mut EvalState::new(), &mut state,
self.lib, self.lib,
fn_name, fn_name,
hash, hash,
@ -353,11 +359,11 @@ pub fn shared_take<T>(value: Shared<T>) -> T {
shared_try_take(value).ok().expect("not shared") shared_try_take(value).ok().expect("not shared")
} }
/// Lock a [`Shared`] resource. /// Lock a [`Locked`] resource.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
#[allow(dead_code)] #[allow(dead_code)]
pub fn shared_write_lock<'a, T>(value: &'a Locked<T>) -> LockGuard<'a, T> { pub fn locked_write<'a, T>(value: &'a Locked<T>) -> LockGuard<'a, T> {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return value.borrow_mut(); return value.borrow_mut();
@ -365,20 +371,20 @@ pub fn shared_write_lock<'a, T>(value: &'a Locked<T>) -> LockGuard<'a, T> {
return value.write().unwrap(); return value.write().unwrap();
} }
/// A general function trail object. /// General function trail object.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult; pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult;
/// A general function trail object. /// General function trail object.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + Send + 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; 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"))] #[cfg(not(feature = "sync"))]
pub type IteratorFn = dyn Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>; pub type IteratorFn = dyn Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
/// A standard function that gets an iterator from a type. /// Function that gets an iterator from a type.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type IteratorFn = dyn Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync; pub type IteratorFn = dyn Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync;
@ -387,40 +393,40 @@ pub type FnPlugin = dyn PluginFunction;
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type FnPlugin = dyn PluginFunction + Send + 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 = "unchecked"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnProgressCallback = dyn Fn(u64) -> Option<Dynamic>; pub type OnProgressCallback = dyn Fn(u64) -> Option<Dynamic>;
/// A standard callback function for progress reporting. /// Callback function for progress reporting.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnProgressCallback = dyn Fn(u64) -> Option<Dynamic> + Send + Sync; pub type OnProgressCallback = dyn Fn(u64) -> Option<Dynamic> + Send + Sync;
/// A standard callback function for printing. /// Callback function for printing.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnPrintCallback = dyn Fn(&str); pub type OnPrintCallback = dyn Fn(&str);
/// A standard callback function for printing. /// Callback function for printing.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnPrintCallback = dyn Fn(&str) + Send + Sync; pub type OnPrintCallback = dyn Fn(&str) + Send + Sync;
/// A standard callback function for debugging. /// Callback function for debugging.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnDebugCallback = dyn Fn(&str, Option<&str>, Position); pub type OnDebugCallback = dyn Fn(&str, Option<&str>, Position);
/// A standard callback function for debugging. /// Callback function for debugging.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnDebugCallback = dyn Fn(&str, Option<&str>, Position) + Send + 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"))] #[cfg(not(feature = "sync"))]
pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token; 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")] #[cfg(feature = "sync")]
pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token + Send + 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"))] #[cfg(not(feature = "sync"))]
pub type OnVarCallback = dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>>; pub type OnVarCallback = dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>>;
/// A standard callback function for variable access. /// Callback function for variable access.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnVarCallback = pub type OnVarCallback =
dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> + Send + Sync; dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> + Send + Sync;

View File

@ -74,7 +74,7 @@ impl Engine {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
#[cfg(not(feature = "no_function"))] #[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 // Put arguments into scope as variables
scope.extend(fn_def.params.iter().cloned().zip(args.into_iter().map(|v| { scope.extend(fn_def.params.iter().cloned().zip(args.into_iter().map(|v| {
@ -115,10 +115,9 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
if let Some(ref modules) = fn_def.global { if let Some(ref modules) = fn_def.global {
modules for (n, m) in modules.iter().cloned() {
.iter() global.push_import(n, m)
.cloned() }
.for_each(|(n, m)| global.push_import(n, m));
} }
// Evaluate the function // Evaluate the function

View File

@ -1401,9 +1401,9 @@ impl Module {
/// Sub-modules are flattened onto the root [`Module`], with higher level overriding lower level. /// Sub-modules are flattened onto the root [`Module`], with higher level overriding lower level.
#[inline] #[inline]
pub fn combine_flatten(&mut self, other: Self) -> &mut Self { 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.combine_flatten(shared_take_or_clone(m));
}); }
self.variables.extend(other.variables.into_iter()); self.variables.extend(other.variables.into_iter());
self.functions.extend(other.functions.into_iter()); self.functions.extend(other.functions.into_iter());
self.type_iterators.extend(other.type_iterators.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. /// Only items not existing in this [`Module`] are added.
#[inline] #[inline]
pub fn fill_with(&mut self, other: &Self) -> &mut Self { 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) { if !self.modules.contains_key(k) {
self.modules.insert(k.clone(), v.clone()); 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) { if !self.variables.contains_key(k) {
self.variables.insert(k.clone(), v.clone()); 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()); 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.type_iterators.entry(k).or_insert(v.clone());
}); }
self.all_functions.clear(); self.all_functions.clear();
self.all_variables.clear(); self.all_variables.clear();
self.all_type_iterators.clear(); self.all_type_iterators.clear();
@ -1455,12 +1455,11 @@ impl Module {
other: &Self, other: &Self,
_filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool + Copy, _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool + Copy,
) -> &mut Self { ) -> &mut Self {
#[cfg(not(feature = "no_function"))] for (k, v) in &other.modules {
other.modules.iter().for_each(|(k, v)| {
let mut m = Self::new(); let mut m = Self::new();
m.merge_filtered(v, _filter); m.merge_filtered(v, _filter);
self.set_sub_module(k.clone(), m); self.set_sub_module(k.clone(), m);
}); }
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
self.modules self.modules
.extend(other.modules.iter().map(|(k, v)| (k.clone(), v.clone()))); .extend(other.modules.iter().map(|(k, v)| (k.clone(), v.clone())));
@ -1658,60 +1657,90 @@ impl Module {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)]
pub fn eval_ast_as_new( pub fn eval_ast_as_new(
scope: crate::Scope, scope: crate::Scope,
ast: &crate::AST, ast: &crate::AST,
engine: &crate::Engine, engine: &crate::Engine,
) -> RhaiResultOf<Self> {
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<Self> { ) -> RhaiResultOf<Self> {
let mut scope = scope; 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_imports_len = global.num_imports();
let orig_source = global.source.clone();
let orig_constants = std::mem::take(&mut global.constants);
// Run the script // 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 // Create new module
let mut module = let mut module = Module::new();
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
});
// Extra modules left in the scope become sub-modules // Extra modules left become sub-modules
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
let mut func_global = None; let mut func_global = None;
global.into_iter().skip(orig_imports_len).for_each(|kv| { if result.is_ok() {
#[cfg(not(feature = "no_function"))] global
if func_global.is_none() { .scan_imports_raw()
func_global = Some(StaticVec::new()); .skip(orig_imports_len)
} .for_each(|(k, m)| {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
func_global.as_mut().expect("`Some`").push(kv.clone()); 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"))] #[cfg(not(feature = "no_function"))]
let func_global = func_global.map(|v| v.into_boxed_slice()); let func_global = func_global.map(|v| v.into_boxed_slice());
@ -1773,29 +1802,29 @@ impl Module {
) -> bool { ) -> bool {
let mut contains_indexed_global_functions = false; 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. // Index all the sub-modules first.
path.push(name); path.push(name);
if index_module(m, path, variables, functions, type_iterators) { if index_module(m, path, variables, functions, type_iterators) {
contains_indexed_global_functions = true; contains_indexed_global_functions = true;
} }
path.pop(); path.pop();
}); }
// Index all variables // 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); let hash_var = crate::calc_qualified_var_hash(path.iter().copied(), var_name);
variables.insert(hash_var, value.clone()); variables.insert(hash_var, value.clone());
}); }
// Index type iterators // 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()); type_iterators.insert(type_id, func.clone());
contains_indexed_global_functions = true; contains_indexed_global_functions = true;
}); }
// Index all Rust functions // Index all Rust functions
module.functions.iter().for_each(|(&hash, f)| { for (&hash, f) in &module.functions {
match f.metadata.namespace { match f.metadata.namespace {
FnNamespace::Global => { FnNamespace::Global => {
// Flatten all functions with global namespace // Flatten all functions with global namespace
@ -1806,7 +1835,7 @@ impl Module {
} }
match f.metadata.access { match f.metadata.access {
FnAccess::Public => (), FnAccess::Public => (),
FnAccess::Private => return, // Do not index private functions FnAccess::Private => continue, // Do not index private functions
} }
if !f.func.is_script() { if !f.func.is_script() {
@ -1824,7 +1853,7 @@ impl Module {
); );
functions.insert(hash_qualified_script, f.func.clone()); functions.insert(hash_qualified_script, f.func.clone());
} }
}); }
contains_indexed_global_functions contains_indexed_global_functions
} }

View File

@ -124,7 +124,7 @@ impl ModuleResolver for ModuleResolversCollection {
path: &str, path: &str,
pos: Position, pos: Position,
) -> RhaiResultOf<Shared<Module>> { ) -> RhaiResultOf<Shared<Module>> {
for resolver in self.0.iter() { for resolver in &self.0 {
match resolver.resolve(engine, source_path, path, pos) { match resolver.resolve(engine, source_path, path, pos) {
Ok(module) => return Ok(module), Ok(module) => return Ok(module),
Err(err) => match *err { Err(err) => match *err {

View File

@ -1,9 +1,10 @@
#![cfg(not(feature = "no_std"))] #![cfg(not(feature = "no_std"))]
#![cfg(not(target_family = "wasm"))] #![cfg(not(target_family = "wasm"))]
use crate::func::native::shared_write_lock; use crate::func::native::locked_write;
use crate::{ use crate::{
Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, ERR, Engine, GlobalRuntimeState, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Scope,
Shared, ERR,
}; };
use std::{ use std::{
@ -207,12 +208,12 @@ impl FileModuleResolver {
let file_path = self.get_file_path(path.as_ref(), source_path); 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. /// Empty the internal cache.
#[inline] #[inline]
pub fn clear_cache(&mut self) -> &mut Self { pub fn clear_cache(&mut self) -> &mut Self {
shared_write_lock(&self.cache).clear(); locked_write(&self.cache).clear();
self self
} }
/// Remove the specified path from internal cache. /// Remove the specified path from internal cache.
@ -227,7 +228,7 @@ impl FileModuleResolver {
) -> Option<Shared<Module>> { ) -> Option<Shared<Module>> {
let file_path = self.get_file_path(path.as_ref(), source_path.as_ref().map(<_>::as_ref)); 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) .remove_entry(&file_path)
.map(|(_, v)| v) .map(|(_, v)| v)
} }
@ -252,24 +253,25 @@ impl FileModuleResolver {
file_path.set_extension(self.extension.as_str()); // Force extension file_path.set_extension(self.extension.as_str()); // Force extension
file_path file_path
} }
}
impl ModuleResolver for FileModuleResolver { /// Resolve a module based on a path.
fn resolve( fn impl_resolve(
&self, &self,
engine: &Engine, engine: &Engine,
source_path: Option<&str>, global: Option<&mut GlobalRuntimeState>,
source: Option<&str>,
path: &str, path: &str,
pos: Position, pos: Position,
) -> RhaiResultOf<Shared<Module>> { ) -> Result<Shared<Module>, Box<crate::EvalAltResult>> {
// Load relative paths from source if there is no base path specified // Load relative paths from source if there is no base path specified
let source_path = let source_path = global
source_path.and_then(|p| Path::new(p).parent().map(|p| p.to_string_lossy())); .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())); 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() { if self.is_cache_enabled() {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
let c = self.cache.borrow(); 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 scope = Scope::new();
let mut ast = engine let mut ast = engine
@ -295,18 +296,43 @@ impl ModuleResolver for FileModuleResolver {
ast.set_source(path); ast.set_source(path);
// Make a module from the AST let m: Shared<Module> = if let Some(global) = global {
let m: Shared<Module> = Module::eval_ast_as_new(scope, &ast, engine) Module::eval_ast_as_new_raw(engine, scope, global, &ast)
.map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))? } else {
.into(); 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() { 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) Ok(m)
} }
}
impl ModuleResolver for FileModuleResolver {
fn resolve_raw(
&self,
engine: &Engine,
global: &mut GlobalRuntimeState,
path: &str,
pos: Position,
) -> RhaiResultOf<Shared<Module>> {
self.impl_resolve(engine, Some(global), None, path, pos)
}
#[inline(always)]
fn resolve(
&self,
engine: &Engine,
source: Option<&str>,
path: &str,
pos: Position,
) -> RhaiResultOf<Shared<Module>> {
self.impl_resolve(engine, None, source, path, pos)
}
/// Resolve an `AST` based on a path string. /// Resolve an `AST` based on a path string.
/// ///

View File

@ -1,5 +1,5 @@
use crate::func::native::SendSync; 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")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -21,11 +21,26 @@ pub trait ModuleResolver: SendSync {
fn resolve( fn resolve(
&self, &self,
engine: &Engine, engine: &Engine,
source_path: Option<&str>, source: Option<&str>,
path: &str, path: &str,
pos: Position, pos: Position,
) -> RhaiResultOf<Shared<Module>>; ) -> RhaiResultOf<Shared<Module>>;
/// 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<Shared<Module>> {
self.resolve(engine, global.source(), path, pos)
}
/// Resolve an `AST` based on a path string. /// Resolve an `AST` based on a path string.
/// ///
/// Returns [`None`] (default) if such resolution is not supported /// Returns [`None`] (default) if such resolution is not supported
@ -40,7 +55,7 @@ pub trait ModuleResolver: SendSync {
fn resolve_ast( fn resolve_ast(
&self, &self,
engine: &Engine, engine: &Engine,
source_path: Option<&str>, source: Option<&str>,
path: &str, path: &str,
pos: Position, pos: Position,
) -> Option<RhaiResultOf<AST>> { ) -> Option<RhaiResultOf<AST>> {

View File

@ -138,7 +138,7 @@ impl<'a> OptimizerState<'a> {
self.engine self.engine
.call_native_fn( .call_native_fn(
&mut GlobalRuntimeState::new(), &mut GlobalRuntimeState::new(&self.engine),
&mut EvalState::new(), &mut EvalState::new(),
lib, lib,
fn_name, fn_name,
@ -237,7 +237,7 @@ fn optimize_stmt_block(
}); });
// Optimize each statement in the block // Optimize each statement in the block
statements.iter_mut().for_each(|stmt| { for stmt in statements.iter_mut() {
match stmt { match stmt {
Stmt::Var(value_expr, x, options, _) => { Stmt::Var(value_expr, x, options, _) => {
if options.contains(AST_OPTION_CONSTANT) { if options.contains(AST_OPTION_CONSTANT) {
@ -260,7 +260,7 @@ fn optimize_stmt_block(
// Optimize the statement // Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result), _ => optimize_stmt(stmt, state, preserve_result),
} }
}); }
// Remove all pure statements except the last one // Remove all pure statements except the last one
let mut index = 0; let mut index = 0;
@ -640,7 +640,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// switch // switch
Stmt::Switch(match_expr, x, _) => { Stmt::Switch(match_expr, x, _) => {
optimize_expr(match_expr, state, false); 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); let statements = mem::take(&mut *block.statements);
*block.statements = *block.statements =
optimize_stmt_block(statements, state, preserve_result, true, false); 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), _ => block.condition = Some(condition),
} }
} }
}); }
// Remove false cases // Remove false cases
while let Some((&key, _)) = x.cases.iter().find(|(_, block)| match block.condition { 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 // Add constants and variables from the scope
scope.iter().for_each(|(name, constant, value)| { for (name, constant, value) in scope.iter() {
if !constant { if !constant {
state.push_var(name, AccessMode::ReadWrite, None); state.push_var(name, AccessMode::ReadWrite, None);
} else { } else {
state.push_var(name, AccessMode::ReadOnly, Some(value)); state.push_var(name, AccessMode::ReadOnly, Some(value));
} }
}); }
statements = optimize_stmt_block(statements, &mut state, true, false, true); statements = optimize_stmt_block(statements, &mut state, true, false, true);
statements statements
@ -1191,9 +1191,8 @@ pub fn optimize_into_ast(
// We only need the script library's signatures for optimization purposes // We only need the script library's signatures for optimization purposes
let mut lib2 = crate::Module::new(); let mut lib2 = crate::Module::new();
functions for fn_def in &functions {
.iter() lib2.set_script_fn(crate::ast::ScriptFnDef {
.map(|fn_def| crate::ast::ScriptFnDef {
name: fn_def.name.clone(), name: fn_def.name.clone(),
access: fn_def.access, access: fn_def.access,
body: crate::ast::StmtBlock::NONE, body: crate::ast::StmtBlock::NONE,
@ -1204,33 +1203,25 @@ pub fn optimize_into_ast(
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: None, comments: None,
})
.for_each(|fn_def| {
lib2.set_script_fn(fn_def);
}); });
}
let lib2 = &[&lib2]; let lib2 = &[&lib2];
functions for fn_def in functions {
.into_iter() let mut fn_def = crate::func::native::shared_take_or_clone(fn_def);
.map(|fn_def| {
let mut fn_def = crate::func::native::shared_take_or_clone(fn_def);
// Optimize the function body // Optimize the function body
let body = mem::take(&mut *fn_def.body); let body = mem::take(&mut *fn_def.body);
*fn_def.body = *fn_def.body = optimize_top_level(body, engine, scope, lib2, optimization_level);
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); module.set_script_fn(fn_def);
}); }
} else {
for fn_def in functions {
module.set_script_fn(fn_def);
}
} }
module module

View File

@ -189,7 +189,7 @@ fn collect_fn_metadata(
.for_each(|(_, _, _, _, f)| { .for_each(|(_, _, _, _, f)| {
list.push(make_metadata(dict, Some(namespace.clone()), f).into()) 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!( let ns = format!(
"{}{}{}", "{}{}{}",
namespace, namespace,
@ -197,11 +197,12 @@ fn collect_fn_metadata(
ns ns
); );
scan_module(list, dict, ns.into(), m.as_ref(), filter) scan_module(list, dict, ns.into(), m.as_ref(), filter)
}); }
} }
ctx.iter_imports_raw() for (ns, m) in ctx.iter_imports_raw() {
.for_each(|(ns, m)| scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter)); scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter)
}
} }
list list

View File

@ -3450,9 +3450,9 @@ impl Engine {
{ {
let mut m = crate::Module::new(); let mut m = crate::Module::new();
_lib.into_iter().for_each(|fn_def| { for fn_def in _lib {
m.set_script_fn(fn_def); m.set_script_fn(fn_def);
}); }
return Ok(AST::new(statements, m)); return Ok(AST::new(statements, m));
} }

View File

@ -206,9 +206,9 @@ impl Engine {
let _ast = ast; let _ast = ast;
let mut global = ModuleMetadata::new(); 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()); global.modules.insert(name, m.as_ref().into());
}); }
self.global_modules self.global_modules
.iter() .iter()
@ -221,11 +221,11 @@ impl Engine {
}); });
#[cfg(not(feature = "no_function"))] #[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(); let mut meta: FnMetadata = f.into();
meta.namespace = FnNamespace::Global; meta.namespace = FnNamespace::Global;
global.functions.push(meta); global.functions.push(meta);
}); }
global.functions.sort(); global.functions.sort();

View File

@ -1119,18 +1119,18 @@ impl Dynamic {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(ref mut a, _, ref mut access) => { Union::Array(ref mut a, _, ref mut access) => {
*access = typ; *access = typ;
a.iter_mut().for_each(|v| { for v in a.iter_mut() {
v.set_access_mode(typ); v.set_access_mode(typ);
}); }
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Blob(_, _, ref mut access) => *access = typ, Union::Blob(_, _, ref mut access) => *access = typ,
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(ref mut m, _, ref mut access) => { Union::Map(ref mut m, _, ref mut access) => {
*access = typ; *access = typ;
m.values_mut().for_each(|v| { for v in m.values_mut() {
v.set_access_mode(typ); v.set_access_mode(typ);
}); }
} }
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(_, _, ref mut access) => *access = typ, Union::TimeStamp(_, _, ref mut access) => *access = typ,
@ -1708,14 +1708,14 @@ impl Dynamic {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell, _, _) => { 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::<T>() if (*guard).type_id() != TypeId::of::<T>()
&& TypeId::of::<Dynamic>() != TypeId::of::<T>() && TypeId::of::<Dynamic>() != TypeId::of::<T>()
{ {
return None; return None;
} else { } else {
return Some(DynamicWriteLock(DynamicWriteLockInner::Guard(value))); return Some(DynamicWriteLock(DynamicWriteLockInner::Guard(guard)));
} }
} }
_ => (), _ => (),

View File

@ -611,9 +611,9 @@ impl Scope<'_> {
impl<K: Into<Identifier>> Extend<(K, Dynamic)> for Scope<'_> { impl<K: Into<Identifier>> Extend<(K, Dynamic)> for Scope<'_> {
#[inline] #[inline]
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(name, value)| { for (name, value) in iter {
self.push_dynamic_value(name, AccessMode::ReadWrite, value); self.push_dynamic_value(name, AccessMode::ReadWrite, value);
}); }
} }
} }
@ -629,7 +629,7 @@ impl<K: Into<Identifier>> FromIterator<(K, Dynamic)> for Scope<'_> {
impl<K: Into<Identifier>> Extend<(K, bool, Dynamic)> for Scope<'_> { impl<K: Into<Identifier>> Extend<(K, bool, Dynamic)> for Scope<'_> {
#[inline] #[inline]
fn extend<T: IntoIterator<Item = (K, bool, Dynamic)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (K, bool, Dynamic)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(name, is_constant, value)| { for (name, is_constant, value) in iter {
self.push_dynamic_value( self.push_dynamic_value(
name, name,
if is_constant { if is_constant {
@ -639,7 +639,7 @@ impl<K: Into<Identifier>> Extend<(K, bool, Dynamic)> for Scope<'_> {
}, },
value, value,
); );
}); }
} }
} }

View File

@ -1,9 +1,12 @@
#![cfg(feature = "debugging")] #![cfg(feature = "debugging")]
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Dynamic, Engine, EvalAltResult, INT};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use rhai::Array; use rhai::Array;
#[cfg(not(feature = "no_object"))]
use rhai::Map;
#[test] #[test]
fn test_debugging() -> Result<(), Box<EvalAltResult>> { fn test_debugging() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
@ -32,3 +35,44 @@ fn test_debugging() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
#[cfg(not(feature = "no_object"))]
fn test_debugger_state() -> Result<(), Box<EvalAltResult>> {
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::<Map>().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(())
}