Add throw.

This commit is contained in:
Stephen Chung 2020-03-03 18:15:20 +08:00
parent e2cb111e4b
commit 9f80bf03c4
4 changed files with 102 additions and 11 deletions

View File

@ -563,6 +563,40 @@ fn do_addition(x) {
}
```
## Return
```rust
return;
return 123 + 456;
```
## Errors and Exceptions
```rust
if error != "" {
throw error; // `throw` takes a string to form the exception text
}
throw; // no exception text
```
All of `Engine`'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult` holding error information.
Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(reason, position))` with the exception text captured by the `reason` parameter.
```rust
let result = engine.eval::<i64>(&mut scope, r#"
let x = 42;
if x > 0 {
throw x + " is too large!";
}
"#);
println!(result); // prints "Runtime error: 42 is too large! (line 5, position 15)"
```
## Arrays
You can create arrays of values, and then access them with numeric indices.

View File

@ -35,6 +35,7 @@ pub enum EvalAltResult {
ErrorCantOpenScriptFile(String, std::io::Error),
ErrorDotExpr(Position),
ErrorArithmetic(String, Position),
ErrorRuntime(String, Position),
LoopBreak,
Return(Dynamic, Position),
}
@ -70,6 +71,7 @@ impl Error for EvalAltResult {
Self::ErrorCantOpenScriptFile(_, _) => "Cannot open script file",
Self::ErrorDotExpr(_) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorRuntime(_, _) => "Runtime error",
Self::LoopBreak => "[Not Error] Breaks out of loop",
Self::Return(_, _) => "[Not Error] Function returns value",
}
@ -95,6 +97,8 @@ impl std::fmt::Display for EvalAltResult {
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::ErrorDotExpr(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorArithmetic(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::ErrorRuntime(s, pos) if s.is_empty() => write!(f, "{} ({})", desc, pos),
Self::ErrorRuntime(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::LoopBreak => write!(f, "{}", desc),
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorCantOpenScriptFile(filename, err) => {
@ -879,11 +883,31 @@ impl Engine {
Stmt::Break(_) => Err(EvalAltResult::LoopBreak),
Stmt::Return(pos) => Err(EvalAltResult::Return(().into_dynamic(), *pos)),
// Empty return
Stmt::ReturnWithVal(None, true, pos) => {
Err(EvalAltResult::Return(().into_dynamic(), *pos))
}
Stmt::ReturnWithVal(a, pos) => {
let result = self.eval_expr(scope, a)?;
Err(EvalAltResult::Return(result, *pos))
// Return value
Stmt::ReturnWithVal(Some(a), true, pos) => {
Err(EvalAltResult::Return(self.eval_expr(scope, a)?, *pos))
}
// Empty throw
Stmt::ReturnWithVal(None, false, pos) => {
Err(EvalAltResult::ErrorRuntime("".into(), *pos))
}
// Throw value
Stmt::ReturnWithVal(Some(a), false, pos) => {
let val = self.eval_expr(scope, a)?;
Err(EvalAltResult::ErrorRuntime(
(val.downcast_ref() as Option<&String>)
.map(|s| s.as_ref())
.unwrap_or("")
.to_string(),
*pos,
))
}
Stmt::Let(name, init, _) => {

View File

@ -222,8 +222,7 @@ pub enum Stmt {
Block(Vec<Stmt>),
Expr(Box<Expr>),
Break(Position),
Return(Position),
ReturnWithVal(Box<Expr>, Position),
ReturnWithVal(Option<Box<Expr>>, bool, Position),
}
#[derive(Debug, Clone)]
@ -310,6 +309,7 @@ pub enum Token {
Fn,
Break,
Return,
Throw,
PlusAssign,
MinusAssign,
MultiplyAssign,
@ -382,6 +382,7 @@ impl Token {
Fn => "fn",
Break => "break",
Return => "return",
Throw => "throw",
PlusAssign => "+=",
MinusAssign => "-=",
MultiplyAssign => "*=",
@ -456,6 +457,7 @@ impl Token {
Modulo |
ModuloAssign |
Return |
Throw |
PowerOf |
In |
PowerOfAssign => true,
@ -498,7 +500,7 @@ impl Token {
use self::Token::*;
match *self {
UnaryPlus | UnaryMinus | Equals | Bang | Return => true,
UnaryPlus | UnaryMinus | Equals | Bang | Return | Throw => true,
_ => false,
}
}
@ -816,6 +818,7 @@ impl<'a> TokenIterator<'a> {
"loop" => Token::Loop,
"break" => Token::Break,
"return" => Token::Return,
"throw" => Token::Throw,
"fn" => Token::Fn,
"for" => Token::For,
"in" => Token::In,
@ -1743,6 +1746,7 @@ fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
// Parse statements inside the block
statements.push(parse_stmt(input)?);
// Notice semicolons are optional
if let Some(&(Token::SemiColon, _)) = input.peek() {
input.next();
}
@ -1778,15 +1782,25 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
input.next();
Ok(Stmt::Break(pos))
}
Some(&(Token::Return, _)) => {
Some(&(ref token @ Token::Return, _)) | Some(&(ref token @ Token::Throw, _)) => {
let is_return = match token {
Token::Return => true,
Token::Throw => false,
_ => panic!(),
};
input.next();
match input.peek() {
Some(&(Token::SemiColon, pos)) => Ok(Stmt::Return(pos)),
// return; or throw;
Some(&(Token::SemiColon, pos)) => Ok(Stmt::ReturnWithVal(None, is_return, pos)),
// Just a return/throw without anything at the end of script
None => Ok(Stmt::ReturnWithVal(None, is_return, Position::eof())),
// return or throw with expression
Some(&(_, pos)) => {
let ret = parse_expr(input)?;
Ok(Stmt::ReturnWithVal(Box::new(ret), pos))
Ok(Stmt::ReturnWithVal(Some(Box::new(ret)), is_return, pos))
}
_ => parse_expr_stmt(input),
}
}
Some(&(Token::LeftBrace, _)) => parse_block(input),
@ -1859,6 +1873,7 @@ fn parse_top_level<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<AST, P
_ => statements.push(parse_stmt(input)?),
}
// Notice semicolons are optional
if let Some(&(Token::SemiColon, _)) = input.peek() {
input.next();
}

18
tests/throw.rs Normal file
View File

@ -0,0 +1,18 @@
use rhai::{Engine, EvalAltResult};
#[test]
fn test_throw() {
let mut engine = Engine::new();
match engine.eval::<i64>(r#"if true { throw "hello" }"#) {
Ok(_) => panic!("not an error"),
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "hello" => (),
Err(err) => panic!("wrong error: {}", err),
}
match engine.eval::<i64>(r#"throw;"#) {
Ok(_) => panic!("not an error"),
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "" => (),
Err(err) => panic!("wrong error: {}", err),
}
}