diff --git a/meson.build b/meson.build index 7965faf..d2bff82 100644 --- a/meson.build +++ b/meson.build @@ -41,7 +41,7 @@ endif build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() version_conf = configuration_data() -version_conf.set('ANT_VERSION', '0.0.5.43') +version_conf.set('ANT_VERSION', '0.0.5.44') version_conf.set('ANT_GIT_HASH', git_hash) version_conf.set('ANT_BUILD_DATE', build_date) diff --git a/src/ant.c b/src/ant.c index f4b73f3..1a81fc9 100644 --- a/src/ant.c +++ b/src/ant.c @@ -180,6 +180,7 @@ static jsval_t js_arrow_func(struct js *js, jsoff_t params_start, jsoff_t params static jsval_t js_while(struct js *js); static jsval_t js_do_while(struct js *js); static jsval_t js_block_or_stmt(struct js *js); +static jsval_t js_try(struct js *js); static jsval_t do_op(struct js *, uint8_t op, jsval_t l, jsval_t r); static jsval_t do_instanceof(struct js *js, jsval_t l, jsval_t r); static jsval_t do_in(struct js *js, jsval_t l, jsval_t r); @@ -3491,6 +3492,258 @@ static jsval_t js_do_while(struct js *js) { return res; } +static jsval_t js_try(struct js *js) { + uint8_t flags = js->flags, exe = !(flags & F_NOEXEC); + jsval_t res = js_mkundef(); + jsval_t try_result = js_mkundef(); + jsval_t catch_result = js_mkundef(); + jsval_t finally_result = js_mkundef(); + + bool had_exception = false; + char saved_errmsg[256] = {0}; + jsval_t exception_value = js_mkundef(); + + js->consumed = 1; + + if (next(js) != TOK_LBRACE) { + return js_mkerr(js, "{ expected after try"); + } + + jsoff_t try_start = js->pos; + js->flags |= F_NOEXEC; + js->consumed = 1; + + while (next(js) != TOK_EOF && next(js) != TOK_RBRACE) { + jsval_t v = js_stmt(js); + if (is_err(v)) break; + } + if (next(js) == TOK_RBRACE) js->consumed = 1; + jsoff_t try_end = js->pos; + + bool has_catch = false; + bool has_finally = false; + jsoff_t catch_start = 0, catch_end = 0; + jsoff_t finally_start = 0, finally_end = 0; + jsoff_t catch_param_off = 0, catch_param_len = 0; + + if (lookahead(js) == TOK_CATCH) { + has_catch = true; + js->consumed = 1; + next(js); + js->consumed = 1; + + if (next(js) == TOK_LPAREN) { + js->consumed = 1; + if (next(js) == TOK_IDENTIFIER) { + catch_param_off = js->toff; + catch_param_len = js->tlen; + js->consumed = 1; + } + if (next(js) != TOK_RPAREN) { + return js_mkerr(js, ") expected in catch"); + } + js->consumed = 1; + } + + if (next(js) != TOK_LBRACE) { + return js_mkerr(js, "{ expected after catch"); + } + + catch_start = js->pos; + js->consumed = 1; + + while (next(js) != TOK_EOF && next(js) != TOK_RBRACE) { + jsval_t v = js_stmt(js); + if (is_err(v)) break; + } + if (next(js) == TOK_RBRACE) js->consumed = 1; + catch_end = js->pos; + } + + if (lookahead(js) == TOK_FINALLY) { + has_finally = true; + js->consumed = 1; + next(js); + js->consumed = 1; + + if (next(js) != TOK_LBRACE) { + return js_mkerr(js, "{ expected after finally"); + } + + finally_start = js->pos; + js->consumed = 1; + + while (next(js) != TOK_EOF && next(js) != TOK_RBRACE) { + jsval_t v = js_stmt(js); + if (is_err(v)) break; + } + if (next(js) == TOK_RBRACE) js->consumed = 1; + finally_end = js->pos; + } + + if (!has_catch && !has_finally) { + return js_mkerr(js, "try requires catch or finally"); + } + + jsoff_t end_pos = has_finally ? finally_end : (has_catch ? catch_end : try_end); + + if (exe) { + bool try_returned = false; + jsval_t try_return_value = js_mkundef(); + + js->flags = flags & (uint8_t)~F_NOEXEC; + js->pos = try_start; + js->consumed = 1; + + while (next(js) != TOK_EOF && next(js) != TOK_RBRACE && !(js->flags & (F_RETURN | F_THROW | F_BREAK))) { + try_result = js_stmt(js); + if (is_err(try_result)) { + had_exception = true; + break; + } + } + + if (js->flags & F_RETURN) { + try_returned = true; + try_return_value = try_result; + js->flags &= (uint8_t)~(F_RETURN | F_NOEXEC); + } + + if (js->flags & F_THROW) { + had_exception = true; + js->flags &= (uint8_t)~F_THROW; + strncpy(saved_errmsg, js->errmsg, sizeof(saved_errmsg) - 1); + saved_errmsg[sizeof(saved_errmsg) - 1] = '\0'; + + jsval_t err_obj = mkobj(js, 0); + jsval_t msg_key = js_mkstr(js, "message", 7); + jsval_t name_key = js_mkstr(js, "name", 4); + + char *colon = strchr(saved_errmsg, ':'); + if (colon && strncmp(saved_errmsg, "Uncaught ", 9) == 0) { + char *type_start = saved_errmsg + 9; + size_t type_len = colon - type_start; + char *msg_start = colon + 2; + char *newline = strchr(msg_start, '\n'); + size_t msg_len = newline ? (size_t)(newline - msg_start) : strlen(msg_start); + + jsval_t name_val = js_mkstr(js, type_start, type_len); + jsval_t msg_val = js_mkstr(js, msg_start, msg_len); + setprop(js, err_obj, name_key, name_val); + setprop(js, err_obj, msg_key, msg_val); + } else { + jsval_t msg_val = js_mkstr(js, saved_errmsg, strlen(saved_errmsg)); + jsval_t name_val = js_mkstr(js, "Error", 5); + setprop(js, err_obj, name_key, name_val); + setprop(js, err_obj, msg_key, msg_val); + } + + exception_value = err_obj; + js->errmsg[0] = '\0'; + } + + if (next(js) == TOK_RBRACE) js->consumed = 1; + + bool exception_handled = false; + bool catch_returned = false; + jsval_t catch_return_value = js_mkundef(); + + if (had_exception && has_catch) { + exception_handled = true; + mkscope(js); + + if (catch_param_len > 0) { + jsval_t key = js_mkstr(js, &js->code[catch_param_off], catch_param_len); + mkprop(js, js->scope, key, exception_value, false); + } + + js->flags = flags & (uint8_t)~F_NOEXEC; + js->pos = catch_start; + js->consumed = 1; + + while (next(js) != TOK_EOF && next(js) != TOK_RBRACE && !(js->flags & (F_RETURN | F_THROW | F_BREAK))) { + catch_result = js_stmt(js); + if (is_err(catch_result)) break; + } + + if (js->flags & F_RETURN) { + catch_returned = true; + catch_return_value = catch_result; + js->flags &= (uint8_t)~(F_RETURN | F_NOEXEC); + } + + if (next(js) == TOK_RBRACE) js->consumed = 1; + delscope(js); + + if (js->flags & F_THROW) { + exception_handled = false; + strncpy(saved_errmsg, js->errmsg, sizeof(saved_errmsg) - 1); + saved_errmsg[sizeof(saved_errmsg) - 1] = '\0'; + } else { + res = catch_result; + } + } + + if (has_finally) { + uint8_t pre_finally_flags = js->flags; + bool had_pre_finally_exception = (js->flags & F_THROW) != 0; + char pre_finally_errmsg[256] = {0}; + if (had_pre_finally_exception) { + strncpy(pre_finally_errmsg, js->errmsg, sizeof(pre_finally_errmsg) - 1); + js->flags &= (uint8_t)~F_THROW; + js->errmsg[0] = '\0'; + } + + js->flags = flags & (uint8_t)~F_NOEXEC; + js->pos = finally_start; + js->consumed = 1; + + while (next(js) != TOK_EOF && next(js) != TOK_RBRACE && !(js->flags & (F_RETURN | F_THROW | F_BREAK))) { + finally_result = js_stmt(js); + if (is_err(finally_result)) break; + } + + if (next(js) == TOK_RBRACE) js->consumed = 1; + + if (!(js->flags & (F_RETURN | F_THROW))) { + if (had_pre_finally_exception) { + js->flags = pre_finally_flags; + strncpy(js->errmsg, pre_finally_errmsg, sizeof(js->errmsg) - 1); + } else if (had_exception && !exception_handled) { + js->flags |= F_THROW; + strncpy(js->errmsg, saved_errmsg, sizeof(js->errmsg) - 1); + } else if (catch_returned) { + js->flags |= F_RETURN; + res = catch_return_value; + } else if (try_returned) { + js->flags |= F_RETURN; + res = try_return_value; + } + } + } else if (had_exception && !exception_handled) { + js->flags |= F_THROW; + strncpy(js->errmsg, saved_errmsg, sizeof(js->errmsg) - 1); + res = mkval(T_ERR, 0); + } else if (catch_returned) { + js->flags |= F_RETURN; + res = catch_return_value; + } else if (try_returned) { + js->flags |= F_RETURN; + res = try_return_value; + } + + if (!had_exception && !try_returned && !(js->flags & (F_RETURN | F_THROW))) { + res = try_result; + } + } + + js->pos = end_pos; + js->tok = TOK_SEMICOLON; + js->consumed = 0; + + return res; +} + static jsval_t js_break(struct js *js) { if (js->flags & F_NOEXEC) { } else { @@ -3719,7 +3972,6 @@ static jsval_t js_stmt(struct js *js) { case TOK_CASE: case TOK_CATCH: case TOK_DEFAULT: case TOK_FINALLY: case TOK_SWITCH: - case TOK_TRY: case TOK_WITH: case TOK_YIELD: res = js_mkerr(js, "'%.*s' not implemented", (int) js->tlen, js->code + js->toff); break; @@ -3758,6 +4010,7 @@ static jsval_t js_stmt(struct js *js) { case TOK_LBRACE: res = js_block(js, !(js->flags & F_NOEXEC)); break; case TOK_FOR: res = js_for(js); break; case TOK_RETURN: res = js_return(js); break; + case TOK_TRY: res = js_try(js); break; default: res = resolveprop(js, js_expr(js)); break; } diff --git a/tests/test_try_catch.cjs b/tests/test_try_catch.cjs new file mode 100644 index 0000000..9e25997 --- /dev/null +++ b/tests/test_try_catch.cjs @@ -0,0 +1,151 @@ +// Test 1: Basic try-catch +console.log("Test 1: Basic try-catch"); +let result1 = "not caught"; +try { + throw new Error("test error"); + result1 = "should not reach"; +} catch (e) { + result1 = "caught: " + e.message; +} +console.log(result1); + +// Test 2: Try without exception +console.log("\nTest 2: Try without exception"); +let result2 = "initial"; +try { + result2 = "no error"; +} catch (e) { + result2 = "caught"; +} +console.log(result2); + +// Test 3: Try-catch-finally +console.log("\nTest 3: Try-catch-finally"); +let result3 = []; +try { + result3.push("try"); + throw new Error("error"); +} catch (e) { + result3.push("catch"); +} finally { + result3.push("finally"); +} +console.log(result3.join(", ")); + +// Test 4: Finally without catch +console.log("\nTest 4: Try-finally (no exception)"); +let result4 = []; +try { + result4.push("try"); +} finally { + result4.push("finally"); +} +console.log(result4.join(", ")); + +// Test 5: Catch without parentheses (optional binding) +console.log("\nTest 5: Catch without binding"); +let result5 = "not caught"; +try { + throw "error string"; +} catch { + result5 = "caught without binding"; +} +console.log(result5); + +// Test 6: Nested try-catch +console.log("\nTest 6: Nested try-catch"); +let result6 = []; +try { + result6.push("outer try"); + try { + result6.push("inner try"); + throw new Error("inner error"); + } catch (e) { + result6.push("inner catch: " + e.message); + } + result6.push("after inner"); +} catch (e) { + result6.push("outer catch"); +} +console.log(result6.join(", ")); + +// Test 7: Rethrow in catch +console.log("\nTest 7: Rethrow in catch"); +let result7 = []; +try { + try { + throw new Error("original"); + } catch (e) { + result7.push("first catch: " + e.message); + throw new Error("rethrown"); + } +} catch (e) { + result7.push("second catch: " + e.message); +} +console.log(result7.join(", ")); + +// Test 8: Finally runs even with return +console.log("\nTest 8: Finally with return"); +function testFinallyReturn() { + let x = []; + try { + x.push("try"); + return x; + } finally { + x.push("finally"); + } +} +console.log(testFinallyReturn().join(", ")); + +// Test 9: Access error properties +console.log("\nTest 9: Error properties"); +try { + throw new Error("test message"); +} catch (e) { + console.log("name: " + e.name); + console.log("message: " + e.message); +} + +// Test 10: Custom error class +console.log("\nTest 10: Custom error class"); +class CustomError extends Error { + constructor(msg) { + super(msg); + this.name = "CustomError"; + } +} + +try { + throw new CustomError("custom message"); +} catch (e) { + console.log("Caught " + e.name + ": " + e.message); +} + +// Test 11: Finally modifies value +console.log("\nTest 11: Finally executes after catch"); +let counter = 0; +try { + counter = 1; + throw new Error("test"); +} catch (e) { + counter = 2; +} finally { + counter = counter + 10; +} +console.log("counter: " + counter); + +// Test 12: Exception in finally propagates +console.log("\nTest 12: Exception in try, no catch"); +let result12 = []; +try { + try { + throw new Error("original error"); + } finally { + result12.push("finally ran"); + } +} catch (e) { + result12.push("outer caught: " + e.message); +} +console.log(result12.join(", ")); + +console.log("\nAll tests completed!");