Refine error display.
This commit is contained in:
parent
549193f49b
commit
fcc7589ffc
@ -33,7 +33,7 @@ Standard features
|
||||
-----------------
|
||||
|
||||
* Simple language similar to JavaScript+Rust with [dynamic](https://rhai.rs/book/language/dynamic.html) typing.
|
||||
* Fairly efficient evaluation (1 million iterations in 0.15 sec on a single-core 2.6 GHz Linux VM).
|
||||
* Fairly efficient evaluation (1 million iterations in 0.14 sec on a single-core 2.6 GHz Linux VM).
|
||||
* Tight integration with native Rust [functions](https://rhai.rs/book/rust/functions.html) and [types](https://rhai.rs/book/rust/custom-types.html), including [getters/setters](https://rhai.rs/book/rust/getters-setters.html), [methods](https://rhai.rs/book/rust/methods.html) and [indexers](https://rhai.rs/book/rust/indexers.html).
|
||||
* Freely pass Rust values into a script as [variables](https://rhai.rs/book/language/variables.html)/[constants](https://rhai.rs/book/language/constants.html) via an external [`Scope`](https://rhai.rs/book/engine/scope.html) - all clonable Rust types are supported; no need to implement any special trait. Or tap directly into the [variable resolution process](https://rhai.rs/book/engine/var.html).
|
||||
* Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) (including packed [byte arrays](https://rhai.rs/book/language/blobs.html)) and [object maps](https://rhai.rs/book/language/object-maps.html).
|
||||
|
@ -127,9 +127,13 @@ impl Engine {
|
||||
}
|
||||
|
||||
let statements = ast.statements();
|
||||
if !statements.is_empty() {
|
||||
self.eval_global_statements(global, caches, scope, statements)?;
|
||||
}
|
||||
|
||||
let result = if !statements.is_empty() {
|
||||
self.eval_global_statements(global, caches, scope, statements)
|
||||
.map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
if self.is_debugger_registered() {
|
||||
@ -139,7 +143,7 @@ impl Engine {
|
||||
self.run_debugger(global, caches, scope, &mut this, node)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,17 +100,19 @@ fn print_error(input: &str, mut err: EvalAltResult) {
|
||||
// Print error position
|
||||
if pos.is_none() {
|
||||
// No position
|
||||
println!("{err}");
|
||||
println!("\x1b[31m{err}\x1b[39m");
|
||||
} else {
|
||||
// Specific position - print line text
|
||||
println!("{line_no}{}", lines[pos.line().unwrap() - 1]);
|
||||
|
||||
// Display position marker
|
||||
println!(
|
||||
"{0:>1$} {err}",
|
||||
"^",
|
||||
line_no.len() + pos.position().unwrap(),
|
||||
);
|
||||
for (i, err_line) in err.to_string().split('\n').enumerate() {
|
||||
// Display position marker
|
||||
println!(
|
||||
"\x1b[31m{0:>1$}{err_line}\x1b[39m",
|
||||
if i > 0 { " " } else { "^ " },
|
||||
line_no.len() + pos.position().unwrap() + 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,7 +239,13 @@ fn debug_callback(
|
||||
) -> Result<DebuggerCommand, Box<EvalAltResult>> {
|
||||
// Check event
|
||||
match event {
|
||||
DebuggerEvent::Start if source.is_some() => {
|
||||
println!("\x1b[32m! Script '{}' start\x1b[39m", source.unwrap())
|
||||
}
|
||||
DebuggerEvent::Start => println!("\x1b[32m! Script start\x1b[39m"),
|
||||
DebuggerEvent::End if source.is_some() => {
|
||||
println!("\x1b[31m! Script '{}' end\x1b[39m", source.unwrap())
|
||||
}
|
||||
DebuggerEvent::End => println!("\x1b[31m! Script end\x1b[39m"),
|
||||
DebuggerEvent::Step => (),
|
||||
DebuggerEvent::BreakPoint(n) => {
|
||||
@ -572,7 +580,7 @@ fn debug_callback(
|
||||
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
|
||||
}
|
||||
["run" | "r"] => {
|
||||
println!("Restarting script...");
|
||||
println!("Terminating current run...");
|
||||
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
|
||||
}
|
||||
_ => eprintln!(
|
||||
@ -630,10 +638,13 @@ fn main() {
|
||||
while let Err(err) = engine.run_ast_with_scope(&mut Scope::new(), &ast) {
|
||||
match *err {
|
||||
// Loop back to restart
|
||||
EvalAltResult::ErrorTerminated(..) => (),
|
||||
EvalAltResult::ErrorTerminated(..) => {
|
||||
println!("Restarting script...");
|
||||
}
|
||||
// Break evaluation
|
||||
_ => {
|
||||
print_error(&script, *err);
|
||||
println!();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -31,12 +31,14 @@ fn print_error(input: &str, mut err: EvalAltResult) {
|
||||
// Specific position - print line text
|
||||
println!("{line_no}{}", lines[pos.line().unwrap() - 1]);
|
||||
|
||||
// Display position marker
|
||||
println!(
|
||||
"{0:>1$} {err}",
|
||||
"^",
|
||||
line_no.len() + pos.position().unwrap(),
|
||||
);
|
||||
for (i, err_line) in err.to_string().split('\n').enumerate() {
|
||||
// Display position marker
|
||||
println!(
|
||||
"{0:>1$}{err_line}",
|
||||
if i > 0 { " " } else { "^ " },
|
||||
line_no.len() + pos.position().unwrap() + 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,11 +8,15 @@ fn eprint_error(input: &str, mut err: EvalAltResult) {
|
||||
let line_no = format!("{line}: ");
|
||||
|
||||
eprintln!("{line_no}{}", lines[line - 1]);
|
||||
eprintln!(
|
||||
"{:>1$} {err_msg}",
|
||||
"^",
|
||||
line_no.len() + pos.position().unwrap(),
|
||||
);
|
||||
|
||||
for (i, err_line) in err_msg.to_string().split('\n').enumerate() {
|
||||
// Display position marker
|
||||
println!(
|
||||
"{0:>1$}{err_line}",
|
||||
if i > 0 { " " } else { "^ " },
|
||||
line_no.len() + pos.position().unwrap() + 1,
|
||||
);
|
||||
}
|
||||
eprintln!();
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
use super::call::FnCallArgs;
|
||||
use crate::ast::ScriptFnDef;
|
||||
use crate::eval::{Caches, GlobalRuntimeState};
|
||||
use crate::{Dynamic, Engine, Position, RhaiError, RhaiResult, Scope, ERR};
|
||||
use crate::{Dynamic, Engine, Position, RhaiResult, Scope, ERR};
|
||||
use std::mem;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -33,32 +33,6 @@ impl Engine {
|
||||
rewind_scope: bool,
|
||||
pos: Position,
|
||||
) -> RhaiResult {
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn make_error(
|
||||
name: String,
|
||||
_fn_def: &ScriptFnDef,
|
||||
global: &GlobalRuntimeState,
|
||||
err: RhaiError,
|
||||
pos: Position,
|
||||
) -> RhaiResult {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let source = _fn_def
|
||||
.environ
|
||||
.as_ref()
|
||||
.and_then(|environ| environ.lib.id().map(str::to_string));
|
||||
#[cfg(feature = "no_module")]
|
||||
let source = None;
|
||||
|
||||
Err(ERR::ErrorInFunctionCall(
|
||||
name,
|
||||
source.unwrap_or_else(|| global.source().unwrap_or("").to_string()),
|
||||
err,
|
||||
pos,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
assert!(fn_def.params.len() == args.len());
|
||||
|
||||
self.track_operation(global, pos)?;
|
||||
@ -97,14 +71,13 @@ impl Engine {
|
||||
// Push a new call stack frame
|
||||
#[cfg(feature = "debugging")]
|
||||
if self.is_debugger_registered() {
|
||||
let fn_name = fn_def.name.clone();
|
||||
let args = scope.iter().skip(orig_scope_len).map(|(.., v)| v).collect();
|
||||
let source = global.source.clone();
|
||||
|
||||
global.debugger_mut().push_call_stack_frame(
|
||||
fn_def.name.clone(),
|
||||
scope.iter().skip(orig_scope_len).map(|(.., v)| v).collect(),
|
||||
source,
|
||||
pos,
|
||||
);
|
||||
global
|
||||
.debugger_mut()
|
||||
.push_call_stack_frame(fn_name, args, source, pos);
|
||||
}
|
||||
|
||||
// Merge in encapsulated environment, if any
|
||||
@ -137,27 +110,32 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Evaluate the function
|
||||
let mut _result = self
|
||||
let mut _result: RhaiResult = self
|
||||
.eval_stmt_block(global, caches, scope, this_ptr, &fn_def.body, rewind_scope)
|
||||
.or_else(|err| match *err {
|
||||
// Convert return statement to return value
|
||||
ERR::Return(x, ..) => Ok(x),
|
||||
// Error in sub function call
|
||||
ERR::ErrorInFunctionCall(name, src, err, ..) => {
|
||||
let fn_name = if src.is_empty() {
|
||||
format!("{name} < {}", fn_def.name)
|
||||
} else {
|
||||
format!("{name} @ '{src}' < {}", fn_def.name)
|
||||
};
|
||||
make_error(fn_name, fn_def, global, err, pos)
|
||||
}
|
||||
// System errors are passed straight-through
|
||||
mut err if err.is_system_exception() => {
|
||||
err.set_position(pos);
|
||||
Err(err.into())
|
||||
}
|
||||
// Other errors are wrapped in `ErrorInFunctionCall`
|
||||
_ => make_error(fn_def.name.to_string(), fn_def, global, err, pos),
|
||||
_ => Err(ERR::ErrorInFunctionCall(
|
||||
fn_def.name.to_string(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
fn_def
|
||||
.environ
|
||||
.as_deref()
|
||||
.and_then(|environ| environ.lib.id())
|
||||
.unwrap_or_else(|| global.source().unwrap_or(""))
|
||||
.to_string(),
|
||||
#[cfg(feature = "no_module")]
|
||||
global.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
pos,
|
||||
)
|
||||
.into()),
|
||||
});
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
|
@ -135,22 +135,20 @@ impl fmt::Display for EvalAltResult {
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Self::ErrorInFunctionCall(s, src, err, ..) if crate::parser::is_anonymous_fn(s) => {
|
||||
write!(f, "{err} in call to closure")?;
|
||||
write!(f, "{err}\n| in closure call")?;
|
||||
if !src.is_empty() {
|
||||
write!(f, " @ '{src}'")?;
|
||||
}
|
||||
}
|
||||
Self::ErrorInFunctionCall(s, src, err, ..) => {
|
||||
write!(f, "{err} in call to function {s}")?;
|
||||
write!(f, "{err}\n| in call to function '{s}'")?;
|
||||
if !src.is_empty() {
|
||||
write!(f, " @ '{src}'")?;
|
||||
}
|
||||
}
|
||||
|
||||
Self::ErrorInModule(s, err, ..) if s.is_empty() => {
|
||||
write!(f, "Error in module > {err}")?
|
||||
}
|
||||
Self::ErrorInModule(s, err, ..) => write!(f, "Error in module '{s}' > {err}")?,
|
||||
Self::ErrorInModule(s, err, ..) if s.is_empty() => write!(f, "{err}\n| in module")?,
|
||||
Self::ErrorInModule(s, err, ..) => write!(f, "{err}\n| in module '{s}'")?,
|
||||
|
||||
Self::ErrorVariableExists(s, ..) => write!(f, "Variable already defined: {s}")?,
|
||||
Self::ErrorForbiddenVariable(s, ..) => write!(f, "Forbidden variable name: {s}")?,
|
||||
@ -159,16 +157,14 @@ impl fmt::Display for EvalAltResult {
|
||||
Self::ErrorIndexNotFound(s, ..) => write!(f, "Invalid index: {s}")?,
|
||||
Self::ErrorFunctionNotFound(s, ..) => write!(f, "Function not found: {s}")?,
|
||||
Self::ErrorModuleNotFound(s, ..) => write!(f, "Module not found: {s}")?,
|
||||
Self::ErrorDataRace(s, ..) => {
|
||||
write!(f, "Data race detected when accessing variable: {s}")?
|
||||
}
|
||||
Self::ErrorDataRace(s, ..) => write!(f, "Data race detected on variable '{s}'")?,
|
||||
|
||||
Self::ErrorDotExpr(s, ..) if s.is_empty() => f.write_str("Malformed dot expression")?,
|
||||
Self::ErrorDotExpr(s, ..) => f.write_str(s)?,
|
||||
|
||||
Self::ErrorIndexingType(s, ..) => write!(f, "Indexer unavailable: {s}")?,
|
||||
Self::ErrorUnboundThis(..) => f.write_str("'this' not bound")?,
|
||||
Self::ErrorFor(..) => f.write_str("For loop expects an iterable type")?,
|
||||
Self::ErrorFor(..) => f.write_str("For loop expects iterable type")?,
|
||||
Self::ErrorTooManyOperations(..) => f.write_str("Too many operations")?,
|
||||
Self::ErrorTooManyModules(..) => f.write_str("Too many modules imported")?,
|
||||
Self::ErrorStackOverflow(..) => f.write_str("Stack overflow")?,
|
||||
@ -188,31 +184,25 @@ impl fmt::Display for EvalAltResult {
|
||||
if s.starts_with(crate::engine::FN_GET) =>
|
||||
{
|
||||
let prop = &s[crate::engine::FN_GET.len()..];
|
||||
write!(
|
||||
f,
|
||||
"Property {prop} is not pure and cannot be accessed on a constant"
|
||||
)?
|
||||
write!(f, "Non-pure property {prop} cannot be accessed on constant")?
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Self::ErrorNonPureMethodCallOnConstant(s, ..)
|
||||
if s.starts_with(crate::engine::FN_SET) =>
|
||||
{
|
||||
let prop = &s[crate::engine::FN_SET.len()..];
|
||||
write!(f, "Cannot modify property '{prop}' of a constant")?
|
||||
write!(f, "Cannot modify property '{prop}' of constant")?
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_GET => {
|
||||
write!(
|
||||
f,
|
||||
"Indexer is not pure and cannot be accessed on a constant"
|
||||
)?
|
||||
write!(f, "Non-pure indexer cannot be accessed on constant")?
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_SET => {
|
||||
write!(f, "Cannot assign to the indexer of a constant")?
|
||||
write!(f, "Cannot assign to indexer of constant")?
|
||||
}
|
||||
Self::ErrorNonPureMethodCallOnConstant(s, ..) => {
|
||||
write!(f, "Non-pure method '{s}' cannot be called on a constant")?
|
||||
write!(f, "Non-pure method '{s}' cannot be called on constant")?
|
||||
}
|
||||
|
||||
Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant {s}")?,
|
||||
@ -230,8 +220,8 @@ impl fmt::Display for EvalAltResult {
|
||||
Self::ErrorArithmetic(s, ..) if s.is_empty() => f.write_str("Arithmetic error")?,
|
||||
Self::ErrorArithmetic(s, ..) => f.write_str(s)?,
|
||||
|
||||
Self::LoopBreak(true, ..) => f.write_str("'break' must be inside a loop")?,
|
||||
Self::LoopBreak(false, ..) => f.write_str("'continue' must be inside a loop")?,
|
||||
Self::LoopBreak(true, ..) => f.write_str("'break' must be within a loop")?,
|
||||
Self::LoopBreak(false, ..) => f.write_str("'continue' must be within a loop")?,
|
||||
|
||||
Self::Return(..) => f.write_str("NOT AN ERROR - function returns value")?,
|
||||
|
||||
@ -261,7 +251,7 @@ impl fmt::Display for EvalAltResult {
|
||||
f,
|
||||
"Bit-field index {index} out of bounds: only {max} bits in bit-field",
|
||||
)?,
|
||||
Self::ErrorDataTooLarge(typ, ..) => write!(f, "{typ} exceeds maximum limit")?,
|
||||
Self::ErrorDataTooLarge(typ, ..) => write!(f, "{typ} too large")?,
|
||||
|
||||
Self::ErrorCustomSyntax(s, tokens, ..) => write!(f, "{s}: {}", tokens.join(" "))?,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user