From f9feb4187789fa3119b40009a2e9d5e1e6d07262 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Mon, 12 Jun 2017 09:12:43 +0200 Subject: [PATCH] Implement support for handling exceptions in user scripts --- doc/17-language-reference.md | 33 ++++++++++++++++++++++++++++----- lib/base/configwriter.cpp | 2 ++ lib/config/config_lexer.ll | 2 ++ lib/config/config_parser.yy | 6 ++++++ lib/config/expression.cpp | 13 +++++++++++++ lib/config/expression.hpp | 21 +++++++++++++++++++++ 6 files changed, 72 insertions(+), 5 deletions(-) diff --git a/doc/17-language-reference.md b/doc/17-language-reference.md index 7aa58bbe8..adc20520f 100644 --- a/doc/17-language-reference.md +++ b/doc/17-language-reference.md @@ -851,7 +851,7 @@ Example: var s = String(3) /* Sets s to "3". */ -## Exceptions +## Throwing Exceptions Built-in commands may throw exceptions to signal errors such as invalid arguments. User scripts can throw exceptions using the `throw` keyword. @@ -860,7 +860,20 @@ Example: throw "An error occurred." -There is currently no way for scripts to catch exceptions. +## Handling Exceptions + +Exceptions can be handled using the `try` and `except` keywords. When an exception occurs while executing code in the +`try` clause no further statements in the `try` clause are evaluated and the `except` clause is executed instead. + +Example: + + try { + throw "Test" + + log("This statement won't get executed.") + } except { + log("An error occurred in the try clause.") + } ## Breakpoints @@ -920,7 +933,7 @@ These keywords are reserved and must not be used as constants or custom attribut template include include_recursive - ignore_on_error + include_zones library null true @@ -928,7 +941,13 @@ These keywords are reserved and must not be used as constants or custom attribut const var this + globals + locals use + default + ignore_on_error + current_filename + current_line apply to where @@ -937,12 +956,16 @@ These keywords are reserved and must not be used as constants or custom attribut ignore function return + break + continue for if else + while + throw + try + except in - current_filename - current_line You can escape reserved keywords using the `@` character. The following example tries to set `vars.include` which references a reserved keyword and generates diff --git a/lib/base/configwriter.cpp b/lib/base/configwriter.cpp index 75d0752b5..05ddb30aa 100644 --- a/lib/base/configwriter.cpp +++ b/lib/base/configwriter.cpp @@ -262,6 +262,8 @@ const std::vector& ConfigWriter::GetKeywords(void) keywords.push_back("else"); keywords.push_back("while"); keywords.push_back("throw"); + keywords.push_back("try"); + keywords.push_back("except"); } return keywords; diff --git a/lib/config/config_lexer.ll b/lib/config/config_lexer.ll index 920e13d2e..d636b811f 100644 --- a/lib/config/config_lexer.ll +++ b/lib/config/config_lexer.ll @@ -197,6 +197,8 @@ if return T_IF; else return T_ELSE; while return T_WHILE; throw return T_THROW; +try return T_TRY; +except return T_EXCEPT; ignore_on_error return T_IGNORE_ON_ERROR; current_filename return T_CURRENT_FILENAME; current_line return T_CURRENT_LINE; diff --git a/lib/config/config_parser.yy b/lib/config/config_parser.yy index a02bf2915..36f972410 100644 --- a/lib/config/config_parser.yy +++ b/lib/config/config_parser.yy @@ -172,6 +172,8 @@ static void MakeRBinaryOp(Expression** result, Expression *left, Expression *rig %token T_ELSE "else (T_ELSE)" %token T_WHILE "while (T_WHILE)" %token T_THROW "throw (T_THROW)" +%token T_TRY "try (T_TRY)" +%token T_EXCEPT "except (T_EXCEPT)" %token T_FOLLOWS "=> (T_FOLLOWS)" %token T_NULLARY_LAMBDA_BEGIN "{{ (T_NULLARY_LAMBDA_BEGIN)" %token T_NULLARY_LAMBDA_END "}} (T_NULLARY_LAMBDA_END)" @@ -666,6 +668,10 @@ lterm: T_LIBRARY rterm { $$ = new ThrowExpression($2, false, @$); } + | T_TRY rterm_scope T_EXCEPT rterm_scope + { + $$ = new TryExceptExpression($2, $4, @$); + } | rterm_side_effect ; diff --git a/lib/config/expression.cpp b/lib/config/expression.cpp index b0e1b6cad..3f95435a0 100644 --- a/lib/config/expression.cpp +++ b/lib/config/expression.cpp @@ -941,3 +941,16 @@ ExpressionResult UsingExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhin return Empty; } +ExpressionResult TryExceptExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + try { + ExpressionResult tryResult = m_TryBody->Evaluate(frame, dhint); + CHECK_RESULT(tryResult); + } catch (const std::exception& ex) { + ExpressionResult exceptResult = m_ExceptBody->Evaluate(frame, dhint); + CHECK_RESULT(exceptResult); + } + + return Empty; +} + diff --git a/lib/config/expression.hpp b/lib/config/expression.hpp index a4c46dc06..bc90ef481 100644 --- a/lib/config/expression.hpp +++ b/lib/config/expression.hpp @@ -1027,6 +1027,27 @@ private: Expression *m_Name; }; +class I2_CONFIG_API TryExceptExpression : public DebuggableExpression +{ +public: + TryExceptExpression(Expression *tryBody, Expression *exceptBody, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_TryBody(tryBody), m_ExceptBody(exceptBody) + { } + + ~TryExceptExpression(void) + { + delete m_TryBody; + delete m_ExceptBody; + } + +protected: + virtual ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + Expression *m_TryBody; + Expression *m_ExceptBody; +}; + } #endif /* EXPRESSION_H */