Add try-catch.

This commit is contained in:
Stephen Chung
2020-10-20 23:16:03 +08:00
parent 5ee9dfc5cd
commit 07bdb824fe
12 changed files with 343 additions and 35 deletions

View File

@@ -1932,6 +1932,63 @@ impl Engine {
// Break statement
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
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
let expr = x.1.as_ref().unwrap();

View File

@@ -104,7 +104,7 @@ pub enum ParseErrorType {
///
/// Never appears under the `no_object` feature.
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,
/// An identifier is a reserved keyword.
Reserved(String),

View File

@@ -268,7 +268,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
))),
// let id;
stmt @ Stmt::Let(_) => stmt,
// import expr as id;
// import expr as var;
#[cfg(not(feature = "no_module"))]
Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1, x.2))),
// { block }
@@ -389,6 +389,22 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
_ => 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;
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
// return expr;

View File

@@ -758,6 +758,14 @@ pub enum Stmt {
Const(Box<((String, Position), Option<Expr>, Position)>),
/// { stmt; ... }
Block(Box<(StaticVec<Stmt>, Position)>),
/// try { stmt; ... } catch ( var ) { stmt; ... }
TryCatch(
Box<(
(Stmt, Position),
Option<(String, Position)>,
(Stmt, Position),
)>,
),
/// expr
Expr(Box<Expr>),
/// continue
@@ -766,10 +774,10 @@ pub enum Stmt {
Break(Position),
/// return/throw
ReturnWithVal(Box<((ReturnType, Position), Option<Expr>, Position)>),
/// import expr as module
/// import expr as var
#[cfg(not(feature = "no_module"))]
Import(Box<(Expr, Option<(ImmutableString, Position)>, Position)>),
/// expr id as name, ...
/// export var as var, ...
#[cfg(not(feature = "no_module"))]
Export(
Box<(
@@ -796,13 +804,14 @@ impl Stmt {
Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos,
Stmt::Let(x) => (x.0).1,
Stmt::Const(x) => (x.0).1,
Stmt::ReturnWithVal(x) => (x.0).1,
Stmt::Block(x) => x.1,
Stmt::IfThenElse(x) => x.3,
Stmt::Expr(x) => x.position(),
Stmt::While(x) => x.2,
Stmt::Loop(x) => x.1,
Stmt::For(x) => x.3,
Stmt::ReturnWithVal(x) => (x.0).1,
Stmt::TryCatch(x) => (x.0).1,
#[cfg(not(feature = "no_module"))]
Stmt::Import(x) => x.2,
@@ -820,7 +829,6 @@ impl Stmt {
Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos = new_pos,
Stmt::Let(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::IfThenElse(x) => x.3 = new_pos,
Stmt::Expr(x) => {
@@ -829,6 +837,8 @@ impl Stmt {
Stmt::While(x) => x.2 = new_pos,
Stmt::Loop(x) => x.1 = 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"))]
Stmt::Import(x) => x.2 = new_pos,
@@ -849,7 +859,8 @@ impl Stmt {
| Stmt::While(_)
| Stmt::Loop(_)
| 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!
Stmt::Noop(_) => false,
@@ -884,6 +895,7 @@ impl Stmt {
Stmt::Let(_) | Stmt::Const(_) => false,
Stmt::Block(x) => x.0.iter().all(Stmt::is_pure),
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"))]
Stmt::Import(_) => false,
@@ -1358,13 +1370,12 @@ fn eat_token(input: &mut TokenStream, token: Token) -> Position {
}
/// Match a particular token, consuming it if matched.
fn match_token(input: &mut TokenStream, token: Token) -> Result<bool, ParseError> {
let (t, _) = input.peek().unwrap();
fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) {
let (t, pos) = input.peek().unwrap();
if *t == token {
eat_token(input, token);
Ok(true)
(true, eat_token(input, token))
} else {
Ok(false)
(false, *pos)
}
}
@@ -1378,7 +1389,7 @@ fn parse_paren_expr(
#[cfg(not(feature = "unchecked"))]
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));
}
@@ -1989,7 +2000,7 @@ fn parse_primary(
// Qualified function call with !
#[cfg(not(feature = "no_closure"))]
(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)
} else {
PERR::BadInput("'!' cannot be used to call module functions".to_string())
@@ -1999,12 +2010,13 @@ fn parse_primary(
// Function call with !
#[cfg(not(feature = "no_closure"))]
(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(
Token::LeftParen.syntax().into(),
"to start arguments list of function call".into(),
)
.into_err(input.peek().unwrap().1));
.into_err(pos));
}
let ((name, pos), modules, _, _) = *x;
@@ -2813,7 +2825,7 @@ fn parse_if(
let if_body = parse_block(input, state, lib, settings.level_up())?;
// 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() {
// if guard { if_body } else if ...
parse_if(input, state, lib, settings.level_up())?
@@ -2957,7 +2969,7 @@ fn parse_let(
};
// let name = ...
let init_value = if match_token(input, Token::Equals)? {
let init_value = if match_token(input, Token::Equals).0 {
// let name = expr
Some(parse_expr(input, state, lib, settings.level_up())?)
} else {
@@ -2997,7 +3009,7 @@ fn parse_import(
let expr = parse_expr(input, state, lib, settings.level_up())?;
// 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))));
}
@@ -3046,7 +3058,7 @@ fn parse_export(
(_, 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() {
(Token::Identifier(s), pos) => Some((s.clone(), pos)),
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
@@ -3121,7 +3133,7 @@ fn parse_block(
#[cfg(not(feature = "no_module"))]
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
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::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.
#[cfg(not(feature = "no_function"))]
fn parse_fn(
@@ -3364,7 +3437,7 @@ fn parse_fn(
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);
loop {
@@ -3514,7 +3587,7 @@ fn parse_anon_fn(
let mut params = Vec::new();
if input.next().unwrap().0 != Token::Or {
if !match_token(input, Token::Pipe)? {
if !match_token(input, Token::Pipe).0 {
loop {
match input.next().unwrap() {
(Token::Pipe, _) => break,

View File

@@ -282,6 +282,10 @@ pub enum Token {
Return,
/// `throw`
Throw,
/// `try`
Try,
/// `catch`
Catch,
/// `+=`
PlusAssign,
/// `-=`
@@ -397,6 +401,8 @@ impl Token {
Break => "break",
Return => "return",
Throw => "throw",
Try => "try",
Catch => "catch",
PlusAssign => "+=",
MinusAssign => "-=",
MultiplyAssign => "*=",
@@ -479,6 +485,8 @@ impl Token {
"break" => Break,
"return" => Return,
"throw" => Throw,
"try" => Try,
"catch" => Catch,
"+=" => PlusAssign,
"-=" => MinusAssign,
"*=" => MultiplyAssign,
@@ -516,9 +524,9 @@ impl Token {
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public"
| "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with"
| "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try"
| "catch" | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async"
| "await" | "yield" => Reserved(syntax.into()),
| "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case"
| "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" | "await"
| "yield" => Reserved(syntax.into()),
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR