Add try-catch.
This commit is contained in:
parent
5ee9dfc5cd
commit
07bdb824fe
@ -5,6 +5,9 @@ Rhai Release Notes
|
|||||||
Version 0.19.3
|
Version 0.19.3
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
This version streamlines some of the advanced API's, and adds the `try` ... `catch` statement
|
||||||
|
to catch exceptions.
|
||||||
|
|
||||||
Breaking changes
|
Breaking changes
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
@ -17,7 +20,8 @@ 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.
|
* `throw` statement can now throw any value instead of just text strings.
|
||||||
|
* New `try` ... `catch` statement to catch exceptions.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
@ -82,7 +82,8 @@ The Rhai Scripting Language
|
|||||||
12. [For Loop](language/for.md)
|
12. [For Loop](language/for.md)
|
||||||
13. [Return Values](language/return.md)
|
13. [Return Values](language/return.md)
|
||||||
14. [Throw Exception on Error](language/throw.md)
|
14. [Throw Exception on Error](language/throw.md)
|
||||||
15. [Functions](language/functions.md)
|
15. [Catch Exceptions](language/try-catch.md)
|
||||||
|
16. [Functions](language/functions.md)
|
||||||
1. [Call Method as Function](language/method.md)
|
1. [Call Method as Function](language/method.md)
|
||||||
2. [Overloading](language/overload.md)
|
2. [Overloading](language/overload.md)
|
||||||
3. [Namespaces](language/fn-namespaces.md)
|
3. [Namespaces](language/fn-namespaces.md)
|
||||||
@ -90,11 +91,11 @@ The Rhai Scripting Language
|
|||||||
5. [Currying](language/fn-curry.md)
|
5. [Currying](language/fn-curry.md)
|
||||||
6. [Anonymous Functions](language/fn-anon.md)
|
6. [Anonymous Functions](language/fn-anon.md)
|
||||||
7. [Closures](language/fn-closure.md)
|
7. [Closures](language/fn-closure.md)
|
||||||
16. [Print and Debug](language/print-debug.md)
|
17. [Print and Debug](language/print-debug.md)
|
||||||
17. [Modules](language/modules/index.md)
|
18. [Modules](language/modules/index.md)
|
||||||
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
|
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
|
||||||
2. [Import Modules](language/modules/import.md)
|
2. [Import Modules](language/modules/import.md)
|
||||||
18. [Eval Statement](language/eval.md)
|
19. [Eval Statement](language/eval.md)
|
||||||
6. [Safety and Protection](safety/index.md)
|
6. [Safety and Protection](safety/index.md)
|
||||||
1. [Checked Arithmetic](safety/checked.md)
|
1. [Checked Arithmetic](safety/checked.md)
|
||||||
2. [Sand-Boxing](safety/sandbox.md)
|
2. [Sand-Boxing](safety/sandbox.md)
|
||||||
|
@ -21,6 +21,8 @@ Keywords List
|
|||||||
| `break` | break out of loop iteration | | no | |
|
| `break` | break out of loop iteration | | no | |
|
||||||
| `return` | return value | | no | |
|
| `return` | return value | | no | |
|
||||||
| `throw` | throw exception | | no | |
|
| `throw` | throw exception | | no | |
|
||||||
|
| `try` | trap exception | | no | |
|
||||||
|
| `catch` | catch exception | | no | |
|
||||||
| `import` | import module | [`no_module`] | no | |
|
| `import` | import module | [`no_module`] | no | |
|
||||||
| `export` | export variable | [`no_module`] | no | |
|
| `export` | export variable | [`no_module`] | no | |
|
||||||
| `as` | alias for variable export | [`no_module`] | no | |
|
| `as` | alias for variable export | [`no_module`] | no | |
|
||||||
@ -55,8 +57,6 @@ Reserved Keywords
|
|||||||
| `case` | matching |
|
| `case` | matching |
|
||||||
| `public` | function/field access |
|
| `public` | function/field access |
|
||||||
| `new` | constructor |
|
| `new` | constructor |
|
||||||
| `try` | trap exception |
|
|
||||||
| `catch` | catch exception |
|
|
||||||
| `use` | import namespace |
|
| `use` | import namespace |
|
||||||
| `with` | scope |
|
| `with` | scope |
|
||||||
| `module` | module |
|
| `module` | module |
|
||||||
|
@ -29,5 +29,24 @@ let result = engine.eval::<i64>(r#"
|
|||||||
}
|
}
|
||||||
"#);
|
"#);
|
||||||
|
|
||||||
println!(result); // prints "Runtime error: 42 (line 5, position 15)"
|
println!("{}", result); // prints "Runtime error: 42 (line 5, position 15)"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Catch a Thrown Exception
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
It is possible to _catch_ an exception instead of having it abort the evaluation
|
||||||
|
of the entire script via the [`try` ... `catch`]({{rootUrl}}/language/try-catch.md)
|
||||||
|
statement common to many C-like languages.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
try
|
||||||
|
{
|
||||||
|
throw 42;
|
||||||
|
}
|
||||||
|
catch (err) // 'err' captures the thrown exception value
|
||||||
|
{
|
||||||
|
print(err); // prints 42
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
104
doc/src/language/try-catch.md
Normal file
104
doc/src/language/try-catch.md
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
Catch Exceptions
|
||||||
|
================
|
||||||
|
|
||||||
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
|
||||||
|
When an [exception] is thrown via a `throw` statement, evaluation of the script halts
|
||||||
|
and the [`Engine`] returns with `Err(Box<EvalAltResult::ErrorRuntime>)` containing the
|
||||||
|
exception value that has been thrown.
|
||||||
|
|
||||||
|
It is possible, via the `try` ... `catch` statement, to _catch_ exceptions.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Catch an exception and capturing its value
|
||||||
|
try
|
||||||
|
{
|
||||||
|
throw 42;
|
||||||
|
}
|
||||||
|
catch (err) // 'err' captures the thrown exception value
|
||||||
|
{
|
||||||
|
print(err); // prints 42
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch an exception without capturing its value
|
||||||
|
try
|
||||||
|
{
|
||||||
|
print(42/0); // deliberate divide-by-zero exception
|
||||||
|
}
|
||||||
|
catch // no catch variable - exception value is discarded
|
||||||
|
{
|
||||||
|
print("Ouch!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exception in the 'catch' block
|
||||||
|
try
|
||||||
|
{
|
||||||
|
print(42/0); // throw divide-by-zero exception
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("You seem to be dividing by zero here...");
|
||||||
|
|
||||||
|
throw "die"; // a 'throw' statement inside a 'catch' block
|
||||||
|
// throws a new exception
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Re-Throw Exception
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Like the `try` ... `catch` syntax in most languages, it is possible to _re-throw_
|
||||||
|
an exception within the `catch` block simply by another `throw` statement without
|
||||||
|
a value.
|
||||||
|
|
||||||
|
|
||||||
|
```rust
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Call something that will throw an exception...
|
||||||
|
do_something_bad_that_throws();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Oooh! You've done something real bad!");
|
||||||
|
|
||||||
|
throw; // 'throw' without a value within a 'catch' block
|
||||||
|
// re-throws the original exception
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Catchable Exceptions
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Many script-oriented exceptions can be caught via `try` ... `catch`:
|
||||||
|
|
||||||
|
* Runtime error thrown by a `throw` statement
|
||||||
|
* Arithmetic error
|
||||||
|
* Variable not found
|
||||||
|
* [Function] not found
|
||||||
|
* [Module] not found
|
||||||
|
* Unbound [`this`]
|
||||||
|
* Data type mismatch
|
||||||
|
* [Array]/[string] indexing out-of-bounds
|
||||||
|
* Indexing with an inappropriate type
|
||||||
|
* `for` statement without an iterator
|
||||||
|
* Error in an `in` expression
|
||||||
|
* Data race detected
|
||||||
|
* Assignment to a calculated value/constant value
|
||||||
|
* Dot expression error
|
||||||
|
|
||||||
|
|
||||||
|
Non-Catchable Exceptions
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Some exceptions _cannot_ be caught:
|
||||||
|
|
||||||
|
* Syntax error during parsing
|
||||||
|
* System error - e.g. script file not found
|
||||||
|
* Script evaluation over [limits]({{rootUrl}}/safety/index.md)
|
||||||
|
* [Stack overflow][maximum call stack depth]
|
||||||
|
* Script evaluation manually terminated
|
@ -89,6 +89,8 @@
|
|||||||
[function overloading]: {{rootUrl}}/rust/functions.md#function-overloading
|
[function overloading]: {{rootUrl}}/rust/functions.md#function-overloading
|
||||||
[fallible function]: {{rootUrl}}/rust/fallible.md
|
[fallible function]: {{rootUrl}}/rust/fallible.md
|
||||||
[fallible functions]: {{rootUrl}}/rust/fallible.md
|
[fallible functions]: {{rootUrl}}/rust/fallible.md
|
||||||
|
[exception]: {{rootUrl}}/language/throw.md
|
||||||
|
[exceptions]: {{rootUrl}}/language/throw.md
|
||||||
[function pointer]: {{rootUrl}}/language/fn-ptr.md
|
[function pointer]: {{rootUrl}}/language/fn-ptr.md
|
||||||
[function pointers]: {{rootUrl}}/language/fn-ptr.md
|
[function pointers]: {{rootUrl}}/language/fn-ptr.md
|
||||||
[currying]: {{rootUrl}}/language/fn-curry.md
|
[currying]: {{rootUrl}}/language/fn-curry.md
|
||||||
|
@ -1932,6 +1932,63 @@ impl Engine {
|
|||||||
// Break statement
|
// Break statement
|
||||||
Stmt::Break(pos) => EvalAltResult::LoopBreak(true, *pos).into(),
|
Stmt::Break(pos) => EvalAltResult::LoopBreak(true, *pos).into(),
|
||||||
|
|
||||||
|
// Try/Catch statement
|
||||||
|
Stmt::TryCatch(x) => {
|
||||||
|
let ((body, _), var_def, (catch_body, _)) = x.as_ref();
|
||||||
|
|
||||||
|
let result = self
|
||||||
|
.eval_stmt(scope, mods, state, lib, this_ptr, body, level)
|
||||||
|
.map(|_| ().into());
|
||||||
|
|
||||||
|
if let Err(err) = result {
|
||||||
|
match *err {
|
||||||
|
mut err @ EvalAltResult::ErrorRuntime(_, _) | mut err
|
||||||
|
if err.catchable() =>
|
||||||
|
{
|
||||||
|
let value = match err {
|
||||||
|
EvalAltResult::ErrorRuntime(ref x, _) => x.clone(),
|
||||||
|
_ => {
|
||||||
|
err.set_position(Position::none());
|
||||||
|
err.to_string().into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let has_var = if let Some((var_name, _)) = var_def {
|
||||||
|
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
|
||||||
|
scope.push(var_name, value);
|
||||||
|
state.scope_level += 1;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result = self
|
||||||
|
.eval_stmt(scope, mods, state, lib, this_ptr, catch_body, level)
|
||||||
|
.map(|_| ().into());
|
||||||
|
|
||||||
|
if let Some(result_err) = result.as_ref().err() {
|
||||||
|
match result_err.as_ref() {
|
||||||
|
EvalAltResult::ErrorRuntime(x, pos) if x.is::<()>() => {
|
||||||
|
err.set_position(*pos);
|
||||||
|
result = Err(Box::new(err));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_var {
|
||||||
|
scope.rewind(scope.len() - 1);
|
||||||
|
state.scope_level -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
_ => Err(err),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return value
|
// Return value
|
||||||
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
|
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
|
||||||
let expr = x.1.as_ref().unwrap();
|
let expr = x.1.as_ref().unwrap();
|
||||||
|
@ -104,7 +104,7 @@ pub enum ParseErrorType {
|
|||||||
///
|
///
|
||||||
/// Never appears under the `no_object` feature.
|
/// Never appears under the `no_object` feature.
|
||||||
PropertyExpected,
|
PropertyExpected,
|
||||||
/// Missing a variable name after the `let`, `const` or `for` keywords.
|
/// Missing a variable name after the `let`, `const`, `for` or `catch` keywords.
|
||||||
VariableExpected,
|
VariableExpected,
|
||||||
/// An identifier is a reserved keyword.
|
/// An identifier is a reserved keyword.
|
||||||
Reserved(String),
|
Reserved(String),
|
||||||
|
@ -268,7 +268,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
))),
|
))),
|
||||||
// let id;
|
// let id;
|
||||||
stmt @ Stmt::Let(_) => stmt,
|
stmt @ Stmt::Let(_) => stmt,
|
||||||
// import expr as id;
|
// import expr as var;
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1, x.2))),
|
Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1, x.2))),
|
||||||
// { block }
|
// { block }
|
||||||
@ -389,6 +389,22 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
_ => Stmt::Block(Box::new((result.into(), pos))),
|
_ => Stmt::Block(Box::new((result.into(), pos))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// try { block } catch ( var ) { block }
|
||||||
|
Stmt::TryCatch(x) if (x.0).0.is_pure() => {
|
||||||
|
// If try block is pure, there will never be any exceptions
|
||||||
|
state.set_dirty();
|
||||||
|
let pos = (x.0).0.position();
|
||||||
|
let mut statements: StaticVec<_> = Default::default();
|
||||||
|
statements.push(optimize_stmt((x.0).0, state, preserve_result));
|
||||||
|
statements.push(Stmt::Noop(pos));
|
||||||
|
Stmt::Block(Box::new((statements, pos)))
|
||||||
|
}
|
||||||
|
// try { block } catch ( var ) { block }
|
||||||
|
Stmt::TryCatch(x) => Stmt::TryCatch(Box::new((
|
||||||
|
(optimize_stmt((x.0).0, state, false), (x.0).1),
|
||||||
|
x.1,
|
||||||
|
(optimize_stmt((x.2).0, state, false), (x.2).1),
|
||||||
|
))),
|
||||||
// expr;
|
// expr;
|
||||||
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
|
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
|
||||||
// return expr;
|
// return expr;
|
||||||
|
115
src/parser.rs
115
src/parser.rs
@ -758,6 +758,14 @@ pub enum Stmt {
|
|||||||
Const(Box<((String, Position), Option<Expr>, Position)>),
|
Const(Box<((String, Position), Option<Expr>, Position)>),
|
||||||
/// { stmt; ... }
|
/// { stmt; ... }
|
||||||
Block(Box<(StaticVec<Stmt>, Position)>),
|
Block(Box<(StaticVec<Stmt>, Position)>),
|
||||||
|
/// try { stmt; ... } catch ( var ) { stmt; ... }
|
||||||
|
TryCatch(
|
||||||
|
Box<(
|
||||||
|
(Stmt, Position),
|
||||||
|
Option<(String, Position)>,
|
||||||
|
(Stmt, Position),
|
||||||
|
)>,
|
||||||
|
),
|
||||||
/// expr
|
/// expr
|
||||||
Expr(Box<Expr>),
|
Expr(Box<Expr>),
|
||||||
/// continue
|
/// continue
|
||||||
@ -766,10 +774,10 @@ pub enum Stmt {
|
|||||||
Break(Position),
|
Break(Position),
|
||||||
/// return/throw
|
/// return/throw
|
||||||
ReturnWithVal(Box<((ReturnType, Position), Option<Expr>, Position)>),
|
ReturnWithVal(Box<((ReturnType, Position), Option<Expr>, Position)>),
|
||||||
/// import expr as module
|
/// import expr as var
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Import(Box<(Expr, Option<(ImmutableString, Position)>, Position)>),
|
Import(Box<(Expr, Option<(ImmutableString, Position)>, Position)>),
|
||||||
/// expr id as name, ...
|
/// export var as var, ...
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Export(
|
Export(
|
||||||
Box<(
|
Box<(
|
||||||
@ -796,13 +804,14 @@ impl Stmt {
|
|||||||
Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos,
|
Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos,
|
||||||
Stmt::Let(x) => (x.0).1,
|
Stmt::Let(x) => (x.0).1,
|
||||||
Stmt::Const(x) => (x.0).1,
|
Stmt::Const(x) => (x.0).1,
|
||||||
Stmt::ReturnWithVal(x) => (x.0).1,
|
|
||||||
Stmt::Block(x) => x.1,
|
Stmt::Block(x) => x.1,
|
||||||
Stmt::IfThenElse(x) => x.3,
|
Stmt::IfThenElse(x) => x.3,
|
||||||
Stmt::Expr(x) => x.position(),
|
Stmt::Expr(x) => x.position(),
|
||||||
Stmt::While(x) => x.2,
|
Stmt::While(x) => x.2,
|
||||||
Stmt::Loop(x) => x.1,
|
Stmt::Loop(x) => x.1,
|
||||||
Stmt::For(x) => x.3,
|
Stmt::For(x) => x.3,
|
||||||
|
Stmt::ReturnWithVal(x) => (x.0).1,
|
||||||
|
Stmt::TryCatch(x) => (x.0).1,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(x) => x.2,
|
Stmt::Import(x) => x.2,
|
||||||
@ -820,7 +829,6 @@ impl Stmt {
|
|||||||
Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos = new_pos,
|
Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos = new_pos,
|
||||||
Stmt::Let(x) => (x.0).1 = new_pos,
|
Stmt::Let(x) => (x.0).1 = new_pos,
|
||||||
Stmt::Const(x) => (x.0).1 = new_pos,
|
Stmt::Const(x) => (x.0).1 = new_pos,
|
||||||
Stmt::ReturnWithVal(x) => (x.0).1 = new_pos,
|
|
||||||
Stmt::Block(x) => x.1 = new_pos,
|
Stmt::Block(x) => x.1 = new_pos,
|
||||||
Stmt::IfThenElse(x) => x.3 = new_pos,
|
Stmt::IfThenElse(x) => x.3 = new_pos,
|
||||||
Stmt::Expr(x) => {
|
Stmt::Expr(x) => {
|
||||||
@ -829,6 +837,8 @@ impl Stmt {
|
|||||||
Stmt::While(x) => x.2 = new_pos,
|
Stmt::While(x) => x.2 = new_pos,
|
||||||
Stmt::Loop(x) => x.1 = new_pos,
|
Stmt::Loop(x) => x.1 = new_pos,
|
||||||
Stmt::For(x) => x.3 = new_pos,
|
Stmt::For(x) => x.3 = new_pos,
|
||||||
|
Stmt::ReturnWithVal(x) => (x.0).1 = new_pos,
|
||||||
|
Stmt::TryCatch(x) => (x.0).1 = new_pos,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(x) => x.2 = new_pos,
|
Stmt::Import(x) => x.2 = new_pos,
|
||||||
@ -849,7 +859,8 @@ impl Stmt {
|
|||||||
| Stmt::While(_)
|
| Stmt::While(_)
|
||||||
| Stmt::Loop(_)
|
| Stmt::Loop(_)
|
||||||
| Stmt::For(_)
|
| Stmt::For(_)
|
||||||
| Stmt::Block(_) => true,
|
| Stmt::Block(_)
|
||||||
|
| Stmt::TryCatch(_) => true,
|
||||||
|
|
||||||
// A No-op requires a semicolon in order to know it is an empty statement!
|
// A No-op requires a semicolon in order to know it is an empty statement!
|
||||||
Stmt::Noop(_) => false,
|
Stmt::Noop(_) => false,
|
||||||
@ -884,6 +895,7 @@ impl Stmt {
|
|||||||
Stmt::Let(_) | Stmt::Const(_) => false,
|
Stmt::Let(_) | Stmt::Const(_) => false,
|
||||||
Stmt::Block(x) => x.0.iter().all(Stmt::is_pure),
|
Stmt::Block(x) => x.0.iter().all(Stmt::is_pure),
|
||||||
Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_) => false,
|
Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_) => false,
|
||||||
|
Stmt::TryCatch(x) => (x.0).0.is_pure() && (x.2).0.is_pure(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(_) => false,
|
Stmt::Import(_) => false,
|
||||||
@ -1358,13 +1370,12 @@ fn eat_token(input: &mut TokenStream, token: Token) -> Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Match a particular token, consuming it if matched.
|
/// Match a particular token, consuming it if matched.
|
||||||
fn match_token(input: &mut TokenStream, token: Token) -> Result<bool, ParseError> {
|
fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) {
|
||||||
let (t, _) = input.peek().unwrap();
|
let (t, pos) = input.peek().unwrap();
|
||||||
if *t == token {
|
if *t == token {
|
||||||
eat_token(input, token);
|
(true, eat_token(input, token))
|
||||||
Ok(true)
|
|
||||||
} else {
|
} else {
|
||||||
Ok(false)
|
(false, *pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1378,7 +1389,7 @@ fn parse_paren_expr(
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||||
|
|
||||||
if match_token(input, Token::RightParen)? {
|
if match_token(input, Token::RightParen).0 {
|
||||||
return Ok(Expr::Unit(settings.pos));
|
return Ok(Expr::Unit(settings.pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1989,7 +2000,7 @@ fn parse_primary(
|
|||||||
// Qualified function call with !
|
// Qualified function call with !
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
(Expr::Variable(x), Token::Bang) if x.1.is_some() => {
|
(Expr::Variable(x), Token::Bang) if x.1.is_some() => {
|
||||||
return Err(if !match_token(input, Token::LeftParen)? {
|
return Err(if !match_token(input, Token::LeftParen).0 {
|
||||||
LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(token_pos)
|
LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(token_pos)
|
||||||
} else {
|
} else {
|
||||||
PERR::BadInput("'!' cannot be used to call module functions".to_string())
|
PERR::BadInput("'!' cannot be used to call module functions".to_string())
|
||||||
@ -1999,12 +2010,13 @@ fn parse_primary(
|
|||||||
// Function call with !
|
// Function call with !
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
(Expr::Variable(x), Token::Bang) => {
|
(Expr::Variable(x), Token::Bang) => {
|
||||||
if !match_token(input, Token::LeftParen)? {
|
let (matched, pos) = match_token(input, Token::LeftParen);
|
||||||
|
if !matched {
|
||||||
return Err(PERR::MissingToken(
|
return Err(PERR::MissingToken(
|
||||||
Token::LeftParen.syntax().into(),
|
Token::LeftParen.syntax().into(),
|
||||||
"to start arguments list of function call".into(),
|
"to start arguments list of function call".into(),
|
||||||
)
|
)
|
||||||
.into_err(input.peek().unwrap().1));
|
.into_err(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ((name, pos), modules, _, _) = *x;
|
let ((name, pos), modules, _, _) = *x;
|
||||||
@ -2813,7 +2825,7 @@ fn parse_if(
|
|||||||
let if_body = parse_block(input, state, lib, settings.level_up())?;
|
let if_body = parse_block(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
// if guard { if_body } else ...
|
// if guard { if_body } else ...
|
||||||
let else_body = if match_token(input, Token::Else).unwrap_or(false) {
|
let else_body = if match_token(input, Token::Else).0 {
|
||||||
Some(if let (Token::If, _) = input.peek().unwrap() {
|
Some(if let (Token::If, _) = input.peek().unwrap() {
|
||||||
// if guard { if_body } else if ...
|
// if guard { if_body } else if ...
|
||||||
parse_if(input, state, lib, settings.level_up())?
|
parse_if(input, state, lib, settings.level_up())?
|
||||||
@ -2957,7 +2969,7 @@ fn parse_let(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// let name = ...
|
// let name = ...
|
||||||
let init_value = if match_token(input, Token::Equals)? {
|
let init_value = if match_token(input, Token::Equals).0 {
|
||||||
// let name = expr
|
// let name = expr
|
||||||
Some(parse_expr(input, state, lib, settings.level_up())?)
|
Some(parse_expr(input, state, lib, settings.level_up())?)
|
||||||
} else {
|
} else {
|
||||||
@ -2997,7 +3009,7 @@ fn parse_import(
|
|||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
// import expr as ...
|
// import expr as ...
|
||||||
if !match_token(input, Token::As)? {
|
if !match_token(input, Token::As).0 {
|
||||||
return Ok(Stmt::Import(Box::new((expr, None, token_pos))));
|
return Ok(Stmt::Import(Box::new((expr, None, token_pos))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3046,7 +3058,7 @@ fn parse_export(
|
|||||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let rename = if match_token(input, Token::As)? {
|
let rename = if match_token(input, Token::As).0 {
|
||||||
match input.next().unwrap() {
|
match input.next().unwrap() {
|
||||||
(Token::Identifier(s), pos) => Some((s.clone(), pos)),
|
(Token::Identifier(s), pos) => Some((s.clone(), pos)),
|
||||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
@ -3121,7 +3133,7 @@ fn parse_block(
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let prev_mods_len = state.modules.len();
|
let prev_mods_len = state.modules.len();
|
||||||
|
|
||||||
while !match_token(input, Token::RightBrace)? {
|
while !match_token(input, Token::RightBrace).0 {
|
||||||
// Parse statements inside the block
|
// Parse statements inside the block
|
||||||
settings.is_global = false;
|
settings.is_global = false;
|
||||||
|
|
||||||
@ -3320,6 +3332,8 @@ fn parse_stmt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Token::Try => parse_try_catch(input, state, lib, settings.level_up()).map(Some),
|
||||||
|
|
||||||
Token::Let => parse_let(input, state, lib, Normal, settings.level_up()).map(Some),
|
Token::Let => parse_let(input, state, lib, Normal, settings.level_up()).map(Some),
|
||||||
Token::Const => parse_let(input, state, lib, Constant, settings.level_up()).map(Some),
|
Token::Const => parse_let(input, state, lib, Constant, settings.level_up()).map(Some),
|
||||||
|
|
||||||
@ -3336,6 +3350,65 @@ fn parse_stmt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a try/catch statement.
|
||||||
|
fn parse_try_catch(
|
||||||
|
input: &mut TokenStream,
|
||||||
|
state: &mut ParseState,
|
||||||
|
lib: &mut FunctionsLib,
|
||||||
|
mut settings: ParseSettings,
|
||||||
|
) -> Result<Stmt, ParseError> {
|
||||||
|
// try ...
|
||||||
|
let token_pos = eat_token(input, Token::Try);
|
||||||
|
settings.pos = token_pos;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||||
|
|
||||||
|
// try { body }
|
||||||
|
let body = parse_block(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
|
// try { body } catch
|
||||||
|
let (matched, catch_pos) = match_token(input, Token::Catch);
|
||||||
|
|
||||||
|
if !matched {
|
||||||
|
return Err(
|
||||||
|
PERR::MissingToken(Token::Catch.into(), "for the 'try' statement".into())
|
||||||
|
.into_err(catch_pos),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// try { body } catch (
|
||||||
|
let var_def = if match_token(input, Token::LeftParen).0 {
|
||||||
|
let id = match input.next().unwrap() {
|
||||||
|
(Token::Identifier(s), pos) => (s, pos),
|
||||||
|
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (matched, pos) = match_token(input, Token::RightParen);
|
||||||
|
|
||||||
|
if !matched {
|
||||||
|
return Err(PERR::MissingToken(
|
||||||
|
Token::RightParen.into(),
|
||||||
|
"to enclose the catch variable".into(),
|
||||||
|
)
|
||||||
|
.into_err(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// try { body } catch ( var ) { catch_block }
|
||||||
|
let catch_body = parse_block(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
|
Ok(Stmt::TryCatch(Box::new((
|
||||||
|
(body, token_pos),
|
||||||
|
var_def,
|
||||||
|
(catch_body, catch_pos),
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a function definition.
|
/// Parse a function definition.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
fn parse_fn(
|
fn parse_fn(
|
||||||
@ -3364,7 +3437,7 @@ fn parse_fn(
|
|||||||
|
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
if !match_token(input, Token::RightParen)? {
|
if !match_token(input, Token::RightParen).0 {
|
||||||
let sep_err = format!("to separate the parameters of function '{}'", name);
|
let sep_err = format!("to separate the parameters of function '{}'", name);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@ -3514,7 +3587,7 @@ fn parse_anon_fn(
|
|||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
if input.next().unwrap().0 != Token::Or {
|
if input.next().unwrap().0 != Token::Or {
|
||||||
if !match_token(input, Token::Pipe)? {
|
if !match_token(input, Token::Pipe).0 {
|
||||||
loop {
|
loop {
|
||||||
match input.next().unwrap() {
|
match input.next().unwrap() {
|
||||||
(Token::Pipe, _) => break,
|
(Token::Pipe, _) => break,
|
||||||
|
14
src/token.rs
14
src/token.rs
@ -282,6 +282,10 @@ pub enum Token {
|
|||||||
Return,
|
Return,
|
||||||
/// `throw`
|
/// `throw`
|
||||||
Throw,
|
Throw,
|
||||||
|
/// `try`
|
||||||
|
Try,
|
||||||
|
/// `catch`
|
||||||
|
Catch,
|
||||||
/// `+=`
|
/// `+=`
|
||||||
PlusAssign,
|
PlusAssign,
|
||||||
/// `-=`
|
/// `-=`
|
||||||
@ -397,6 +401,8 @@ impl Token {
|
|||||||
Break => "break",
|
Break => "break",
|
||||||
Return => "return",
|
Return => "return",
|
||||||
Throw => "throw",
|
Throw => "throw",
|
||||||
|
Try => "try",
|
||||||
|
Catch => "catch",
|
||||||
PlusAssign => "+=",
|
PlusAssign => "+=",
|
||||||
MinusAssign => "-=",
|
MinusAssign => "-=",
|
||||||
MultiplyAssign => "*=",
|
MultiplyAssign => "*=",
|
||||||
@ -479,6 +485,8 @@ impl Token {
|
|||||||
"break" => Break,
|
"break" => Break,
|
||||||
"return" => Return,
|
"return" => Return,
|
||||||
"throw" => Throw,
|
"throw" => Throw,
|
||||||
|
"try" => Try,
|
||||||
|
"catch" => Catch,
|
||||||
"+=" => PlusAssign,
|
"+=" => PlusAssign,
|
||||||
"-=" => MinusAssign,
|
"-=" => MinusAssign,
|
||||||
"*=" => MultiplyAssign,
|
"*=" => MultiplyAssign,
|
||||||
@ -516,9 +524,9 @@ impl Token {
|
|||||||
|
|
||||||
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public"
|
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public"
|
||||||
| "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with"
|
| "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with"
|
||||||
| "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try"
|
| "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case"
|
||||||
| "catch" | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async"
|
| "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" | "await"
|
||||||
| "await" | "yield" => Reserved(syntax.into()),
|
| "yield" => Reserved(syntax.into()),
|
||||||
|
|
||||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR
|
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_throw() {
|
fn test_throw() {
|
||||||
@ -14,3 +14,27 @@ fn test_throw() {
|
|||||||
EvalAltResult::ErrorRuntime(s, _) if s.is::<()>()
|
EvalAltResult::ErrorRuntime(s, _) if s.is::<()>()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_try_catch() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>("try { throw 42; } catch (x) { return x; }")?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>("try { throw 42; } catch { return 123; }")?,
|
||||||
|
123
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
*engine
|
||||||
|
.eval::<()>("try { 42/0; } catch { throw; }")
|
||||||
|
.expect_err("expects error"),
|
||||||
|
EvalAltResult::ErrorArithmetic(_, _)
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user