Skip to content

Commit

Permalink
Implements try/catch (most of #9)
Browse files Browse the repository at this point in the history
- So far basic use cases (including nested try statements) are working.
  Still needs `finally` and additional tests before #9 can be closed.
- Fixes inheritance on the native Error constructors.
  • Loading branch information
ndreynolds committed May 1, 2013
1 parent 5d67134 commit 31d4c79
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 19 deletions.
22 changes: 20 additions & 2 deletions src/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -678,14 +678,32 @@ forin_stmt(js_val *ctx, ast_node *node)
static js_val *
try_stmt(js_val *ctx, ast_node *node)
{
// TODO: Exception handling
return fh_eval(ctx, node->e1);
eval_state *state = fh_new_state(node->line, node->column);
state->catch = true;
state->ctx = ctx;
fh_push_state(state);

// Try
if (!setjmp(state->jmp))
return fh_eval(ctx, node->e1);
// Catch
else {
fh_set(ctx, node->e2->e1->sval, fh_get(ctx, "FH_LAST_ERROR"));
return fh_eval(ctx, node->e2->e2);
}

// Finally
if (node->e3)
fh_eval(ctx, node->e3);

return JSUNDEF();
}

static js_val *
throw_stmt(js_val *ctx, ast_node *exp)
{
eval_state *state = fh_new_state(exp->line, exp->column);
fh_push_state(state);
js_val *val = fh_eval(ctx, exp);
fh_throw(state, val);
return JSUNDEF();
Expand Down
21 changes: 20 additions & 1 deletion src/flathead.c
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ fh_new_state(int line, int column)
state->this = NULL;
state->parent = NULL;
state->construct = false;
state->catch = false;

return state;
}
Expand Down Expand Up @@ -478,6 +479,23 @@ fh_cast(js_val *val, js_type type)
void
fh_throw(eval_state *state, js_val *error)
{
// Search the callstack for a catch
eval_state *tmp = state;
while(tmp != NULL) {
if (tmp->catch) {
fh_set(tmp->ctx, "FH_LAST_ERROR", error);

// Pop all frames up to and including the catch.
while (fh->callstack != tmp)
fh_pop_state();
fh_pop_state();

longjmp(tmp->jmp, 1);
UNREACHABLE();
}
tmp = state->parent;
}

fprintf(stderr, "%s\n", TO_STR(fh_to_primitive(error, T_STRING))->string.ptr);

while (state != NULL) {
Expand All @@ -491,9 +509,10 @@ fh_throw(eval_state *state, js_val *error)
state = state->parent;
}

// Catch errors within REPL: clear callstack and start over.
if (fh->opt_interactive) {
fh->callstack = NULL;
longjmp(fh->jmpbuf, 1);
longjmp(fh->repl_jmp, 1);
}
exit(1);
}
Expand Down
14 changes: 10 additions & 4 deletions src/flathead.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ typedef struct {
bool opt_keep_history_file;
const char *opt_history_filename;

jmp_buf jmpbuf; // used to handle errors within REPL
jmp_buf repl_jmp; // used to handle errors within REPL
char *script_name;
struct eval_state *callstack;

Expand All @@ -161,11 +161,13 @@ typedef struct {
typedef struct eval_state {
int line;
int column;
char *caller_info;
char *script_name;
bool construct;
bool catch;
struct js_val *ctx;
struct js_val *this;
char *caller_info;
char *script_name;
jmp_buf jmp;
struct eval_state *parent;
} eval_state;

Expand Down Expand Up @@ -217,11 +219,15 @@ typedef struct {
js_native_function *nativefn;
} js_object;

/* TODO: Store non-objects more efficiently. A lot of space is currenlty wasted
* for JS primitives. `js_val` should only store a pointer to the `js_obj`.
* Maybe the GC can keep separate heaps for objects and values. */

typedef struct js_val {
js_number number;
js_string string;
js_boolean boolean;
js_object object; // TODO: make this a pointer (too big as is)
js_object object;
js_type type;
ctl_signal signal;
struct js_val *proto;
Expand Down
2 changes: 1 addition & 1 deletion src/grammar.y
Original file line number Diff line number Diff line change
Expand Up @@ -1264,7 +1264,7 @@ main(int argc, char **argv)
// Normally errors cause the program to exit, but we'd like the REPL to
// continue. Use setjmp here and longjmp in `fh_error` to simulate
// exception handling.
if (!setjmp(fh->jmpbuf))
if (!setjmp(fh->repl_jmp))
DEBUG(fh_eval_file(source, fh->global));
}
}
Expand Down
55 changes: 54 additions & 1 deletion src/runtime/lib/Error.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ error_eval_new(js_val *instance, js_args *args, eval_state *state)
{
js_val *err = error_new(instance, args, state);
fh_set(err, "name", JSSTR(E_EVAL));
err->proto = fh_try_get_proto(E_EVAL);
return err;
}

Expand All @@ -34,6 +35,7 @@ error_range_new(js_val *instance, js_args *args, eval_state *state)
{
js_val *err = error_new(instance, args, state);
fh_set(err, "name", JSSTR(E_RANGE));
err->proto = fh_try_get_proto(E_RANGE);
return err;
}

Expand All @@ -43,6 +45,7 @@ error_ref_new(js_val *instance, js_args *args, eval_state *state)
{
js_val *err = error_new(instance, args, state);
fh_set(err, "name", JSSTR(E_REFERENCE));
err->proto = fh_try_get_proto(E_REFERENCE);
return err;
}

Expand All @@ -52,6 +55,7 @@ error_syntax_new(js_val *instance, js_args *args, eval_state *state)
{
js_val *err = error_new(instance, args, state);
fh_set(err, "name", JSSTR(E_SYNTAX));
err->proto = fh_try_get_proto(E_SYNTAX);
return err;
}

Expand All @@ -61,6 +65,7 @@ error_type_new(js_val *instance, js_args *args, eval_state *state)
{
js_val *err = error_new(instance, args, state);
fh_set(err, "name", JSSTR(E_TYPE));
err->proto = fh_try_get_proto(E_TYPE);
return err;
}

Expand All @@ -70,6 +75,7 @@ error_uri_new(js_val *instance, js_args *args, eval_state *state)
{
js_val *err = error_new(instance, args, state);
fh_set(err, "name", JSSTR(E_URI));
err->proto = fh_try_get_proto(E_URI);
return err;
}

Expand All @@ -92,7 +98,7 @@ error_proto_to_string(js_val *instance, js_args *args, eval_state *state)
}

js_val *
bootstrap_error()
bootstrap_error(js_val *global)
{
js_val *error = JSNFUNC(error_new, 1);
js_val *prototype = JSOBJ();
Expand All @@ -104,6 +110,7 @@ bootstrap_error()
// Properties
DEF(error, "prototype", prototype);


// Error.prototype
// ---------------

Expand All @@ -117,5 +124,51 @@ bootstrap_error()

fh_attach_prototype(prototype, fh->function_proto);


// Other Error constructors
// ------------------------

// EvalError
js_val *error_eval = JSNFUNC(error_eval_new, 1);
js_val *error_eval_proto = JSOBJ();
error_eval_proto->proto = prototype;
DEF(error_eval, "prototype", error_eval_proto);
DEF(global, "EvalError", error_eval);

// RangeError
js_val *error_range = JSNFUNC(error_range_new, 1);
js_val *error_range_proto = JSOBJ();
error_range_proto->proto = prototype;
DEF(error_range, "prototype", error_range_proto);
DEF(global, "RangeError", error_range);

// ReferenceError
js_val *error_ref = JSNFUNC(error_ref_new, 1);
js_val *error_ref_proto = JSOBJ();
error_ref_proto->proto = prototype;
DEF(error_ref, "prototype", error_ref_proto);
DEF(global, "ReferenceError", error_ref);

// SyntaxError
js_val *error_syntax = JSNFUNC(error_syntax_new, 1);
js_val *error_syntax_proto = JSOBJ();
error_syntax_proto->proto = prototype;
DEF(error_syntax, "prototype", error_syntax_proto);
DEF(global, "SyntaxError", error_syntax);

// TypeError
js_val *error_type = JSNFUNC(error_type_new, 1);
js_val *error_type_proto = JSOBJ();
error_type_proto->proto = prototype;
DEF(error_type, "prototype", error_type_proto);
DEF(global, "TypeError", error_type);

// URIError
js_val *error_uri = JSNFUNC(error_uri_new, 1);
js_val *error_uri_proto = JSOBJ();
error_uri_proto->proto = prototype;
DEF(error_uri, "prototype", error_uri_proto);
DEF(global, "URIError", error_uri);

return error;
}
2 changes: 1 addition & 1 deletion src/runtime/lib/Error.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ js_val * error_uri_new(js_val *, js_args *, eval_state *);

js_val * error_proto_to_string(js_val *, js_args *, eval_state *);

js_val * bootstrap_error(void);
js_val * bootstrap_error(js_val *);

#endif
10 changes: 1 addition & 9 deletions src/runtime/runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ fh_bootstrap()
DEF(global, "Boolean", bootstrap_boolean());
DEF(global, "Date", bootstrap_date());
DEF(global, "RegExp", bootstrap_regexp());
DEF(global, "Error", bootstrap_error());
DEF(global, "Error", bootstrap_error(global));
DEF(global, "Math", bootstrap_math());
DEF(global, "console", bootstrap_console());

Expand All @@ -248,14 +248,6 @@ fh_bootstrap()
DEF(global, "undefined", JSUNDEF());
DEF(global, "this", global);

// Other Error constructors
DEF(global, "EvalError", JSNFUNC(error_eval_new, 1));
DEF(global, "RangeError", JSNFUNC(error_range_new, 1));
DEF(global, "ReferenceError", JSNFUNC(error_ref_new, 1));
DEF(global, "SyntaxError", JSNFUNC(error_syntax_new, 1));
DEF(global, "TypeError", JSNFUNC(error_type_new, 1));
DEF(global, "URIError", JSNFUNC(error_uri_new, 1));

// These aren't currently optimized, just here for compatibility's sake.
DEF(global, "Float32Array", JSNFUNC(arr_new, 1));
DEF(global, "Float64Array", JSNFUNC(arr_new, 1));
Expand Down
24 changes: 24 additions & 0 deletions test/test_error_global.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,30 @@ test('Other Constructors', function() {
assertEquals('URIError', (new URIError).name);
assertEquals('', (new URIError).message);
assertEquals('test', (new URIError('test')).message);

assert((new EvalError) instanceof EvalError);
assert((new EvalError) instanceof Error);
assert((new EvalError) instanceof Object);

assert((new RangeError) instanceof RangeError);
assert((new RangeError) instanceof Error);
assert((new RangeError) instanceof Object);

assert((new ReferenceError) instanceof ReferenceError);
assert((new ReferenceError) instanceof Error);
assert((new ReferenceError) instanceof Object);

assert((new SyntaxError) instanceof SyntaxError);
assert((new SyntaxError) instanceof Error);
assert((new SyntaxError) instanceof Object);

assert((new TypeError) instanceof TypeError);
assert((new TypeError) instanceof Error);
assert((new TypeError) instanceof Object);

assert((new URIError) instanceof URIError);
assert((new URIError) instanceof Error);
assert((new URIError) instanceof Object);
});


Expand Down
39 changes: 39 additions & 0 deletions test/test_try.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// test_try.js
// -----------

// A few basic tests

try {
console.assert(false);
} catch(e) {
}

try {
throw new Error();
} catch(e) {
}

try {
throw new EvalError('the message');
} catch (e) {
console.assert(e.message === 'the message');
console.assert(e.name === 'EvalError');
console.assert(e instanceof EvalError);
}

try {
throw new EvalError('another message');
} catch (someSpecialName) {
console.assert(someSpecialName.message === 'another message');
}

try {
try {
throw new Error('from inner try');
} catch (e) {
console.assert(e.message === 'from inner try');
throw new Error('from inner catch');
}
} catch (e) {
console.assert(e.message === 'from inner catch');
}

0 comments on commit 31d4c79

Please sign in to comment.