1) Change namespaces to iter_namespaces

2) throw can throw any value
This commit is contained in:
Stephen Chung 2020-10-20 18:09:26 +08:00
parent 09f8b13f2d
commit 5ee9dfc5cd
15 changed files with 97 additions and 69 deletions

View File

@ -17,6 +17,7 @@ New features
------------ ------------
* The plugins system is enhanced to support functions taking a `NativeCallContext` as the first parameter. * The plugins system is enhanced to support functions taking a `NativeCallContext` as the first parameter.
* `throw` statement can throw any value instead of just text strings.
Enhancements Enhancements
------------ ------------

View File

@ -121,7 +121,7 @@ where:
* `context: &mut EvalContext` - mutable reference to the current evaluation _context_, exposing the following: * `context: &mut EvalContext` - mutable reference to the current evaluation _context_, exposing the following:
* `context.scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to/removed from it. * `context.scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to/removed from it.
* `context.engine(): &Engine` - reference to the current [`Engine`]. * `context.engine(): &Engine` - reference to the current [`Engine`].
* `context.namespaces(): &[&Module]` - reference to the chain of namespaces (as a slice of [modules]) containing all script-defined functions. * `context.iter_namespaces(): impl Iterator<Item = &Module>` - iterator of the namespaces (as [modules]) containing all script-defined functions.
* `context.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any. * `context.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any.
* `context.call_level(): usize` - the current nesting level of function calls. * `context.call_level(): usize` - the current nesting level of function calls.

View File

@ -77,7 +77,7 @@ where:
* `context: &EvalContext` - reference to the current evaluation _context_, which exposes the following fields: * `context: &EvalContext` - reference to the current evaluation _context_, which exposes the following fields:
* `context.scope: &Scope` - reference to the current [`Scope`] containing all variables up to the current evaluation position. * `context.scope: &Scope` - reference to the current [`Scope`] containing all variables up to the current evaluation position.
* `context.engine(): &Engine` - reference to the current [`Engine`]. * `context.engine(): &Engine` - reference to the current [`Engine`].
* `context.namespaces(): &[&Module]` - reference to the chain of namespaces (as a slice of [modules]) containing all script-defined functions. * `context.iter_namespaces(): impl Iterator<Item = &Module>` - iterator of the namespaces (as [modules]) containing all script-defined functions.
* `context.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any. * `context.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any.
* `context.call_level(): usize` - the current nesting level of function calls. * `context.call_level(): usize` - the current nesting level of function calls.

View File

@ -199,9 +199,9 @@ fn call_fn_ptr_with_value(context: NativeCallContext, args: &mut [&mut Dynamic])
-> Result<Dynamic, Box<EvalAltResult>> -> Result<Dynamic, Box<EvalAltResult>>
{ {
// 'args' is guaranteed to contain enough arguments of the correct types // 'args' is guaranteed to contain enough arguments of the correct types
let fp = std::mem::take(args[1]).cast::<FnPtr>(); // 2nd argument - function pointer let fp = std::mem::take(args[1]).cast::<FnPtr>(); // 2nd argument - function pointer
let value = args[2].clone(); // 3rd argument - function argument let value = args[2].clone(); // 3rd argument - function argument
let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer
// Use 'FnPtr::call_dynamic' to call the function pointer. // Use 'FnPtr::call_dynamic' to call the function pointer.
// Beware, private script-defined functions will not be found. // Beware, private script-defined functions will not be found.
@ -238,7 +238,7 @@ let engine = Engine::new();
let mut ast = engine.compile( let mut ast = engine.compile(
r#" r#"
let test = "hello"; let test = "hello";
|x| test + x // this creates an closure |x| test + x // this creates an closure
"#, "#,
)?; )?;
@ -248,9 +248,13 @@ let fn_ptr = engine.eval_ast::<FnPtr>(&ast)?;
// Get rid of the script, retaining only functions // Get rid of the script, retaining only functions
ast.retain_functions(|_, _, _| true); ast.retain_functions(|_, _, _| true);
// Create native call context via a tuple containing the Engine and the // Create native call context via a tuple
// set of script-defined functions (within the AST) in form of a slice. let context =
let context = (&engine, &[ast.as_ref()]).into(); (
&engine, // the 'Engine'
&[ast.as_ref()] // function namespace from the 'AST'
// as a one-element slice
).into();
// 'f' captures: the engine, the AST, and the closure // 'f' captures: the engine, the AST, and the closure
let f = move |x: i64| fn_ptr.call_dynamic(context, None, [x.into()]); let f = move |x: i64| fn_ptr.call_dynamic(context, None, [x.into()]);

View File

@ -10,24 +10,24 @@ To deliberately return an error during an evaluation, use the `throw` keyword.
```rust ```rust
if some_bad_condition_has_happened { if some_bad_condition_has_happened {
throw error; // 'throw' takes a string as the exception text throw error; // 'throw' any value as the exception
} }
throw; // defaults to empty exception text: "" throw; // defaults to '()'
``` ```
Exceptions thrown via `throw` in the script can be captured in Rust by matching Exceptions thrown via `throw` in the script can be captured in Rust by matching
`Err(Box<EvalAltResult::ErrorRuntime(reason, position)>)` with the exception text `Err(Box<EvalAltResult::ErrorRuntime(value, position)>)` with the exception value
captured by `reason`. captured by `value`.
```rust ```rust
let result = engine.eval::<i64>(r#" let result = engine.eval::<i64>(r#"
let x = 42; let x = 42;
if x > 0 { if x > 0 {
throw x + " is too large!"; throw x;
} }
"#); "#);
println!(result); // prints "Runtime error: 42 is too large! (line 5, position 15)" println!(result); // prints "Runtime error: 42 (line 5, position 15)"
``` ```

View File

@ -70,9 +70,8 @@ where:
* `context: NativeCallContext` - the current _native call context_, which exposes the following: * `context: NativeCallContext` - the current _native call context_, which exposes the following:
* `context.engine(): &Engine` - the current [`Engine`], with all configurations and settings. * `context.engine(): &Engine` - the current [`Engine`], with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer].
using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. * `context.iter_namespaces(): impl Iterator<Item = &Module>` - iterator of the namespaces (as [modules]) containing all script-defined functions.
* `context.namespaces(): &[&Module]` - reference to the chain of namespaces (as a slice of [modules]) containing all script-defined functions.
* `args: &mut [&mut Dynamic]` - a slice containing `&mut` references to [`Dynamic`] values. * `args: &mut [&mut Dynamic]` - a slice containing `&mut` references to [`Dynamic`] values.
The slice is guaranteed to contain enough arguments _of the correct types_. The slice is guaranteed to contain enough arguments _of the correct types_.

View File

@ -29,18 +29,11 @@ fn print_error(input: &str, err: EvalAltResult) {
// Specific position // Specific position
println!("{}{}", line_no, lines[pos.line().unwrap() - 1]); println!("{}{}", line_no, lines[pos.line().unwrap() - 1]);
let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
err => err.to_string(),
};
println!( println!(
"{0:>1$} {2}", "{0:>1$} {2}",
"^", "^",
line_no.len() + pos.position().unwrap(), line_no.len() + pos.position().unwrap(),
err_text.replace(&pos_text, "") err.to_string().replace(&pos_text, "")
); );
} }
} }

View File

@ -32,14 +32,7 @@ fn eprint_error(input: &str, err: EvalAltResult) {
eprintln!("{}", err); eprintln!("{}", err);
} else { } else {
// Specific position // Specific position
let err_text = match err { eprint_line(&lines, pos, &err.to_string())
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
err => err.to_string(),
};
eprint_line(&lines, pos, &err_text)
} }
} }

View File

@ -442,13 +442,13 @@ pub struct Limits {
/// Context of a script evaluation process. /// Context of a script evaluation process.
#[derive(Debug)] #[derive(Debug)]
pub struct EvalContext<'e, 'x, 'px: 'x, 'a, 's, 'm, 'pm: 'm, 't, 'pt: 't> { pub struct EvalContext<'e, 'x, 'px: 'x, 'a, 's, 'm, 'pm: 'm, 't, 'pt: 't> {
engine: &'e Engine, pub(crate) engine: &'e Engine,
pub scope: &'x mut Scope<'px>, pub scope: &'x mut Scope<'px>,
pub(crate) mods: &'a mut Imports, pub(crate) mods: &'a mut Imports,
pub(crate) state: &'s mut State, pub(crate) state: &'s mut State,
lib: &'m [&'pm Module], pub(crate) lib: &'m [&'pm Module],
pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>, pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>,
level: usize, pub(crate) level: usize,
} }
impl<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> { impl<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> {
@ -465,10 +465,10 @@ impl<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm,
pub fn imports(&self) -> &'a Imports { pub fn imports(&self) -> &'a Imports {
self.mods self.mods
} }
/// The chain of namespaces containing definition of all script-defined functions. /// Get an iterator over the namespaces containing definition of all script-defined functions.
#[inline(always)] #[inline(always)]
pub fn namespaces(&self) -> &'m [&'pm Module] { pub fn iter_namespaces(&self) -> impl Iterator<Item = &'pm Module> + 'm {
self.lib self.lib.iter().cloned()
} }
/// The current bound `this` pointer, if any. /// The current bound `this` pointer, if any.
#[inline(always)] #[inline(always)]
@ -1951,16 +1951,12 @@ impl Engine {
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => { Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => {
let expr = x.1.as_ref().unwrap(); let expr = x.1.as_ref().unwrap();
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
EvalAltResult::ErrorRuntime( EvalAltResult::ErrorRuntime(val, (x.0).1).into()
val.take_string().unwrap_or_else(|_| "".into()),
(x.0).1,
)
.into()
} }
// Empty throw // Empty throw
Stmt::ReturnWithVal(x) if (x.0).0 == ReturnType::Exception => { Stmt::ReturnWithVal(x) if (x.0).0 == ReturnType::Exception => {
EvalAltResult::ErrorRuntime("".into(), (x.0).1).into() EvalAltResult::ErrorRuntime(().into(), (x.0).1).into()
} }
Stmt::ReturnWithVal(_) => unreachable!(), Stmt::ReturnWithVal(_) => unreachable!(),

View File

@ -533,7 +533,8 @@ impl Engine {
format!( format!(
"'{}' should not be called in method style. Try {}(...);", "'{}' should not be called in method style. Try {}(...);",
fn_name, fn_name fn_name, fn_name
), )
.into(),
Position::none(), Position::none(),
) )
.into() .into()

View File

@ -72,10 +72,10 @@ impl<'e, 'm, 'pm> NativeCallContext<'e, 'm, 'pm> {
pub fn engine(&self) -> &'e Engine { pub fn engine(&self) -> &'e Engine {
self.engine self.engine
} }
/// The chain of namespaces containing definition of all script-defined functions. /// Get an iterator over the namespaces containing definition of all script-defined functions.
#[inline(always)] #[inline(always)]
pub fn namespaces(&self) -> &'m [&'pm Module] { pub fn iter_namespaces(&self) -> impl Iterator<Item = &'pm Module> + 'm {
self.lib self.lib.iter().cloned()
} }
} }
@ -186,7 +186,7 @@ impl FnPtr {
.engine() .engine()
.exec_fn_call( .exec_fn_call(
&mut Default::default(), &mut Default::default(),
context.namespaces(), context.lib,
fn_name, fn_name,
hash_script, hash_script,
args.as_mut(), args.as_mut(),

View File

@ -3297,25 +3297,24 @@ fn parse_stmt(
match input.peek().unwrap() { match input.peek().unwrap() {
// `return`/`throw` at <EOF> // `return`/`throw` at <EOF>
(Token::EOF, pos) => Ok(Some(Stmt::ReturnWithVal(Box::new(( (Token::EOF, pos) => Ok(Some(Stmt::ReturnWithVal(Box::new((
(return_type, *pos), (return_type, token_pos),
None, None,
token_pos, *pos,
))))), ))))),
// `return;` or `throw;` // `return;` or `throw;`
(Token::SemiColon, _) => Ok(Some(Stmt::ReturnWithVal(Box::new(( (Token::SemiColon, _) => Ok(Some(Stmt::ReturnWithVal(Box::new((
(return_type, settings.pos), (return_type, token_pos),
None, None,
token_pos, settings.pos,
))))), ))))),
// `return` or `throw` with expression // `return` or `throw` with expression
(_, _) => { (_, _) => {
let expr = parse_expr(input, state, lib, settings.level_up())?; let expr = parse_expr(input, state, lib, settings.level_up())?;
let pos = expr.position(); let pos = expr.position();
Ok(Some(Stmt::ReturnWithVal(Box::new(( Ok(Some(Stmt::ReturnWithVal(Box::new((
(return_type, pos), (return_type, token_pos),
Some(expr), Some(expr),
token_pos, pos,
))))) )))))
} }
} }

View File

@ -4,6 +4,7 @@ use crate::any::Dynamic;
use crate::error::ParseErrorType; use crate::error::ParseErrorType;
use crate::parser::INT; use crate::parser::INT;
use crate::token::Position; use crate::token::Position;
use crate::utils::ImmutableString;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::engine::is_anonymous_fn; use crate::engine::is_anonymous_fn;
@ -82,8 +83,8 @@ pub enum EvalAltResult {
ErrorDataTooLarge(String, usize, usize, Position), ErrorDataTooLarge(String, usize, usize, Position),
/// The script is prematurely terminated. /// The script is prematurely terminated.
ErrorTerminated(Position), ErrorTerminated(Position),
/// Run-time error encountered. Wrapped value is the error message. /// Run-time error encountered. Wrapped value is the error.
ErrorRuntime(String, Position), ErrorRuntime(Dynamic, Position),
/// Breaking out of loops - not an error if within a loop. /// Breaking out of loops - not an error if within a loop.
/// The wrapped value, if true, means breaking clean out of the loop (i.e. a `break` statement). /// The wrapped value, if true, means breaking clean out of the loop (i.e. a `break` statement).
@ -186,7 +187,12 @@ impl fmt::Display for EvalAltResult {
| Self::ErrorStackOverflow(_) | Self::ErrorStackOverflow(_)
| Self::ErrorTerminated(_) => f.write_str(desc)?, | Self::ErrorTerminated(_) => f.write_str(desc)?,
Self::ErrorRuntime(s, _) => f.write_str(if s.is_empty() { desc } else { s })?, Self::ErrorRuntime(d, _) if d.is::<ImmutableString>() => {
let s = d.as_str().unwrap();
write!(f, "{}: {}", desc, if s.is_empty() { desc } else { s })?
}
Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str(desc)?,
Self::ErrorRuntime(d, _) => write!(f, "{}: {}", desc, d)?,
Self::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?, Self::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?,
Self::ErrorMismatchOutputType(r, s, _) => { Self::ErrorMismatchOutputType(r, s, _) => {
@ -248,7 +254,7 @@ impl fmt::Display for EvalAltResult {
impl<T: AsRef<str>> From<T> for EvalAltResult { impl<T: AsRef<str>> From<T> for EvalAltResult {
#[inline(always)] #[inline(always)]
fn from(err: T) -> Self { fn from(err: T) -> Self {
Self::ErrorRuntime(err.as_ref().to_string(), Position::none()) Self::ErrorRuntime(err.as_ref().to_string().into(), Position::none())
} }
} }
@ -256,13 +262,49 @@ impl<T: AsRef<str>> From<T> for Box<EvalAltResult> {
#[inline(always)] #[inline(always)]
fn from(err: T) -> Self { fn from(err: T) -> Self {
Box::new(EvalAltResult::ErrorRuntime( Box::new(EvalAltResult::ErrorRuntime(
err.as_ref().to_string(), err.as_ref().to_string().into(),
Position::none(), Position::none(),
)) ))
} }
} }
impl EvalAltResult { impl EvalAltResult {
/// Can this error be caught?
pub fn catchable(&self) -> bool {
match self {
Self::ErrorSystem(_, _) => false,
Self::ErrorParsing(_, _) => false,
Self::ErrorFunctionNotFound(_, _)
| Self::ErrorInFunctionCall(_, _, _)
| Self::ErrorInModule(_, _, _)
| Self::ErrorUnboundThis(_)
| Self::ErrorMismatchDataType(_, _, _)
| Self::ErrorArrayBounds(_, _, _)
| Self::ErrorStringBounds(_, _, _)
| Self::ErrorIndexingType(_, _)
| Self::ErrorFor(_)
| Self::ErrorVariableNotFound(_, _)
| Self::ErrorModuleNotFound(_, _)
| Self::ErrorDataRace(_, _)
| Self::ErrorAssignmentToUnknownLHS(_)
| Self::ErrorAssignmentToConstant(_, _)
| Self::ErrorMismatchOutputType(_, _, _)
| Self::ErrorInExpr(_)
| Self::ErrorDotExpr(_, _)
| Self::ErrorArithmetic(_, _)
| Self::ErrorRuntime(_, _) => true,
Self::ErrorTooManyOperations(_)
| Self::ErrorTooManyModules(_)
| Self::ErrorStackOverflow(_)
| Self::ErrorDataTooLarge(_, _, _, _)
| Self::ErrorTerminated(_)
| Self::LoopBreak(_, _)
| Self::Return(_, _) => false,
}
}
/// Get the `Position` of this error. /// Get the `Position` of this error.
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {

View File

@ -64,14 +64,14 @@ impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_> {
&mut self, &mut self,
expr: &Expression, expr: &Expression,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
self.engine().eval_expr( self.engine.eval_expr(
self.scope, self.scope,
self.mods, self.mods,
self.state, self.state,
self.namespaces(), self.lib,
self.this_ptr, self.this_ptr,
expr.expr(), expr.expr(),
self.call_level(), self.level,
) )
} }
} }

View File

@ -5,12 +5,12 @@ fn test_throw() {
let engine = Engine::new(); let engine = Engine::new();
assert!(matches!( assert!(matches!(
*engine.eval::<()>(r#"if true { throw "hello" }"#).expect_err("expects error"), *engine.eval::<()>("if true { throw 42 }").expect_err("expects error"),
EvalAltResult::ErrorRuntime(s, _) if s == "hello" EvalAltResult::ErrorRuntime(s, _) if s.as_int().unwrap() == 42
)); ));
assert!(matches!( assert!(matches!(
*engine.eval::<()>(r#"throw"#).expect_err("expects error"), *engine.eval::<()>(r#"throw"#).expect_err("expects error"),
EvalAltResult::ErrorRuntime(s, _) if s == "" EvalAltResult::ErrorRuntime(s, _) if s.is::<()>()
)); ));
} }