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 ## Arrays
You can create arrays of values, and then access them with numeric indices. 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), ErrorCantOpenScriptFile(String, std::io::Error),
ErrorDotExpr(Position), ErrorDotExpr(Position),
ErrorArithmetic(String, Position), ErrorArithmetic(String, Position),
ErrorRuntime(String, Position),
LoopBreak, LoopBreak,
Return(Dynamic, Position), Return(Dynamic, Position),
} }
@ -70,6 +71,7 @@ impl Error for EvalAltResult {
Self::ErrorCantOpenScriptFile(_, _) => "Cannot open script file", Self::ErrorCantOpenScriptFile(_, _) => "Cannot open script file",
Self::ErrorDotExpr(_) => "Malformed dot expression", Self::ErrorDotExpr(_) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorRuntime(_, _) => "Runtime error",
Self::LoopBreak => "[Not Error] Breaks out of loop", Self::LoopBreak => "[Not Error] Breaks out of loop",
Self::Return(_, _) => "[Not Error] Function returns value", 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::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::ErrorDotExpr(pos) => write!(f, "{} ({})", desc, pos), Self::ErrorDotExpr(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorArithmetic(s, pos) => write!(f, "{}: {} ({})", desc, s, 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::LoopBreak => write!(f, "{}", desc),
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorCantOpenScriptFile(filename, err) => { Self::ErrorCantOpenScriptFile(filename, err) => {
@ -879,11 +883,31 @@ impl Engine {
Stmt::Break(_) => Err(EvalAltResult::LoopBreak), 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) => { // Return value
let result = self.eval_expr(scope, a)?; Stmt::ReturnWithVal(Some(a), true, pos) => {
Err(EvalAltResult::Return(result, *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, _) => { Stmt::Let(name, init, _) => {

View File

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