diff --git a/README.md b/README.md index f6c2b692..13527b54 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/src/api/run.rs b/src/api/run.rs index fe5d6b92..fd20d4ef 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -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 } } diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index bee052b7..a1ccc2b8 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -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> { // 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; } } diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 260f7de8..cc2e71d2 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -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, + ); + } } } diff --git a/src/bin/rhai-run.rs b/src/bin/rhai-run.rs index 5bd464a3..a70c97cd 100644 --- a/src/bin/rhai-run.rs +++ b/src/bin/rhai-run.rs @@ -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!(); } diff --git a/src/func/script.rs b/src/func/script.rs index db74c8bc..6120d714 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -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")] diff --git a/src/types/error.rs b/src/types/error.rs index bc65040b..0f4c1cdc 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -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(" "))?, }