Support call stack and FunctionExit for native functions.
This commit is contained in:
parent
7163a7331a
commit
4a80483749
@ -24,7 +24,7 @@ New features
|
|||||||
|
|
||||||
* A debugging interface is added.
|
* A debugging interface is added.
|
||||||
* A new bin tool, `rhai-dbg` (aka _The Rhai Debugger_), is added to showcase the debugging interface.
|
* A new bin tool, `rhai-dbg` (aka _The Rhai Debugger_), is added to showcase the debugging interface.
|
||||||
* A new package, `DebuggingPackage`, is added which contains the `stack_trace` function to get the current call stack anywhere in a script.
|
* A new package, `DebuggingPackage`, is added which contains the `back_trace` function to get the current call stack anywhere in a script.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
@ -32,6 +32,7 @@ Enhancements
|
|||||||
* Default features for dependencies (such as `ahash/std` and `num-traits/std`) are no longer required.
|
* Default features for dependencies (such as `ahash/std` and `num-traits/std`) are no longer required.
|
||||||
* The `no_module` feature now eliminates large sections of code via feature gates.
|
* The `no_module` feature now eliminates large sections of code via feature gates.
|
||||||
* Debug display of `AST` is improved.
|
* Debug display of `AST` is improved.
|
||||||
|
* `NativeCallContext::call_level()` is added to give the current nesting level of function calls.
|
||||||
|
|
||||||
REPL tool changes
|
REPL tool changes
|
||||||
-----------------
|
-----------------
|
||||||
|
@ -270,10 +270,30 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DebuggerEvent::FunctionExitWithValue(r) => {
|
DebuggerEvent::FunctionExitWithValue(r) => {
|
||||||
println!("! Return from function call = {}", r)
|
println!(
|
||||||
|
"! Return from function call '{}' => {}",
|
||||||
|
context
|
||||||
|
.global_runtime_state()
|
||||||
|
.debugger
|
||||||
|
.call_stack()
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.fn_name,
|
||||||
|
r
|
||||||
|
)
|
||||||
}
|
}
|
||||||
DebuggerEvent::FunctionExitWithError(err) => {
|
DebuggerEvent::FunctionExitWithError(err) => {
|
||||||
println!("! Return from function call with error: {}", err)
|
println!(
|
||||||
|
"! Return from function call '{}' with error: {}",
|
||||||
|
context
|
||||||
|
.global_runtime_state()
|
||||||
|
.debugger
|
||||||
|
.call_stack()
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.fn_name,
|
||||||
|
err
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +220,7 @@ impl Engine {
|
|||||||
Ok(ref mut obj_ptr) => {
|
Ok(ref mut obj_ptr) => {
|
||||||
self.eval_op_assignment(
|
self.eval_op_assignment(
|
||||||
global, state, lib, op_info, op_pos, obj_ptr, root, new_val,
|
global, state, lib, op_info, op_pos, obj_ptr, root, new_val,
|
||||||
|
level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(new_pos))?;
|
.map_err(|err| err.fill_position(new_pos))?;
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -314,6 +315,7 @@ impl Engine {
|
|||||||
)?;
|
)?;
|
||||||
self.eval_op_assignment(
|
self.eval_op_assignment(
|
||||||
global, state, lib, op_info, op_pos, val_target, root, new_val,
|
global, state, lib, op_info, op_pos, val_target, root, new_val,
|
||||||
|
level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(new_pos))?;
|
.map_err(|err| err.fill_position(new_pos))?;
|
||||||
}
|
}
|
||||||
@ -380,6 +382,7 @@ impl Engine {
|
|||||||
&mut (&mut orig_val).into(),
|
&mut (&mut orig_val).into(),
|
||||||
root,
|
root,
|
||||||
new_val,
|
new_val,
|
||||||
|
level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(new_pos))?;
|
.map_err(|err| err.fill_position(new_pos))?;
|
||||||
|
|
||||||
|
@ -346,13 +346,15 @@ impl Debugger {
|
|||||||
node.position() == *pos && _src == source
|
node.position() == *pos && _src == source
|
||||||
}
|
}
|
||||||
BreakPoint::AtFunctionName { name, .. } => match node {
|
BreakPoint::AtFunctionName { name, .. } => match node {
|
||||||
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => {
|
ASTNode::Expr(Expr::FnCall(x, _))
|
||||||
x.name == *name
|
| ASTNode::Stmt(Stmt::FnCall(x, _))
|
||||||
}
|
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(x, _))) => x.name == *name,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
BreakPoint::AtFunctionCall { name, args, .. } => match node {
|
BreakPoint::AtFunctionCall { name, args, .. } => match node {
|
||||||
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => {
|
ASTNode::Expr(Expr::FnCall(x, _))
|
||||||
|
| ASTNode::Stmt(Stmt::FnCall(x, _))
|
||||||
|
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(x, _))) => {
|
||||||
x.args.len() == *args && x.name == *name
|
x.args.len() == *args && x.name == *name
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
@ -503,7 +505,14 @@ impl Engine {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
DebuggerCommand::FunctionExit => {
|
DebuggerCommand::FunctionExit => {
|
||||||
global.debugger.status = DebuggerStatus::FunctionExit(context.call_level());
|
// Bump a level if it is a function call
|
||||||
|
let level = match node {
|
||||||
|
ASTNode::Expr(Expr::FnCall(_, _))
|
||||||
|
| ASTNode::Stmt(Stmt::FnCall(_, _))
|
||||||
|
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(_, _))) => context.call_level() + 1,
|
||||||
|
_ => context.call_level(),
|
||||||
|
};
|
||||||
|
global.debugger.status = DebuggerStatus::FunctionExit(level);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -353,6 +353,7 @@ impl Engine {
|
|||||||
&mut (&mut concat).into(),
|
&mut (&mut concat).into(),
|
||||||
("", Position::NONE),
|
("", Position::NONE),
|
||||||
item,
|
item,
|
||||||
|
level,
|
||||||
) {
|
) {
|
||||||
result = Err(err.fill_position(expr.position()));
|
result = Err(err.fill_position(expr.position()));
|
||||||
break;
|
break;
|
||||||
|
@ -120,6 +120,7 @@ impl Engine {
|
|||||||
target: &mut Target,
|
target: &mut Target,
|
||||||
root: (&str, Position),
|
root: (&str, Position),
|
||||||
new_val: Dynamic,
|
new_val: Dynamic,
|
||||||
|
level: usize,
|
||||||
) -> RhaiResultOf<()> {
|
) -> RhaiResultOf<()> {
|
||||||
if target.is_read_only() {
|
if target.is_read_only() {
|
||||||
// Assignment to constant variable
|
// Assignment to constant variable
|
||||||
@ -154,7 +155,7 @@ impl Engine {
|
|||||||
let args = &mut [lhs_ptr_inner, &mut new_val];
|
let args = &mut [lhs_ptr_inner, &mut new_val];
|
||||||
|
|
||||||
match self.call_native_fn(
|
match self.call_native_fn(
|
||||||
global, state, lib, op_assign, hash, args, true, true, op_pos,
|
global, state, lib, op_assign, hash, args, true, true, op_pos, level,
|
||||||
) {
|
) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -164,7 +165,7 @@ impl Engine {
|
|||||||
{
|
{
|
||||||
// Expand to `var = var op rhs`
|
// Expand to `var = var op rhs`
|
||||||
let (value, _) = self.call_native_fn(
|
let (value, _) = self.call_native_fn(
|
||||||
global, state, lib, op, hash_op, args, true, false, op_pos,
|
global, state, lib, op, hash_op, args, true, false, op_pos, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -266,6 +267,7 @@ impl Engine {
|
|||||||
&mut lhs_ptr,
|
&mut lhs_ptr,
|
||||||
(var_name, pos),
|
(var_name, pos),
|
||||||
rhs_val,
|
rhs_val,
|
||||||
|
level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(rhs.position()))
|
.map_err(|err| err.fill_position(rhs.position()))
|
||||||
.map(|_| Dynamic::UNIT)
|
.map(|_| Dynamic::UNIT)
|
||||||
|
@ -328,6 +328,8 @@ impl Engine {
|
|||||||
result.as_ref().map(Box::as_ref)
|
result.as_ref().map(Box::as_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Main Entry-Point
|
||||||
|
///
|
||||||
/// Call a native Rust function registered with the [`Engine`].
|
/// Call a native Rust function registered with the [`Engine`].
|
||||||
///
|
///
|
||||||
/// # WARNING
|
/// # WARNING
|
||||||
@ -347,6 +349,7 @@ impl Engine {
|
|||||||
is_ref_mut: bool,
|
is_ref_mut: bool,
|
||||||
is_op_assign: bool,
|
is_op_assign: bool,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
|
level: usize,
|
||||||
) -> RhaiResultOf<(Dynamic, bool)> {
|
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(&mut global.num_operations, pos)?;
|
self.inc_operations(&mut global.num_operations, pos)?;
|
||||||
@ -365,7 +368,14 @@ impl Engine {
|
|||||||
is_op_assign,
|
is_op_assign,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(FnResolutionCacheEntry { func, source }) = func {
|
if func.is_some() {
|
||||||
|
let is_method = func.map(|f| f.func.is_method()).unwrap_or(false);
|
||||||
|
|
||||||
|
// Push a new call stack frame
|
||||||
|
#[cfg(feature = "debugging")]
|
||||||
|
let orig_call_stack_len = global.debugger.call_stack().len();
|
||||||
|
|
||||||
|
let mut result = if let Some(FnResolutionCacheEntry { func, source }) = func {
|
||||||
assert!(func.is_native());
|
assert!(func.is_native());
|
||||||
|
|
||||||
// Calling pure function but the first argument is a reference?
|
// Calling pure function but the first argument is a reference?
|
||||||
@ -379,13 +389,23 @@ impl Engine {
|
|||||||
.change_first_arg_to_copy(args);
|
.change_first_arg_to_copy(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run external function
|
|
||||||
let source = match (source.as_str(), parent_source.as_str()) {
|
let source = match (source.as_str(), parent_source.as_str()) {
|
||||||
("", "") => None,
|
("", "") => None,
|
||||||
("", s) | (s, _) => Some(s),
|
("", s) | (s, _) => Some(s),
|
||||||
};
|
};
|
||||||
|
|
||||||
let context = (self, name, source, &*global, lib, pos).into();
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
|
global.debugger.push_call_stack_frame(
|
||||||
|
name,
|
||||||
|
args.iter().map(|v| (*v).clone()).collect(),
|
||||||
|
source.unwrap_or(""),
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run external function
|
||||||
|
let context = (self, name, source, &*global, lib, pos, level).into();
|
||||||
|
|
||||||
let result = if func.is_plugin_fn() {
|
let result = if func.is_plugin_fn() {
|
||||||
func.get_plugin_fn()
|
func.get_plugin_fn()
|
||||||
@ -400,6 +420,35 @@ impl Engine {
|
|||||||
bk.restore_first_arg(args)
|
bk.restore_first_arg(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
unreachable!("`Some`");
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
|
match global.debugger.status() {
|
||||||
|
crate::eval::DebuggerStatus::FunctionExit(n) if n >= level => {
|
||||||
|
let scope = &mut &mut Scope::new();
|
||||||
|
let node = crate::ast::Stmt::Noop(pos);
|
||||||
|
let node = (&node).into();
|
||||||
|
let event = match result {
|
||||||
|
Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r),
|
||||||
|
Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err),
|
||||||
|
};
|
||||||
|
if let Err(err) = self.run_debugger_raw(
|
||||||
|
scope, global, state, lib, &mut None, node, event, level,
|
||||||
|
) {
|
||||||
|
result = Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop the call stack
|
||||||
|
global.debugger.rewind_call_stack(orig_call_stack_len);
|
||||||
|
}
|
||||||
|
|
||||||
// Check the return value (including data sizes)
|
// Check the return value (including data sizes)
|
||||||
let result = self.check_return_value(result, pos)?;
|
let result = self.check_return_value(result, pos)?;
|
||||||
|
|
||||||
@ -443,7 +492,7 @@ impl Engine {
|
|||||||
(Dynamic::UNIT, false)
|
(Dynamic::UNIT, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (result, func.is_method()),
|
_ => (result, is_method),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -530,6 +579,8 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Main Entry-Point
|
||||||
|
///
|
||||||
/// Perform an actual function call, native Rust or scripted, taking care of special functions.
|
/// Perform an actual function call, native Rust or scripted, taking care of special functions.
|
||||||
///
|
///
|
||||||
/// # WARNING
|
/// # WARNING
|
||||||
@ -562,7 +613,6 @@ impl Engine {
|
|||||||
ensure_no_data_race(fn_name, args, is_ref_mut)?;
|
ensure_no_data_race(fn_name, args, is_ref_mut)?;
|
||||||
|
|
||||||
let _scope = scope;
|
let _scope = scope;
|
||||||
let _level = level;
|
|
||||||
let _is_method_call = is_method_call;
|
let _is_method_call = is_method_call;
|
||||||
|
|
||||||
// These may be redirected from method style calls.
|
// These may be redirected from method style calls.
|
||||||
@ -612,6 +662,8 @@ impl Engine {
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let level = level + 1;
|
||||||
|
|
||||||
// Script-defined function call?
|
// Script-defined function call?
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
if let Some(FnResolutionCacheEntry { func, mut source }) = self
|
if let Some(FnResolutionCacheEntry { func, mut source }) = self
|
||||||
@ -646,7 +698,6 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mem::swap(&mut global.source, &mut source);
|
mem::swap(&mut global.source, &mut source);
|
||||||
let level = _level + 1;
|
|
||||||
|
|
||||||
let result = if _is_method_call {
|
let result = if _is_method_call {
|
||||||
// Method call of script function - map first argument to `this`
|
// Method call of script function - map first argument to `this`
|
||||||
@ -699,7 +750,7 @@ impl Engine {
|
|||||||
// Native function call
|
// Native function call
|
||||||
let hash = hashes.native;
|
let hash = hashes.native;
|
||||||
self.call_native_fn(
|
self.call_native_fn(
|
||||||
global, state, lib, fn_name, hash, args, is_ref_mut, false, pos,
|
global, state, lib, fn_name, hash, args, is_ref_mut, false, pos, level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1318,6 +1369,8 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let level = level + 1;
|
||||||
|
|
||||||
match func {
|
match func {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
Some(f) if f.is_script() => {
|
Some(f) if f.is_script() => {
|
||||||
@ -1331,8 +1384,6 @@ impl Engine {
|
|||||||
let mut source = module.id_raw().clone();
|
let mut source = module.id_raw().clone();
|
||||||
mem::swap(&mut global.source, &mut source);
|
mem::swap(&mut global.source, &mut source);
|
||||||
|
|
||||||
let level = level + 1;
|
|
||||||
|
|
||||||
let result = self.call_script_fn(
|
let result = self.call_script_fn(
|
||||||
new_scope, global, state, lib, &mut None, fn_def, &mut args, pos, true,
|
new_scope, global, state, lib, &mut None, fn_def, &mut args, pos, true,
|
||||||
level,
|
level,
|
||||||
@ -1345,7 +1396,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Some(f) if f.is_plugin_fn() => {
|
Some(f) if f.is_plugin_fn() => {
|
||||||
let context = (self, fn_name, module.id(), &*global, lib, pos).into();
|
let context = (self, fn_name, module.id(), &*global, lib, pos, level).into();
|
||||||
let result = f
|
let result = f
|
||||||
.get_plugin_fn()
|
.get_plugin_fn()
|
||||||
.expect("plugin function")
|
.expect("plugin function")
|
||||||
@ -1356,7 +1407,7 @@ impl Engine {
|
|||||||
|
|
||||||
Some(f) if f.is_native() => {
|
Some(f) if f.is_native() => {
|
||||||
let func = f.get_native_fn().expect("native function");
|
let func = f.get_native_fn().expect("native function");
|
||||||
let context = (self, fn_name, module.id(), &*global, lib, pos).into();
|
let context = (self, fn_name, module.id(), &*global, lib, pos, level).into();
|
||||||
let result = func(context, &mut args);
|
let result = func(context, &mut args);
|
||||||
self.check_return_value(result, pos)
|
self.check_return_value(result, pos)
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,8 @@ pub struct NativeCallContext<'a> {
|
|||||||
lib: &'a [&'a Module],
|
lib: &'a [&'a Module],
|
||||||
/// [Position] of the function call.
|
/// [Position] of the function call.
|
||||||
pos: Position,
|
pos: Position,
|
||||||
|
/// The current nesting level of function calls.
|
||||||
|
level: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
||||||
@ -80,6 +82,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
|||||||
&'a GlobalRuntimeState<'a>,
|
&'a GlobalRuntimeState<'a>,
|
||||||
&'a M,
|
&'a M,
|
||||||
Position,
|
Position,
|
||||||
|
usize,
|
||||||
)> for NativeCallContext<'a>
|
)> for NativeCallContext<'a>
|
||||||
{
|
{
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -91,6 +94,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
|||||||
&'a GlobalRuntimeState,
|
&'a GlobalRuntimeState,
|
||||||
&'a M,
|
&'a M,
|
||||||
Position,
|
Position,
|
||||||
|
usize,
|
||||||
),
|
),
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -100,6 +104,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
|||||||
global: Some(value.3),
|
global: Some(value.3),
|
||||||
lib: value.4.as_ref(),
|
lib: value.4.as_ref(),
|
||||||
pos: value.5,
|
pos: value.5,
|
||||||
|
level: value.6,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,6 +121,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
|||||||
global: None,
|
global: None,
|
||||||
lib: value.2.as_ref(),
|
lib: value.2.as_ref(),
|
||||||
pos: Position::NONE,
|
pos: Position::NONE,
|
||||||
|
level: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,6 +147,7 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
global: None,
|
global: None,
|
||||||
lib,
|
lib,
|
||||||
pos: Position::NONE,
|
pos: Position::NONE,
|
||||||
|
level: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// _(internals)_ Create a new [`NativeCallContext`].
|
/// _(internals)_ Create a new [`NativeCallContext`].
|
||||||
@ -158,6 +165,7 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
global: &'a GlobalRuntimeState,
|
global: &'a GlobalRuntimeState,
|
||||||
lib: &'a [&Module],
|
lib: &'a [&Module],
|
||||||
pos: Position,
|
pos: Position,
|
||||||
|
level: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine,
|
engine,
|
||||||
@ -166,6 +174,7 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
global: Some(global),
|
global: Some(global),
|
||||||
lib,
|
lib,
|
||||||
pos,
|
pos,
|
||||||
|
level,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// The current [`Engine`].
|
/// The current [`Engine`].
|
||||||
@ -186,6 +195,12 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
pub const fn position(&self) -> Position {
|
pub const fn position(&self) -> Position {
|
||||||
self.pos
|
self.pos
|
||||||
}
|
}
|
||||||
|
/// Current nesting level of function calls.
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn call_level(&self) -> usize {
|
||||||
|
self.level
|
||||||
|
}
|
||||||
/// The current source.
|
/// The current source.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -316,7 +331,7 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
is_method_call,
|
is_method_call,
|
||||||
Position::NONE,
|
Position::NONE,
|
||||||
None,
|
None,
|
||||||
0,
|
self.level + 1,
|
||||||
)
|
)
|
||||||
.map(|(r, _)| r)
|
.map(|(r, _)| r)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ use std::mem;
|
|||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
|
/// # Main Entry-Point
|
||||||
|
///
|
||||||
/// Call a script-defined function.
|
/// Call a script-defined function.
|
||||||
///
|
///
|
||||||
/// If `rewind_scope` is `false`, arguments are removed from the scope but new variables are not.
|
/// If `rewind_scope` is `false`, arguments are removed from the scope but new variables are not.
|
||||||
@ -90,6 +92,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Push a new call stack frame
|
// Push a new call stack frame
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
global.debugger.push_call_stack_frame(
|
global.debugger.push_call_stack_frame(
|
||||||
fn_def.name.clone(),
|
fn_def.name.clone(),
|
||||||
scope
|
scope
|
||||||
@ -100,6 +103,7 @@ impl Engine {
|
|||||||
global.source.clone(),
|
global.source.clone(),
|
||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Merge in encapsulated environment, if any
|
// Merge in encapsulated environment, if any
|
||||||
let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
|
let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
|
||||||
@ -167,7 +171,6 @@ impl Engine {
|
|||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
{
|
|
||||||
if self.debugger.is_some() {
|
if self.debugger.is_some() {
|
||||||
match global.debugger.status() {
|
match global.debugger.status() {
|
||||||
crate::eval::DebuggerStatus::FunctionExit(n) if n >= level => {
|
crate::eval::DebuggerStatus::FunctionExit(n) if n >= level => {
|
||||||
@ -177,15 +180,14 @@ impl Engine {
|
|||||||
Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r),
|
Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r),
|
||||||
Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err),
|
Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err),
|
||||||
};
|
};
|
||||||
if let Err(err) = self.run_debugger_raw(
|
if let Err(err) = self
|
||||||
scope, global, state, lib, this_ptr, node, event, level,
|
.run_debugger_raw(scope, global, state, lib, this_ptr, node, event, level)
|
||||||
) {
|
{
|
||||||
result = Err(err);
|
result = Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Pop the call stack
|
// Pop the call stack
|
||||||
global.debugger.rewind_call_stack(orig_call_stack_len);
|
global.debugger.rewind_call_stack(orig_call_stack_len);
|
||||||
|
@ -147,6 +147,7 @@ impl<'a> OptimizerState<'a> {
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
Position::NONE,
|
Position::NONE,
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
|
@ -27,15 +27,23 @@ def_package! {
|
|||||||
|
|
||||||
#[export_module]
|
#[export_module]
|
||||||
mod debugging_functions {
|
mod debugging_functions {
|
||||||
|
/// Get an array of object maps containing the function calls stack.
|
||||||
|
///
|
||||||
|
/// If there is no debugging interface registered, an empty array is returned.
|
||||||
|
///
|
||||||
|
/// An array of strings is returned under `no_object`.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub fn stack_trace(ctx: NativeCallContext) -> Array {
|
pub fn back_trace(ctx: NativeCallContext) -> Array {
|
||||||
if let Some(global) = ctx.global_runtime_state() {
|
if let Some(global) = ctx.global_runtime_state() {
|
||||||
global
|
global
|
||||||
.debugger
|
.debugger
|
||||||
.call_stack()
|
.call_stack()
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
|
.filter(|crate::debugger::CallStackFrame { fn_name, args, .. }| {
|
||||||
|
fn_name != "back_trace" || !args.is_empty()
|
||||||
|
})
|
||||||
.map(
|
.map(
|
||||||
|frame @ crate::debugger::CallStackFrame {
|
|frame @ crate::debugger::CallStackFrame {
|
||||||
fn_name: _fn_name,
|
fn_name: _fn_name,
|
||||||
|
@ -37,9 +37,9 @@ fn check_struct_sizes() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
size_of::<NativeCallContext>(),
|
size_of::<NativeCallContext>(),
|
||||||
if cfg!(feature = "no_position") {
|
if cfg!(feature = "no_position") {
|
||||||
64
|
|
||||||
} else {
|
|
||||||
72
|
72
|
||||||
|
} else {
|
||||||
|
80
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ fn test_debugging() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"
|
"
|
||||||
fn foo(x) {
|
fn foo(x) {
|
||||||
if x >= 5 {
|
if x >= 5 {
|
||||||
stack_trace()
|
back_trace()
|
||||||
} else {
|
} else {
|
||||||
foo(x+1)
|
foo(x+1)
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ fn test_debugging() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(r.len(), 6);
|
assert_eq!(r.len(), 6);
|
||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("len(stack_trace())")?, 0);
|
assert_eq!(engine.eval::<INT>("len(back_trace())")?, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Loading…
Reference in New Issue
Block a user