Skip to content

Commit

Permalink
* Implemented feature #181 "Allow catching aborts".
Browse files Browse the repository at this point in the history
  Syntax changes for UCC:
  Added "throw" command, an alias to "abort" which should be used whenever
  it makes sense. Both forms accept an optional parameter which is passed
  to the handler; if no parameter is specified, Exult will send the text
  "abort executed" for compatibility with the original games. Examples:
    abort;                   // same as abort "abort executed";
    throw;                   // same as abort;
    throw 1+1;
    abort "Error message";
    throw varname;           // varname must be a variable or function
    throw UI_get_random(18);
    abort [1, 2, 3];

  To handle (catch) an abort/throw, use the following syntax:
    try {
        // code that can generate an abort/throw
    } catch (optvarname) {
        // code that handles the abort/throw
    }
    // Code that gets executed afterwards

  optvarname is an optional variable name. If present, a variable will be
  created with this name which will receive the abort/throw's parameter;
  otherwise, that parameter will be discarded.

  Note: any class variables declared and constructed in the "try" block may
  leak if you don't delete it before the abort/throw happens. I have made no
  effort to handle this case.
  • Loading branch information
marzojr committed Jul 16, 2016
1 parent 85626ec commit b4c305f
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 17 deletions.
31 changes: 31 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
2016-07-16 Marzo Sette Torres Junior <marzojr@yahoo.com>
* Implemented feature #181 "Allow catching aborts".
Syntax changes for UCC:
Added "throw" command, an alias to "abort" which should be used whenever
it makes sense. Both forms accept an optional parameter which is passed
to the handler; if no parameter is specified, Exult will send the text
"abort executed" for compatibility with the original games. Examples:
abort; // same as abort "abort executed";
throw; // same as abort;
throw 1+1;
abort "Error message";
throw varname; // varname must be a variable or function
throw UI_get_random(18);
abort [1, 2, 3];

To handle (catch) an abort/throw, use the following syntax:
try {
// code that can generate an abort/throw
} catch (optvarname) {
// code that handles the abort/throw
}
// Code that gets executed afterwards

optvarname is an optional variable name. If present, a variable will be
created with this name which will receive the abort/throw's parameter;
otherwise, that parameter will be discarded.

Note: any class variables declared and constructed in the "try" block may
leak if you don't delete it before the abort/throw happens. I have made no
effort to handle this case.

2016-06-27 Marzo Sette Torres Junior <marzojr@yahoo.com>
* Fixed bug #1958 "Studio: Selecting mod of a game type doesn't work if there
are two same game".
Expand Down
1 change: 1 addition & 0 deletions content/bgkeyring/src/items/misc.uc
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,4 @@ void TripleCrossbow shape#(0x287) ()
}
}
}

7 changes: 5 additions & 2 deletions usecode/compiler/basic_block.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,11 @@ class Opcode {
case UC_CONVERSE:
case UC_JMP:
case UC_JNE:
case UC_TRYSTART:
case UC_CONVERSE32:
case UC_JMP32:
case UC_JNE32:
case UC_TRYSTART32:
is_jump = true;
break;
case UC_CALLINDEX:
Expand Down Expand Up @@ -181,7 +183,7 @@ class Opcode {
|| opcode == UC_RETV || opcode == UC_RETZ;
}
bool is_abort() const {
return opcode == UC_ABRT;
return opcode == UC_ABRT || opcode == UC_THROW;
}
};

Expand Down Expand Up @@ -226,7 +228,8 @@ class Basic_block {
Opcode *jmp_op; // 0 for no instruction (fall-through) to taken
// block; otherwise, one of:
// conditional jumps:
// UC_JNE, UC_CMPS, UC_CONVERSE, UC_LOOPTOP, UC_LOOPTOPS, UC_LOOPTOPTHV
// UC_JNE, UC_CMPS, UC_CONVERSE, UC_LOOPTOP, UC_LOOPTOPS, UC_LOOPTOPTHV,
// UC_TRYSTART
// unconditional jumps:
// UC_JMP
// or the 32-bit versions of these instructions.
Expand Down
60 changes: 60 additions & 0 deletions usecode/compiler/test1.uc
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,63 @@ var adder (a, b)
return a + b;
}
*/

enum party_members
{
PARTY = -357, //Used by several intrinsics (e.g. UI_count_objects) that would otherwise take a single NPC
//Not supported by several other intrinsics that you'd really like it to (e.g. UI_get_cont_items)
AVATAR = -356,
IOLO = -1,
SPARK = -2,
SHAMINO = -3,
DUPRE = -4,
JAANA = -5,
SENTRI = -7,
JULIA = -8,
KATRINA = -9,
TSERAMED = -10
};

var throw_abrt_test(var flag) {
if (flag == 0) {
abort;
} else if (flag == 1) {
try {
throw_abrt_test(0);
} catch (errmsg) {
AVATAR.say(errmsg);
throw flag;
}
} else {
throw flag;
}
return 1;
}

void try_catch_test()
{
if (event == DOUBLECLICK)
{
try {
throw_abrt_test(0);
} catch () {
DUPRE.say("Aha!");
}
try {
throw_abrt_test(0);
} catch (errmsg) {
IOLO.say(errmsg);
}
try {
throw_abrt_test(1);
} catch (errmsg) {
SHAMINO.say(errmsg);
}
try {
throw_abrt_test("Test");
} catch (errmsg) {
SPARK.say(errmsg);
}
throw_abrt_test("Game over");
}
}
3 changes: 3 additions & 0 deletions usecode/compiler/uclex.ll
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,10 @@ event return EVENT;
gflags return FLAG;
item return ITEM;
goto return GOTO;
try return TRY;
catch return CATCH;
abort return ABORT;
throw return THROW;
".original" return ORIGINAL;
<fun_id>{
"shape#" return SHAPENUM;
Expand Down
43 changes: 39 additions & 4 deletions usecode/compiler/ucparse.yy
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ struct Member_selector
%token IF ELSE RETURN DO WHILE FOR UCC_IN WITH TO EXTERN BREAK GOTO CASE
%token VAR VOID ALIAS STRUCT UCC_CHAR UCC_INT UCC_LONG UCC_CONST STRING ENUM
%token CONVERSE NESTED SAY MESSAGE RESPONSE EVENT FLAG ITEM UCTRUE UCFALSE REMOVE
%token ADD HIDE SCRIPT AFTER TICKS STATIC_ ORIGINAL SHAPENUM OBJECTNUM ABORT
%token ADD HIDE SCRIPT AFTER TICKS STATIC_ ORIGINAL SHAPENUM OBJECTNUM
%token CLASS NEW DELETE RUNSCRIPT UCC_INSERT SWITCH DEFAULT
%token ADD_EQ SUB_EQ MUL_EQ DIV_EQ MOD_EQ CHOICE
%token ADD_EQ SUB_EQ MUL_EQ DIV_EQ MOD_EQ CHOICE TRY CATCH ABORT THROW

/*
* Script keywords:
Expand Down Expand Up @@ -201,7 +201,7 @@ struct Member_selector
%type <varvec> param_list opt_param_list
%type <stmt> statement assignment_statement if_statement while_statement
%type <stmt> statement_block return_statement function_call_statement
%type <stmt> special_method_call_statement
%type <stmt> special_method_call_statement trycatch_statement trystart_statement
%type <stmt> array_loop_statement var_decl var_decl_list stmt_declaration
%type <stmt> class_decl class_decl_list struct_decl_list struct_decl
%type <stmt> break_statement converse_statement
Expand Down Expand Up @@ -495,6 +495,7 @@ statement:
stmt_declaration
| assignment_statement
| if_statement
| trycatch_statement
| while_statement
| array_loop_statement
| function_call_statement
Expand All @@ -513,12 +514,19 @@ statement:
| MESSAGE '(' opt_nonclass_expr_list ')' ';'
{ $$ = new Uc_message_statement($3); }
| answer_statement
| ABORT ';'
| throwabort_statement ';'
{ $$ = new Uc_abort_statement(); }
| throwabort_statement expression ';'
{ $$ = new Uc_abort_statement($2); }
| ';' /* Null statement */
{ $$ = 0; }
;

throwabort_statement:
ABORT
| THROW
;

alias_tok:
ALIAS
| '&'
Expand Down Expand Up @@ -1014,6 +1022,33 @@ if_statement:
}
;

trycatch_statement:
trystart_statement '{' statement_list '}'
{
Uc_trycatch_statement *stmt = dynamic_cast<Uc_trycatch_statement*>($1);
if (!stmt) {
yyerror("try/catch statement is not a try/catch statement");
}
stmt->set_catch_statement($3);
$$ = stmt;
}
;

trystart_statement:
TRY '{' statement_list '}' CATCH '(' ')'
{
cur_fun->push_scope();
$$ = new Uc_trycatch_statement($3);
}
| TRY '{' statement_list '}' CATCH '(' IDENTIFIER ')'
{
cur_fun->push_scope();
Uc_trycatch_statement *stmt = new Uc_trycatch_statement($3);
stmt->set_catch_variable(cur_fun->add_symbol($7));
$$ = stmt;
}
;

while_statement:
WHILE '(' nonclass_expr ')' { start_loop(); } statement
{
Expand Down
77 changes: 76 additions & 1 deletion usecode/compiler/ucstmt.cc
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,67 @@ Uc_if_statement::~Uc_if_statement(
delete else_stmt;
}

/*
* Generate code.
*/

void Uc_trycatch_statement::gen(
Uc_function *fun,
vector<Basic_block *> &blocks, // What we are producing.
Basic_block *&curr, // Active block; will usually be *changed*.
Basic_block *end, // Fictitious exit block for function.
map<string, Basic_block *> &labels, // Label map for goto statements.
Basic_block *start, // Block used for 'continue' statements.
Basic_block *exit // Block used for 'break' statements.
) {
static int cnt = 0;
if (!try_stmt) // Optimize whole block away.
return;
// The basic block for the try code.
Basic_block *try_block = new Basic_block();
// The basic block for the catch code.
Basic_block *catch_block = new Basic_block();
// The basic block after the try/catch blocks.
Basic_block *past_trycatch = new Basic_block();
// Gen start opcode for try block.
curr->set_targets(UC_TRYSTART, try_block, catch_block);
// Generate code for try block
blocks.push_back(try_block);
try_stmt->gen(fun, blocks, try_block, end, labels, start, exit);
WriteOp(try_block, UC_TRYEND);
// JMP past CATCH code.
try_block->set_targets(UC_JMP, past_trycatch);
// Generate a temp variable for error if needed
if (!catch_var) {
char buf[50];
sprintf(buf, "_tmperror_%d", cnt++);
// Create a 'tmp' variable.
catch_var = fun->add_symbol(buf);
assert(catch_var != 0);
}
// Generate error variable assignment (push is handled by abort/throw)
blocks.push_back(catch_block);
catch_var->gen_assign(catch_block);
// Do we have anything else to generate on catch block?
if (catch_stmt) {
// Generate catch code.
catch_stmt->gen(fun, blocks, catch_block, end, labels, start, exit);
catch_block->set_taken(past_trycatch);
}
blocks.push_back(curr = past_trycatch);
}

/*
* Delete.
*/

Uc_trycatch_statement::~Uc_trycatch_statement(
) {
delete catch_var;
delete try_stmt;
delete catch_stmt;
}

/*
* Delete.
*/
Expand Down Expand Up @@ -980,12 +1041,26 @@ void Uc_abort_statement::gen(
Basic_block *exit // Block used for 'break' statements.
) {
ignore_unused_variable_warning(fun, labels, start, exit);
WriteOp(curr, UC_ABRT);
if (expr) {
expr->gen_value(curr);
WriteOp(curr, UC_THROW);
} else {
WriteOp(curr, UC_ABRT);
}
curr->set_targets(UC_INVALID, end);
curr = new Basic_block();
blocks.push_back(curr);
}

/*
* Delete.
*/

Uc_abort_statement::~Uc_abort_statement(
) {
delete expr;
}

/*
* Generate code.
*/
Expand Down
28 changes: 27 additions & 1 deletion usecode/compiler/ucstmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,30 @@ class Uc_if_statement : public Uc_statement {
Basic_block *start = 0, Basic_block *exit = 0);
};

/*
* A try/catch statement:
*/
class Uc_trycatch_statement : public Uc_statement {
Uc_var_symbol *catch_var;
Uc_statement *try_stmt, *catch_stmt;
public:
Uc_trycatch_statement(Uc_statement *t)
: catch_var(0), try_stmt(t), catch_stmt(0)
{ }
void set_catch_variable(Uc_var_symbol *v) {
catch_var = v;
}
void set_catch_statement(Uc_statement *s) {
catch_stmt = s;
}
~Uc_trycatch_statement();
// Generate code.
virtual void gen(Uc_function *fun, std::vector<Basic_block *> &blocks,
Basic_block *&curr, Basic_block *end,
std::map<std::string, Basic_block *> &labels,
Basic_block *start = 0, Basic_block *exit = 0);
};

/*
* A generic breakable statement:
*/
Expand Down Expand Up @@ -458,9 +482,11 @@ class Uc_call_statement : public Uc_statement {
* Statement that represents an abort opcode.
*/
class Uc_abort_statement : public Uc_statement {
Uc_expression *expr;
public:
Uc_abort_statement()
Uc_abort_statement(Uc_expression *e = 0) : expr(e)
{ }
~Uc_abort_statement();
// Generate code.
virtual void gen(Uc_function *fun, std::vector<Basic_block *> &blocks,
Basic_block *&curr, Basic_block *end,
Expand Down
6 changes: 5 additions & 1 deletion usecode/opcodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ enum UsecodeOps {
UC_CALLI = 0x39,
UC_PUSHITEMREF = 0x3e,
UC_ABRT = 0x3f,
UC_THROW = 0xbf, // Like abrt, but accepts an expression to be sent up.
UC_CONVERSELOC = 0x40, // CONVERSE jmps here.
UC_PUSHF = 0x42, // PUSH global flag.
UC_POPF = 0x43, // POP global flag.
Expand Down Expand Up @@ -81,6 +82,8 @@ enum UsecodeOps {
UC_POPARRTHV = 0x5e, // Pop this->var array elem.
UC_LOOPTOPTHV = 0x5f, // Loop with this->var array.
UC_PUSHCHOICE = 0x60, // Pushes last selected user choice.
UC_TRYSTART = 0x61, // TRY/CATCH block start.
UC_TRYEND = 0x62, // TRY/CATCH block end.
UC_PUSHFVAR = 0xc2, // PUSH global flag using stack value.
UC_POPFVAR = 0xc3, // POP global flag using stack value.
UC_CALLINDEX_OLD = 0xd3, // Call indirect; UCC never emits this.
Expand All @@ -101,7 +104,8 @@ enum UsecodeOps {
UC_CALLE32 = 0xc7,
UC_DBGFUNC32 = 0xcd, // 32-bit version of SI debug opcode; UCC never emits this.
UC_LOOPTOPS32 = 0xdc, // 32-bit loop with static array.
UC_LOOPTOPTHV32 = 0xdf // 32-bit loop with this->var array.
UC_LOOPTOPTHV32 = 0xdf, // 32-bit loop with this->var array.
UC_TRYSTART32 = 0xe1, // TRY/CATCH block, 32-bit version.
};

inline UsecodeOps &operator|=(UsecodeOps &lhs, int rhs) {
Expand Down
Loading

0 comments on commit b4c305f

Please sign in to comment.