lexer + parser + codegen: try/catch statement

try { ... } catch (name: Type) { ... } is now a first-class El statement.

Lexer: `try` and `catch` are now keywords (Try, Catch token kinds).
Parser: TryCatch AST node with try_body, catch_name, catch_body.
codegen-js: emits try { ... } catch (name) { ... } directly -- correct
  for all browser error handling patterns.
codegen.el (C backend): emits the try body with a comment; exception
  handling is a no-op since C has no analogous mechanism. Programs using
  try/catch should compile with --target=js.

The catch variable type annotation is parsed and skipped (same treatment
as all other type annotations in El).
This commit is contained in:
Will Anderson
2026-05-04 11:00:24 -05:00
parent e23319fe0b
commit beb2a8c5bd
4 changed files with 58 additions and 0 deletions
+13
View File
@@ -812,6 +812,19 @@ fn js_cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [St
if kind == "TypeDef" { return declared }
if kind == "EnumDef" { return declared }
if kind == "Import" { return declared }
if kind == "TryCatch" {
let try_body = stmt["try_body"]
let catch_name: String = stmt["catch_name"]
let catch_body = stmt["catch_body"]
js_emit_line(indent + "try {")
js_cg_stmts(try_body, indent + " ", native_list_clone(declared))
js_emit_line(indent + "} catch (" + catch_name + ") {")
js_cg_stmts(catch_body, indent + " ", native_list_clone(declared))
js_emit_line(indent + "}")
return declared
}
// ExternFn: the function exists in the JS environment (loaded via <script>
// tag or the module context). Emit a comment so the generated file is
// self-documenting, but no JS function body the implementation is external.
+9
View File
@@ -1143,6 +1143,15 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
if kind == "Import" { return declared }
if kind == "ExternFn" { return declared }
if kind == "CgiBlock" { return declared }
// TryCatch: browser-only control flow. In the C target, emit a comment
// noting that the try body runs unconditionally; error handling is a no-op.
// Programs that rely on catching JS exceptions should compile with --target=js.
if kind == "TryCatch" {
let try_body = stmt["try_body"]
emit_line(indent + "/* try (C target: exception handling not supported) */")
cg_stmts(try_body, indent, native_list_clone(declared))
return declared
}
declared
}
+2
View File
@@ -147,6 +147,8 @@ fn keyword_kind(word: String) -> String {
if word == "accessor" { return "Accessor" }
if word == "vessel" { return "Vessel" }
if word == "extern" { return "Extern" }
if word == "try" { return "Try" }
if word == "catch" { return "Catch" }
""
}
+34
View File
@@ -912,6 +912,40 @@ fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
return make_result({ "stmt": "For", "item": item_name, "list": list_expr, "body": body }, p)
}
// try/catch statement
// try { body } catch (name: Type) { handler }
// The catch variable name and type are both captured; type is skipped.
if k == "Try" {
let p = pos + 1
let r_try = parse_block(tokens, p)
let try_body = r_try["stmts"]
let p = r_try["pos"]
let catch_name = "err"
let k2 = tok_kind(tokens, p)
if str_eq(k2, "Catch") {
let p = p + 1
let p = expect(tokens, p, "LParen")
// catch variable name
let kn = tok_kind(tokens, p)
if str_eq(kn, "Ident") {
let catch_name = tok_value(tokens, p)
let p = p + 1
}
// optional type annotation: : Type
let k3 = tok_kind(tokens, p)
if str_eq(k3, "Colon") {
let p = p + 1
let p = skip_type(tokens, p)
}
let p = expect(tokens, p, "RParen")
let r_catch = parse_block(tokens, p)
let catch_body = r_catch["stmts"]
let p = r_catch["pos"]
return make_result({ "stmt": "TryCatch", "try_body": try_body, "catch_name": catch_name, "catch_body": catch_body }, p)
}
return make_result({ "stmt": "TryCatch", "try_body": try_body, "catch_name": catch_name, "catch_body": native_list_empty() }, p)
}
// @decorator capture decorator name and attach to following stmt
if k == "At" {
let p = pos + 1