lang: catch/onfail error bindings take parens
try foo() catch (e) { } // legal
try foo() catch e { } // parse error with a migration hint
Same capture style as the for-loop. All four catch shapes keep working
with the parenthesized binding — block, bare-expression body, and the
== match sugar — and the no-binding forms are unchanged. onfail follows
the same rule (onfail (e) { }); its expression-cleanup form is
disambiguated by the paren-group-before-brace lookahead, so
onfail (f()); stays an expression cleanup.
AST unchanged; the printer renders the parens; the #run escape help
text updated. Corpus migrated (57 catch + 3 onfail bindings, in-source
parser test strings, specs incl. grammar rules, readme untouched —
no catch examples there).
Regression: examples/1157-diagnostics-catch-binding-needs-parens.sx;
re-captured stderr for 1010/1013/1037/1123 (migrated source echoed in
carets + help text).
This commit is contained in:
@@ -24,7 +24,7 @@ clamp :: (x: s64) -> s64 { if x > 10 { return 10; } return x; }
|
|||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> s32 {
|
||||||
print("const_one={}\n", const_one()); // 1
|
print("const_one={}\n", const_one()); // 1
|
||||||
print("raised={}\n", always_raise(5) catch e 0); // 0
|
print("raised={}\n", always_raise(5) catch (e) 0); // 0
|
||||||
print("clamp_hi={}\n", clamp(42)); // 10
|
print("clamp_hi={}\n", clamp(42)); // 10
|
||||||
print("clamp_lo={}\n", clamp(7)); // 7
|
print("clamp_lo={}\n", clamp(7)); // 7
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ sm_pair :: (a: s32, b: s32) -> (s32, s32, !) {
|
|||||||
|
|
||||||
// `catch` block that diverges (logs the tag, then returns a fallback)
|
// `catch` block that diverges (logs the tag, then returns a fallback)
|
||||||
sm_or_default :: (n: s32) -> s32 {
|
sm_or_default :: (n: s32) -> s32 {
|
||||||
return sm_parse(n) catch e {
|
return sm_parse(n) catch (e) {
|
||||||
print(" logged {}\n", e);
|
print(" logged {}\n", e);
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ IoErr :: error { Disk }
|
|||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> s32 {
|
||||||
fail_own := closure(() -> !IoErr { raise error.Disk; });
|
fail_own := closure(() -> !IoErr { raise error.Disk; });
|
||||||
fail_own() catch e {
|
fail_own() catch (e) {
|
||||||
if e == error.Disk { print("own=Disk\n"); }
|
if e == error.Disk { print("own=Disk\n"); }
|
||||||
};
|
};
|
||||||
d := dep_err();
|
d := dep_err();
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ must :: (n: s32) -> !E {
|
|||||||
|
|
||||||
// Diverging body — returns from `classify` on error.
|
// Diverging body — returns from `classify` on error.
|
||||||
classify :: (n: s32) -> s32 {
|
classify :: (n: s32) -> s32 {
|
||||||
must(n) catch e {
|
must(n) catch (e) {
|
||||||
if e == error.Bad { return 1; }
|
if e == error.Bad { return 1; }
|
||||||
if e == error.Empty { return 2; }
|
if e == error.Empty { return 2; }
|
||||||
return 9;
|
return 9;
|
||||||
@@ -28,9 +28,9 @@ classify :: (n: s32) -> s32 {
|
|||||||
return 0; // must(n) succeeded
|
return 0; // must(n) succeeded
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match-body form — sugar for `catch e { if e == { case ... } }`.
|
// Match-body form — sugar for `catch (e) { if e == { case ... } }`.
|
||||||
mclassify :: (n: s32) -> s32 {
|
mclassify :: (n: s32) -> s32 {
|
||||||
must(n) catch e == {
|
must(n) catch (e) == {
|
||||||
case .Bad: return 11;
|
case .Bad: return 11;
|
||||||
case .Empty: return 22;
|
case .Empty: return 22;
|
||||||
else: return 99;
|
else: return 99;
|
||||||
@@ -41,7 +41,7 @@ mclassify :: (n: s32) -> s32 {
|
|||||||
// Selective handle + re-raise (failable enclosing fn; `raise e` is the
|
// Selective handle + re-raise (failable enclosing fn; `raise e` is the
|
||||||
// variable form). Swallows Bad → success; re-raises everything else.
|
// variable form). Swallows Bad → success; re-raises everything else.
|
||||||
handle_some :: (n: s32) -> !E {
|
handle_some :: (n: s32) -> !E {
|
||||||
must(n) catch e {
|
must(n) catch (e) {
|
||||||
if e == error.Bad { return; } // swallow → success
|
if e == error.Bad { return; } // swallow → success
|
||||||
raise e; // re-raise the rest
|
raise e; // re-raise the rest
|
||||||
};
|
};
|
||||||
@@ -50,7 +50,7 @@ handle_some :: (n: s32) -> !E {
|
|||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> s32 {
|
||||||
r : s32 = 0;
|
r : s32 = 0;
|
||||||
must(-1) catch e { if e == error.Bad { r = r + 1; } }; // Bad → +1
|
must(-1) catch (e) { if e == error.Bad { r = r + 1; } }; // Bad → +1
|
||||||
must(5) catch { r = r + 100; }; // success → body skipped
|
must(5) catch { r = r + 100; }; // success → body skipped
|
||||||
r = r + classify(0); // Empty → 2
|
r = r + classify(0); // Empty → 2
|
||||||
r = r + classify(8); // success → 0
|
r = r + classify(8); // success → 0
|
||||||
|
|||||||
@@ -8,6 +8,6 @@
|
|||||||
plain :: () -> s32 { return 0; }
|
plain :: () -> s32 { return 0; }
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> s32 {
|
||||||
plain() catch e { return 1; }; // error: operand has type s32 (not failable)
|
plain() catch (e) { return 1; }; // error: operand has type s32 (not failable)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// the consumer side of the error-channel tuple ABI). `try f()` on a
|
// the consumer side of the error-channel tuple ABI). `try f()` on a
|
||||||
// `-> (T, !E)` callee binds the value slot on success and propagates the error
|
// `-> (T, !E)` callee binds the value slot on success and propagates the error
|
||||||
// on failure (a pure-failable caller returns the tag; a value-carrying caller
|
// on failure (a pure-failable caller returns the tag; a value-carrying caller
|
||||||
// returns `{undef, tag}`). `f() catch e BODY` yields the value slot on success
|
// returns `{undef, tag}`). `f() catch (e) BODY` yields the value slot on success
|
||||||
// or the handler body's value on failure, merged through a block parameter.
|
// or the handler body's value on failure, merged through a block parameter.
|
||||||
// The producer side is `examples/228-value-failable.sx`.
|
// The producer side is `examples/228-value-failable.sx`.
|
||||||
|
|
||||||
@@ -31,12 +31,12 @@ relay :: (n: s32) -> !E {
|
|||||||
|
|
||||||
// value-carrying `catch`, bare-expression fallback.
|
// value-carrying `catch`, bare-expression fallback.
|
||||||
safe :: (n: s32) -> s32 {
|
safe :: (n: s32) -> s32 {
|
||||||
return parse(n) catch e 0;
|
return parse(n) catch (e) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// value-carrying `catch`, match-body value.
|
// value-carrying `catch`, match-body value.
|
||||||
classify :: (n: s32) -> s32 {
|
classify :: (n: s32) -> s32 {
|
||||||
return parse(n) catch e == {
|
return parse(n) catch (e) == {
|
||||||
case .Bad: 1;
|
case .Bad: 1;
|
||||||
case .Empty: 2;
|
case .Empty: 2;
|
||||||
else: 3
|
else: 3
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ parse :: (n: s32) -> (s32, !E) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> s32 {
|
||||||
x := parse(-1) catch e { print("oops\n") }; // error: body yields no value
|
x := parse(-1) catch (e) { print("oops\n") }; // error: body yields no value
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// (ERR step E1.7). Unlike `defer` (which runs on every exit), `onfail` fires
|
// (ERR step E1.7). Unlike `defer` (which runs on every exit), `onfail` fires
|
||||||
// on an error exit — a `raise` or a propagating `try` — and is skipped on
|
// on an error exit — a `raise` or a propagating `try` — and is skipped on
|
||||||
// success. On an error exit `defer` and `onfail` run interleaved in reverse
|
// success. On an error exit `defer` and `onfail` run interleaved in reverse
|
||||||
// declaration order. `onfail e { … }` binds the in-flight error tag.
|
// declaration order. `onfail (e) { … }` binds the in-flight error tag.
|
||||||
// (Per-attempt-`try` gating and `or`-chain absorption refine this in E2.4b.)
|
// (Per-attempt-`try` gating and `or`-chain absorption refine this in E2.4b.)
|
||||||
|
|
||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
@@ -25,7 +25,7 @@ run :: (n: s32) -> !E {
|
|||||||
|
|
||||||
// `onfail e` binds the tag.
|
// `onfail e` binds the tag.
|
||||||
classify :: (n: s32) -> !E {
|
classify :: (n: s32) -> !E {
|
||||||
onfail e { if e == error.Bad { print("cleanup: bad\n"); } }
|
onfail (e) { if e == error.Bad { print("cleanup: bad\n"); } }
|
||||||
if n < 0 { raise error.Bad; }
|
if n < 0 { raise error.Bad; }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ inc :: (n: s32) -> (s32, s32, !E) {
|
|||||||
|
|
||||||
// Multi-value `catch`, bare-expression tuple fallback (absorbs the failure).
|
// Multi-value `catch`, bare-expression tuple fallback (absorbs the failure).
|
||||||
safe :: (n: s32) -> s32 {
|
safe :: (n: s32) -> s32 {
|
||||||
v, b := parse(n) catch e (40, 50);
|
v, b := parse(n) catch (e) (40, 50);
|
||||||
return v + b;
|
return v + b;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi-value `catch` match-body — per-tag dispatch, each arm a value-tuple.
|
// Multi-value `catch` match-body — per-tag dispatch, each arm a value-tuple.
|
||||||
classify :: (n: s32) -> s32 {
|
classify :: (n: s32) -> s32 {
|
||||||
v, b := parse(n) catch e == {
|
v, b := parse(n) catch (e) == {
|
||||||
case .Bad: (1, 1);
|
case .Bad: (1, 1);
|
||||||
case .Empty: (2, 2);
|
case .Empty: (2, 2);
|
||||||
else: (9, 9);
|
else: (9, 9);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ f :: () -> !E {
|
|||||||
defer { return; } // ERROR: return in defer body
|
defer { return; } // ERROR: return in defer body
|
||||||
onfail { try g(); } // ERROR: try in onfail body
|
onfail { try g(); } // ERROR: try in onfail body
|
||||||
defer { for 0..1 (i) { break; } } // ERROR: break in defer body (transitive through loop)
|
defer { for 0..1 (i) { break; } } // ERROR: break in defer body (transitive through loop)
|
||||||
onfail e { if e == error.Bad { continue; } } // ERROR: continue in onfail body
|
onfail (e) { if e == error.Bad { continue; } } // ERROR: continue in onfail body
|
||||||
try g();
|
try g();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ main :: () -> s32 {
|
|||||||
print("a={} b={}\n", a, b); // a=BadDigit b=Overflow
|
print("a={} b={}\n", a, b); // a=BadDigit b=Overflow
|
||||||
|
|
||||||
// A tag bound by `catch` interpolates too (diverging handler).
|
// A tag bound by `catch` interpolates too (diverging handler).
|
||||||
v := parse(0) catch e {
|
v := parse(0) catch (e) {
|
||||||
print("parse failed with {}\n", e); // parse failed with Empty
|
print("parse failed with {}\n", e); // parse failed with Empty
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ main :: () -> s32 {
|
|||||||
// cleared when the handler completes (a non-diverging exit), not on entry.
|
// cleared when the handler completes (a non-diverging exit), not on entry.
|
||||||
// So inside the handler the frames are still visible (here: the `raise` in
|
// So inside the handler the frames are still visible (here: the `raise` in
|
||||||
// `fail` + the `try fail` propagation in `propagate` = 2 frames)...
|
// `fail` + the `try fail` propagation in `propagate` = 2 frames)...
|
||||||
propagate(-1) catch e {
|
propagate(-1) catch (e) {
|
||||||
print("in catch: len={}\n", sx_trace_len()); // 2 (handler sees the chain)
|
print("in catch: len={}\n", sx_trace_len()); // 2 (handler sees the chain)
|
||||||
};
|
};
|
||||||
print("after catch: len={}\n", sx_trace_len()); // 0 (absorbed at handler exit)
|
print("after catch: len={}\n", sx_trace_len()); // 0 (absorbed at handler exit)
|
||||||
|
|
||||||
// A success leaves the buffer empty (nothing pushed).
|
// A success leaves the buffer empty (nothing pushed).
|
||||||
propagate(1) catch e { };
|
propagate(1) catch (e) { };
|
||||||
print("after success: len={}\n", sx_trace_len()); // 0
|
print("after success: len={}\n", sx_trace_len()); // 0
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ mid :: (n: s32) -> !E {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> s32 {
|
||||||
mid(-1) catch e {
|
mid(-1) catch (e) {
|
||||||
print("[stdout] caught {}\n", e); // tag name via the always-linked table
|
print("[stdout] caught {}\n", e); // tag name via the always-linked table
|
||||||
trace.print_current(); // [stderr] the 2-frame trace
|
trace.print_current(); // [stderr] the 2-frame trace
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ main :: () -> (s32, !E) {
|
|||||||
r = r + (try fa(0) or try fa(7)); // a fails → b succeeds → 7
|
r = r + (try fa(0) or try fa(7)); // a fails → b succeeds → 7
|
||||||
r = r + (try fa(0) or try fa(0) or try fa(3)); // first two fail → third → +3 = 10
|
r = r + (try fa(0) or try fa(0) or try fa(3)); // first two fail → third → +3 = 10
|
||||||
r = r + (fa(0) or fa(0) or 96); // bare chain + value terminator → +96 = 106
|
r = r + (fa(0) or fa(0) or 96); // bare chain + value terminator → +96 = 106
|
||||||
r = r + ((try fa(0) or try fa(0)) catch e 5); // both fail → catch handler → +5 = 111
|
r = r + ((try fa(0) or try fa(0)) catch (e) 5); // both fail → catch handler → +5 = 111
|
||||||
r = r + ((try fa(0) or try fa(9)) catch e 0); // second succeeds → catch skipped → +9 = 120
|
r = r + ((try fa(0) or try fa(9)) catch (e) 0); // second succeeds → catch skipped → +9 = 120
|
||||||
|
|
||||||
try fv(0) or try fv(1); // void chain: first fails → second succeeds
|
try fv(0) or try fv(1); // void chain: first fails → second succeeds
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ mid :: () -> !TErr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
probe :: () {
|
probe :: () {
|
||||||
mid() catch e {
|
mid() catch (e) {
|
||||||
print("comptime caught {}\n", e);
|
print("comptime caught {}\n", e);
|
||||||
trace.print_current();
|
trace.print_current();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ sm_pair :: (a: s32, b: s32) -> (s32, s32, !) {
|
|||||||
|
|
||||||
// catch with a diverging block body
|
// catch with a diverging block body
|
||||||
sm_or_default :: (n: s32) -> s32 {
|
sm_or_default :: (n: s32) -> s32 {
|
||||||
return sm_parse(n) catch e {
|
return sm_parse(n) catch (e) {
|
||||||
print(" logged {}\n", e);
|
print(" logged {}\n", e);
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
@@ -61,7 +61,7 @@ sm_run :: (cb: Closure(s32) -> (s32, !SmokeErr), n: s32) -> (s32, !SmokeErr) {
|
|||||||
// bare fn-type param: a NON-failable closure literal widens into the failable
|
// bare fn-type param: a NON-failable closure literal widens into the failable
|
||||||
// slot (the ∅-widening adapter wraps `{value, 0}`)
|
// slot (the ∅-widening adapter wraps `{value, 0}`)
|
||||||
sm_widen :: (cb: (s32) -> (s32, !SmokeErr), n: s32) -> s32 {
|
sm_widen :: (cb: (s32) -> (s32, !SmokeErr), n: s32) -> s32 {
|
||||||
return cb(n) catch e -1;
|
return cb(n) catch (e) -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// generic ($T) value-carrying failable composition, monomorphized per call
|
// generic ($T) value-carrying failable composition, monomorphized per call
|
||||||
@@ -81,11 +81,11 @@ main :: () {
|
|||||||
if err2 == error.BadDigit { print("got: {}\n", err2); }
|
if err2 == error.BadDigit { print("got: {}\n", err2); }
|
||||||
|
|
||||||
// catch — bare-expr body
|
// catch — bare-expr body
|
||||||
ce := sm_parse(0) catch e 100;
|
ce := sm_parse(0) catch (e) 100;
|
||||||
print("catch-expr: {}\n", ce);
|
print("catch-expr: {}\n", ce);
|
||||||
|
|
||||||
// catch — match-body per-tag dispatch
|
// catch — match-body per-tag dispatch
|
||||||
cm := sm_parse(200) catch e == {
|
cm := sm_parse(200) catch (e) == {
|
||||||
case .Overflow: 1;
|
case .Overflow: 1;
|
||||||
case .Empty: 2;
|
case .Empty: 2;
|
||||||
else: 3;
|
else: 3;
|
||||||
@@ -105,9 +105,9 @@ main :: () {
|
|||||||
if !gerr { print("or-chain: {}\n", g); }
|
if !gerr { print("or-chain: {}\n", g); }
|
||||||
|
|
||||||
// multi-value failable consumed by catch (tuple body)
|
// multi-value failable consumed by catch (tuple body)
|
||||||
p, q := sm_pair(0, 3) catch e (0, 0);
|
p, q := sm_pair(0, 3) catch (e) (0, 0);
|
||||||
print("pair-catch: {} {}\n", p, q);
|
print("pair-catch: {} {}\n", p, q);
|
||||||
p2, q2 := sm_pair(4, 5) catch e (0, 0);
|
p2, q2 := sm_pair(4, 5) catch (e) (0, 0);
|
||||||
print("pair-ok: {} {}\n", p2, q2);
|
print("pair-ok: {} {}\n", p2, q2);
|
||||||
|
|
||||||
// pure failable: absorb with no-binding catch
|
// pure failable: absorb with no-binding catch
|
||||||
@@ -120,15 +120,15 @@ main :: () {
|
|||||||
iv, ierr := sm_acquire(false);
|
iv, ierr := sm_acquire(false);
|
||||||
|
|
||||||
// composition: inline failable closure literal through a Closure(...) param
|
// composition: inline failable closure literal through a Closure(...) param
|
||||||
cl := sm_run(closure((x: s32) -> (s32, !SmokeErr) { if x < 0 { raise error.BadDigit; } return x * 2; }), 6) catch e -1;
|
cl := sm_run(closure((x: s32) -> (s32, !SmokeErr) { if x < 0 { raise error.BadDigit; } return x * 2; }), 6) catch (e) -1;
|
||||||
print("closure-run: {}\n", cl); // 12
|
print("closure-run: {}\n", cl); // 12
|
||||||
print("closure-run-err: {}\n", sm_run(closure((x: s32) -> (s32, !SmokeErr) { raise error.Empty; }), 1) catch e -9); // -9
|
print("closure-run-err: {}\n", sm_run(closure((x: s32) -> (s32, !SmokeErr) { raise error.Empty; }), 1) catch (e) -9); // -9
|
||||||
|
|
||||||
// non-failable closure literal widened into the failable bare slot
|
// non-failable closure literal widened into the failable bare slot
|
||||||
print("widen: {}\n", sm_widen(closure((x: s32) -> s32 => x + 1), 9)); // 10
|
print("widen: {}\n", sm_widen(closure((x: s32) -> s32 => x + 1), 9)); // 10
|
||||||
|
|
||||||
// generic failable composition (monomorphized at s32)
|
// generic failable composition (monomorphized at s32)
|
||||||
print("wrap: {}\n", sm_wrap(s32, closure(() -> (s32, !SmokeErr) { return 42; })) catch e 0); // 42
|
print("wrap: {}\n", sm_wrap(s32, closure(() -> (s32, !SmokeErr) { return 42; })) catch (e) 0); // 42
|
||||||
|
|
||||||
print("errors ok\n");
|
print("errors ok\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ guard :: (ok: bool) -> !E {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ok_v :: #run parse(5); // success → 10 (value, error stripped)
|
ok_v :: #run parse(5); // success → 10 (value, error stripped)
|
||||||
caught :: #run parse(-1) catch e 99; // Bad → 99
|
caught :: #run parse(-1) catch (e) 99; // Bad → 99
|
||||||
ored :: #run parse(0) or 55; // Empty → 55
|
ored :: #run parse(0) or 55; // Empty → 55
|
||||||
|
|
||||||
#run guard(false) catch e { }; // onfail fires during the comptime unwind
|
#run guard(false) catch (e) { }; // onfail fires during the comptime unwind
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> s32 {
|
||||||
print("ok={} caught={} ored={}\n", ok_v, caught, ored);
|
print("ok={} caught={} ored={}\n", ok_v, caught, ored);
|
||||||
|
|||||||
@@ -10,13 +10,13 @@
|
|||||||
|
|
||||||
E :: error { Neg }
|
E :: error { Neg }
|
||||||
|
|
||||||
runwith :: (cb: Closure(s64) -> (s64, !E), n: s64) -> s64 { return cb(n) catch e -1; }
|
runwith :: (cb: Closure(s64) -> (s64, !E), n: s64) -> s64 { return cb(n) catch (e) -1; }
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> s32 {
|
||||||
// block-body and arrow-body failable closures, called directly
|
// block-body and arrow-body failable closures, called directly
|
||||||
m := closure((x: s64) -> (s64, !E) { if x < 0 { raise error.Neg; } return x * 2; });
|
m := closure((x: s64) -> (s64, !E) { if x < 0 { raise error.Neg; } return x * 2; });
|
||||||
n := closure((x: s64) -> (s64, !E) => x + 1);
|
n := closure((x: s64) -> (s64, !E) => x + 1);
|
||||||
print("{} {} {} {}\n", m(5) catch e 0, m(-1) catch e 99, m(-1) or 7, n(40) catch e 0); // 10 99 7 41
|
print("{} {} {} {}\n", m(5) catch (e) 0, m(-1) catch (e) 99, m(-1) or 7, n(40) catch (e) 0); // 10 99 7 41
|
||||||
|
|
||||||
// failable closure passed as a Closure(...) parameter
|
// failable closure passed as a Closure(...) parameter
|
||||||
print("param ok={} err={}\n", runwith(m, 5), runwith(m, -1)); // 10 -1
|
print("param ok={} err={}\n", runwith(m, 5), runwith(m, -1)); // 10 -1
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
E :: error { Neg }
|
E :: error { Neg }
|
||||||
|
|
||||||
bare :: (cb: (s64) -> (s64, !E), n: s64) -> s64 { return cb(n) catch e -1; }
|
bare :: (cb: (s64) -> (s64, !E), n: s64) -> s64 { return cb(n) catch (e) -1; }
|
||||||
chain :: (cb: Closure(s64) -> (s64, !E), n: s64) -> (s64, !E) { return try cb(n); }
|
chain :: (cb: Closure(s64) -> (s64, !E), n: s64) -> (s64, !E) { return try cb(n); }
|
||||||
|
|
||||||
dbl :: (x: s64) -> (s64, !E) { if x < 0 { raise error.Neg; } return x * 2; }
|
dbl :: (x: s64) -> (s64, !E) { if x < 0 { raise error.Neg; } return x * 2; }
|
||||||
@@ -25,8 +25,8 @@ main :: () -> s32 {
|
|||||||
|
|
||||||
// Closure(...) param, try-propagated, then caught at the call site
|
// Closure(...) param, try-propagated, then caught at the call site
|
||||||
print("chain ok={} err={}\n",
|
print("chain ok={} err={}\n",
|
||||||
chain(closure((x: s64) -> (s64, !E) => x + 6), 4) catch e 0, // 10
|
chain(closure((x: s64) -> (s64, !E) => x + 6), 4) catch (e) 0, // 10
|
||||||
chain(closure((x: s64) -> (s64, !E) { raise error.Neg; }), 1) catch e 0); // 0
|
chain(closure((x: s64) -> (s64, !E) { raise error.Neg; }), 1) catch (e) 0); // 0
|
||||||
|
|
||||||
// NON-failable closure literal widened into the failable bare slot
|
// NON-failable closure literal widened into the failable bare slot
|
||||||
print("widen={}\n", bare(closure((x: s64) -> s64 => x + 1), 9)); // 10
|
print("widen={}\n", bare(closure((x: s64) -> s64 => x + 1), 9)); // 10
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ main :: () -> s32 {
|
|||||||
handlers.append(closure((x: s32) -> (s32, !) { if x == 0 { raise error.Other; } return x + 100; }));
|
handlers.append(closure((x: s32) -> (s32, !) { if x == 0 { raise error.Other; } return x + 100; }));
|
||||||
|
|
||||||
// success paths
|
// success paths
|
||||||
print("ok0={}\n", dispatch(handlers.items[0], 5) catch e 0); // 10
|
print("ok0={}\n", dispatch(handlers.items[0], 5) catch (e) 0); // 10
|
||||||
print("ok1={}\n", dispatch(handlers.items[1], 7) catch e 0); // 107
|
print("ok1={}\n", dispatch(handlers.items[1], 7) catch (e) 0); // 107
|
||||||
|
|
||||||
// failure paths: each closure raises its own tag, which propagates
|
// failure paths: each closure raises its own tag, which propagates
|
||||||
// through `try` and is absorbed by the call-site `catch` fallback
|
// through `try` and is absorbed by the call-site `catch` fallback
|
||||||
print("err0={}\n", dispatch(handlers.items[0], -1) catch e -1); // raised Negative → -1
|
print("err0={}\n", dispatch(handlers.items[0], -1) catch (e) -1); // raised Negative → -1
|
||||||
print("err1={}\n", dispatch(handlers.items[1], 0) catch e -2); // raised Other → -2
|
print("err1={}\n", dispatch(handlers.items[1], 0) catch (e) -2); // raised Other → -2
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ main :: () -> s32 {
|
|||||||
handlers : List(Closure(s32) -> (s32, !)) = .{};
|
handlers : List(Closure(s32) -> (s32, !)) = .{};
|
||||||
handlers.append(closure((x: s32) -> (s32, !) { if x < 0 { raise error.Negative; } return x; }));
|
handlers.append(closure((x: s32) -> (s32, !) { if x < 0 { raise error.Negative; } return x; }));
|
||||||
handlers.append(closure((x: s32) -> (s32, !) { if x == 0 { raise error.Other; } return x; }));
|
handlers.append(closure((x: s32) -> (s32, !) { if x == 0 { raise error.Other; } return x; }));
|
||||||
print("r={}\n", reject(handlers.items[0], 5) catch e 0);
|
print("r={}\n", reject(handlers.items[0], 5) catch (e) 0);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
E :: error { Neg }
|
E :: error { Neg }
|
||||||
|
|
||||||
take :: (cb: Closure(s32) -> (s32, !E), x: s32) -> s32 { return cb(x) catch e -1; }
|
take :: (cb: Closure(s32) -> (s32, !E), x: s32) -> s32 { return cb(x) catch (e) -1; }
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> s32 {
|
||||||
// `-> s32` (non-failable) but the body raises → lambda-specific hint:
|
// `-> s32` (non-failable) but the body raises → lambda-specific hint:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ wrap :: ($T: Type, f: Closure() -> (T, !E)) -> (T, !E) { return try f(); }
|
|||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> s32 {
|
||||||
// success, consumed by catch
|
// success, consumed by catch
|
||||||
print("catch={}\n", wrap(s32, closure(() -> (s32, !E) { return 7; })) catch e -1); // 7
|
print("catch={}\n", wrap(s32, closure(() -> (s32, !E) { return 7; })) catch (e) -1); // 7
|
||||||
|
|
||||||
// success, consumed by destructure (binds value + error slot); the value
|
// success, consumed by destructure (binds value + error slot); the value
|
||||||
// slot is read only under an `if !err` guard (ERR E1.8 path-sensitivity)
|
// slot is read only under an `if !err` guard (ERR E1.8 path-sensitivity)
|
||||||
@@ -21,9 +21,9 @@ main :: () -> s32 {
|
|||||||
if !err { print("destr={} ok=true\n", r); } // destr=9 ok=true
|
if !err { print("destr={} ok=true\n", r); } // destr=9 ok=true
|
||||||
|
|
||||||
// failure path: the raised tag propagates through the generic `try`
|
// failure path: the raised tag propagates through the generic `try`
|
||||||
print("fail={}\n", wrap(s32, closure(() -> (s32, !E) { raise error.Bad; }) ) catch e -1); // -1
|
print("fail={}\n", wrap(s32, closure(() -> (s32, !E) { raise error.Bad; }) ) catch (e) -1); // -1
|
||||||
|
|
||||||
// a second monomorphization at a different T
|
// a second monomorphization at a different T
|
||||||
print("u8={}\n", wrap(u8, closure(() -> (u8, !E) { return 200; })) catch e 0); // 200
|
print("u8={}\n", wrap(u8, closure(() -> (u8, !E) { return 200; })) catch (e) 0); // 200
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
E :: error { Z }
|
E :: error { Z }
|
||||||
|
|
||||||
bare :: (cb: (s64) -> s64, n: s64) -> s64 { return cb(n); }
|
bare :: (cb: (s64) -> s64, n: s64) -> s64 { return cb(n); }
|
||||||
baref :: (cb: (s64) -> (s64, !E), n: s64) -> s64 { return cb(n) catch e -1; }
|
baref :: (cb: (s64) -> (s64, !E), n: s64) -> s64 { return cb(n) catch (e) -1; }
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> s32 {
|
||||||
inc := closure((x: s64) -> s64 => x + 1); // capture-free closure var
|
inc := closure((x: s64) -> s64 => x + 1); // capture-free closure var
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ main :: () -> s32 {
|
|||||||
// (4) early-return / raise helpers
|
// (4) early-return / raise helpers
|
||||||
total = total + guarded(4); // +40
|
total = total + guarded(4); // +40
|
||||||
total = total + guarded(-1); // -1
|
total = total + guarded(-1); // -1
|
||||||
total = total + (relay(2) catch e 0); // parse(2)=20 → +1 = 21
|
total = total + (relay(2) catch (e) 0); // parse(2)=20 → +1 = 21
|
||||||
|
|
||||||
print("liveness total: {}\n", total); // 50+70+30+40-1+21 = 210
|
print("liveness total: {}\n", total); // 50+70+30+40-1+21 = 210
|
||||||
return total;
|
return total;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ recover :: () -> (s32, !E) { raise error.Bad; }
|
|||||||
|
|
||||||
work :: (n: s32) -> !E {
|
work :: (n: s32) -> !E {
|
||||||
defer print("defer: always\n"); // plain cleanup
|
defer print("defer: always\n"); // plain cleanup
|
||||||
onfail { failing() catch e print("onfail: caught (catch)\n"); } // catch absorbs
|
onfail { failing() catch (e) print("onfail: caught (catch)\n"); } // catch absorbs
|
||||||
onfail { x := recover() or 7; print("onfail: x={} (or)\n", x); } // or-value absorbs
|
onfail { x := recover() or 7; print("onfail: x={} (or)\n", x); } // or-value absorbs
|
||||||
if n < 0 { raise error.Bad; }
|
if n < 0 { raise error.Bad; }
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ run :: () {
|
|||||||
defer {
|
defer {
|
||||||
v, e := probe(); // destructure decl
|
v, e := probe(); // destructure decl
|
||||||
if !e { print("defer: v={}\n", v); } // value live under the guard
|
if !e { print("defer: v={}\n", v); } // value live under the guard
|
||||||
failing() catch x print("defer: caught\n"); // catch-statement absorbs
|
failing() catch (x) print("defer: caught\n"); // catch-statement absorbs
|
||||||
}
|
}
|
||||||
print("body\n");
|
print("body\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ work :: () {
|
|||||||
if !err { print("defer closure: v={}\n", v); } // E1.8: live under guard
|
if !err { print("defer closure: v={}\n", v); } // E1.8: live under guard
|
||||||
try failing();
|
try failing();
|
||||||
};
|
};
|
||||||
emit() catch e print("defer closure: raised\n");
|
emit() catch (e) print("defer closure: raised\n");
|
||||||
}
|
}
|
||||||
print("body\n");
|
print("body\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ parse :: (n: s32) -> (s32, !E) {
|
|||||||
|
|
||||||
// `catch` tag binding spelled `s2`, referenced in the match body.
|
// `catch` tag binding spelled `s2`, referenced in the match body.
|
||||||
classify :: (n: s32) -> s32 {
|
classify :: (n: s32) -> s32 {
|
||||||
return parse(n) catch `s2 == {
|
return parse(n) catch (`s2) == {
|
||||||
case .Bad: 1;
|
case .Bad: 1;
|
||||||
case .Empty: 2;
|
case .Empty: 2;
|
||||||
else: 3
|
else: 3
|
||||||
@@ -25,7 +25,7 @@ classify :: (n: s32) -> s32 {
|
|||||||
|
|
||||||
// `onfail` tag binding spelled `u8`, referenced in the cleanup body.
|
// `onfail` tag binding spelled `u8`, referenced in the cleanup body.
|
||||||
cleanup :: (n: s32) -> !E {
|
cleanup :: (n: s32) -> !E {
|
||||||
onfail `u8 { if `u8 == error.Bad { print("cleanup: bad\n"); } }
|
onfail (`u8) { if `u8 == error.Bad { print("cleanup: bad\n"); } }
|
||||||
if n < 0 { raise error.Bad; }
|
if n < 0 { raise error.Bad; }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ must :: (n: s32) -> !E {
|
|||||||
}
|
}
|
||||||
|
|
||||||
classify :: (n: s32) -> !E {
|
classify :: (n: s32) -> !E {
|
||||||
onfail s64 { } // onfail tag binding
|
onfail (s64) { } // onfail tag binding
|
||||||
must(n) catch u8 { return; }; // catch tag binding
|
must(n) catch (u8) { return; }; // catch tag binding
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
examples/1157-diagnostics-catch-binding-needs-parens.sx
Normal file
14
examples/1157-diagnostics-catch-binding-needs-parens.sx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// The catch error binding is parenthesized — `catch (e) { }`; a bare
|
||||||
|
// binding is rejected with a migration hint. (Same rule as the for-loop
|
||||||
|
// capture; `onfail (e) { }` follows it too.)
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
E :: error { Bad };
|
||||||
|
|
||||||
|
f :: () -> (s64, !E) { raise error.Bad; }
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
v := f() catch e { 0 };
|
||||||
|
print("{}\n", v);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
error: `catch` requires a failable expression; operand has type 's32'
|
error: `catch` requires a failable expression; operand has type 's32'
|
||||||
--> /Users/agra/projects/sx/examples/1010-errors-catch-rejections.sx:11:5
|
--> examples/1010-errors-catch-rejections.sx:11:5
|
||||||
|
|
|
|
||||||
11 | plain() catch e { return 1; }; // error: operand has type s32 (not failable)
|
11 | plain() catch (e) { return 1; }; // error: operand has type s32 (not failable)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
error: `catch` body must produce a value of type 's32' (or diverge with `return` / `raise`)
|
error: `catch` body must produce a value of type 's32' (or diverge with `return` / `raise`)
|
||||||
--> /Users/agra/projects/sx/examples/1013-errors-value-failable-reject.sx:17:10
|
--> examples/1013-errors-value-failable-reject.sx:17:10
|
||||||
|
|
|
|
||||||
17 | x := parse(-1) catch e { print("oops\n") }; // error: body yields no value
|
17 | x := parse(-1) catch (e) { print("oops\n") }; // error: body yields no value
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
error: comptime `#run` (x) raised an unhandled error: error.Bad
|
error: comptime `#run` (x) raised an unhandled error: error.Bad
|
||||||
error return trace (most recent call last):
|
error return trace (most recent call last):
|
||||||
parse at 1037-errors-comptime-run-escape.sx:11:17
|
parse at 1037-errors-comptime-run-escape.sx:11:17
|
||||||
help: handle it at the `#run` site — `#run <expr> catch e { ... }` or `#run <expr> or <default>`
|
help: handle it at the `#run` site — `#run <expr> catch (e) { ... }` or `#run <expr> or <default>`
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
error: 's64' is a reserved type name and cannot be used as an identifier
|
error: 's64' is a reserved type name and cannot be used as an identifier
|
||||||
--> examples/1123-diagnostics-reserved-name-catch-onfail.sx:20:12
|
--> examples/1123-diagnostics-reserved-name-catch-onfail.sx:20:13
|
||||||
|
|
|
|
||||||
20 | onfail s64 { } // onfail tag binding
|
20 | onfail (s64) { } // onfail tag binding
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
||||||
error: 'u8' is a reserved type name and cannot be used as an identifier
|
error: 'u8' is a reserved type name and cannot be used as an identifier
|
||||||
--> examples/1123-diagnostics-reserved-name-catch-onfail.sx:21:19
|
--> examples/1123-diagnostics-reserved-name-catch-onfail.sx:21:20
|
||||||
|
|
|
|
||||||
21 | must(n) catch u8 { return; }; // catch tag binding
|
21 | must(n) catch (u8) { return; }; // catch tag binding
|
||||||
| ^^
|
| ^^
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
error: the catch error binding needs parens: `catch (e) { ... }`
|
||||||
|
--> examples/1157-diagnostics-catch-binding-needs-parens.sx:12:20
|
||||||
|
|
|
||||||
|
12 | v := f() catch e { 0 };
|
||||||
|
| ^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
38
specs.md
38
specs.md
@@ -109,7 +109,7 @@ M :: union { `s1: s32; } // union tag
|
|||||||
`u8, rest := pair(); // destructure name
|
`u8, rest := pair(); // destructure name
|
||||||
if `s16 := maybe() { } // optional binding
|
if `s16 := maybe() { } // optional binding
|
||||||
for xs, 0.. (`bool, `u16) { } // for captures
|
for xs, 0.. (`bool, `u16) { } // for captures
|
||||||
x catch `s2 { } // catch tag binding
|
x catch (`s2) { } // catch tag binding
|
||||||
```
|
```
|
||||||
|
|
||||||
In the **member-name positions** among these — struct field, union tag, and
|
In the **member-name positions** among these — struct field, union tag, and
|
||||||
@@ -2819,7 +2819,7 @@ Statement form. Terminates the immediately enclosing failable function (like
|
|||||||
```sx
|
```sx
|
||||||
if bad raise error.BadDigit; // literal tag
|
if bad raise error.BadDigit; // literal tag
|
||||||
|
|
||||||
v := foo() catch e {
|
v := foo() catch (e) {
|
||||||
if e == error.Specific return default;
|
if e == error.Specific return default;
|
||||||
raise e; // variable tag — re-raise
|
raise e; // variable tag — re-raise
|
||||||
};
|
};
|
||||||
@@ -2854,37 +2854,39 @@ tag — use `catch` for that.
|
|||||||
|
|
||||||
### `catch`
|
### `catch`
|
||||||
|
|
||||||
Expression form. Handles the error inline. The binding is a **bare name, no
|
Expression form. Handles the error inline. The binding is **parenthesized**
|
||||||
parens** (`catch e`), and is **optional**. Four shapes, disambiguated by the
|
(`catch (e)`) — like a for-loop capture — and is **optional**. Four shapes,
|
||||||
token after `catch`:
|
disambiguated by the token after `catch`:
|
||||||
|
|
||||||
| Form | Binding | Body |
|
| Form | Binding | Body |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `catch { ... }` | none (tag ignored) | block — braces required |
|
| `catch { ... }` | none (tag ignored) | block — braces required |
|
||||||
| `catch e { ... }` | `e` | block |
|
| `catch (e) { ... }` | `e` | block |
|
||||||
| `catch e EXPR` | `e` | bare expression (no braces) |
|
| `catch (e) EXPR` | `e` | bare expression (no braces) |
|
||||||
| `catch e == { case ... }` | `e` | match over `e` (sugar for `{ if e == { ... } }`) |
|
| `catch (e) == { case ... }` | `e` | match over `e` (sugar for `{ if e == { ... } }`) |
|
||||||
|
|
||||||
|
A bare binding (`catch (e) { }`) is a parse error with a migration hint.
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
v := parse_digit(s) catch e {
|
v := parse_digit(s) catch (e) {
|
||||||
log.warn("bad input: {}", e);
|
log.warn("bad input: {}", e);
|
||||||
return default; // noreturn body
|
return default; // noreturn body
|
||||||
};
|
};
|
||||||
|
|
||||||
v := parse_digit(s) catch e compute_fallback(e); // value-producing body
|
v := parse_digit(s) catch (e) compute_fallback(e); // value-producing body
|
||||||
|
|
||||||
v, n := parse(s) catch e {
|
v, n := parse(s) catch (e) {
|
||||||
log.warn("parse failed: {}", e);
|
log.warn("parse failed: {}", e);
|
||||||
(0, 0) // tuple body for a multi-value failable
|
(0, 0) // tuple body for a multi-value failable
|
||||||
};
|
};
|
||||||
|
|
||||||
v := parse(s) catch e == { // match-body form
|
v := parse(s) catch (e) == { // match-body form
|
||||||
case .Empty: 0;
|
case .Empty: 0;
|
||||||
case .BadDigit: -1;
|
case .BadDigit: -1;
|
||||||
else: raise e;
|
else: raise e;
|
||||||
};
|
};
|
||||||
|
|
||||||
v := (try foo() or try boo()) catch e { return 0; }; // catch over an `or` chain
|
v := (try foo() or try boo()) catch (e) { return 0; }; // catch over an `or` chain
|
||||||
```
|
```
|
||||||
|
|
||||||
**Body type rule.** The body (block-as-expression) must produce the failable's
|
**Body type rule.** The body (block-as-expression) must produce the failable's
|
||||||
@@ -2928,7 +2930,7 @@ of the other markers directly on `X`) is required.
|
|||||||
|
|
||||||
```sx
|
```sx
|
||||||
a := parse(s) or 0; // OK — terminator on the path
|
a := parse(s) or 0; // OK — terminator on the path
|
||||||
a := parse(s) catch e {...}; // OK — catch marks
|
a := parse(s) catch (e) {...}; // OK — catch marks
|
||||||
v, err := failable(); // OK — destructure marks
|
v, err := failable(); // OK — destructure marks
|
||||||
a := try foo() or try boo(); // OK — each try marks its own exit
|
a := try foo() or try boo(); // OK — each try marks its own exit
|
||||||
|
|
||||||
@@ -2972,7 +2974,7 @@ Dropping the error slot is a compile error:
|
|||||||
v, _ := failable(); // ERROR: the error slot cannot be dropped — handle it
|
v, _ := failable(); // ERROR: the error slot cannot be dropped — handle it
|
||||||
```
|
```
|
||||||
|
|
||||||
Value slots may be discarded (`_, n := parse(s) catch e { return; }`). The
|
Value slots may be discarded (`_, n := parse(s) catch (e) { return; }`). The
|
||||||
statement form `try foo();` is the explicit "propagate, use no value." On a
|
statement form `try foo();` is the explicit "propagate, use no value." On a
|
||||||
value-carrying failable, the value slot is live only where the compiler can
|
value-carrying failable, the value slot is live only where the compiler can
|
||||||
prove the error slot is null (path-sensitive flow-check).
|
prove the error slot is null (path-sensitive flow-check).
|
||||||
@@ -2995,7 +2997,7 @@ make_handle :: () -> (Handle, !) {
|
|||||||
|
|
||||||
open :: (path: string) -> (Handle, !) {
|
open :: (path: string) -> (Handle, !) {
|
||||||
h := try sys_open(path);
|
h := try sys_open(path);
|
||||||
onfail e { log.warn("init failed for {}: {}", path, e); sys_close(h); }
|
onfail (e) { log.warn("init failed for {}: {}", path, e); sys_close(h); }
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -3089,7 +3091,7 @@ return_stmt = 'return' expr? ';'
|
|||||||
break_stmt = 'break' ';'
|
break_stmt = 'break' ';'
|
||||||
continue_stmt = 'continue' ';'
|
continue_stmt = 'continue' ';'
|
||||||
raise_stmt = 'raise' expr ';'
|
raise_stmt = 'raise' expr ';'
|
||||||
onfail_stmt = 'onfail' IDENT? block
|
onfail_stmt = 'onfail' ('(' IDENT ')')? (block | expr ';')
|
||||||
defer_stmt = 'defer' expr ';'
|
defer_stmt = 'defer' expr ';'
|
||||||
insert_stmt = '#insert' expr ';'
|
insert_stmt = '#insert' expr ';'
|
||||||
push_stmt = 'push' expr block
|
push_stmt = 'push' expr block
|
||||||
@@ -3103,7 +3105,7 @@ for_iter = expr [range_op [expr]]
|
|||||||
range_op = '..' | '..=' | '..<' | '<..' | '<..=' | '<..<' | '=..' | '=..=' | '=..<'
|
range_op = '..' | '..=' | '..<' | '<..' | '<..=' | '<..<' | '=..' | '=..=' | '=..<'
|
||||||
for_capture = '(' ['*'] IDENT (',' ['*'] IDENT)* ')'
|
for_capture = '(' ['*'] IDENT (',' ['*'] IDENT)* ')'
|
||||||
binary = catch_expr (binop catch_expr)* // binop includes `or` (fallback / chain)
|
binary = catch_expr (binop catch_expr)* // binop includes `or` (fallback / chain)
|
||||||
catch_expr = unary ('catch' IDENT? (block | '==' '{' case_arm* else_arm? '}' | unary))?
|
catch_expr = unary ('catch' ('(' IDENT ')')? (block | '==' '{' case_arm* else_arm? '}' | unary))?
|
||||||
unary = ('-' | '!' | 'xx' | 'try' | 'cast' '(' type ')') postfix
|
unary = ('-' | '!' | 'xx' | 'try' | 'cast' '(' type ')') postfix
|
||||||
| postfix
|
| postfix
|
||||||
postfix = primary ('(' args? ')' | '.' IDENT | '.{' field_init_list '}')*
|
postfix = primary ('(' args? ')' | '.' IDENT | '.{' field_init_list '}')*
|
||||||
|
|||||||
@@ -807,7 +807,7 @@ pub const LLVMEmitter = struct {
|
|||||||
std.debug.print(" {s} at {s}:{d}:{d}\n", .{ fname, file, line, col });
|
std.debug.print(" {s} at {s}:{d}:{d}\n", .{ fname, file, line, col });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std.debug.print("help: handle it at the `#run` site — `#run <expr> catch e {{ ... }}` or `#run <expr> or <default>`\n", .{});
|
std.debug.print("help: handle it at the `#run` site — `#run <expr> catch (e) {{ ... }}` or `#run <expr> or <default>`\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run comptime side-effect functions (e.g., `#run main();` at top level).
|
/// Run comptime side-effect functions (e.g., `#run main();` at top level).
|
||||||
|
|||||||
@@ -2158,7 +2158,7 @@ pub const Parser = struct {
|
|||||||
return try self.createNode(start, .{ .raise_stmt = .{ .tag = tag_expr } });
|
return try self.createNode(start, .{ .raise_stmt = .{ .tag = tag_expr } });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Onfail statement: onfail { body } | onfail e { body } | onfail <expr>;
|
// Onfail statement: onfail { body } | onfail (e) { body } | onfail <expr>;
|
||||||
// A binding is present only when an identifier is immediately followed
|
// A binding is present only when an identifier is immediately followed
|
||||||
// by `{`; otherwise the text after `onfail` is the (no-binding) body.
|
// by `{`; otherwise the text after `onfail` is the (no-binding) body.
|
||||||
if (self.current.tag == .kw_onfail) {
|
if (self.current.tag == .kw_onfail) {
|
||||||
@@ -2168,10 +2168,20 @@ pub const Parser = struct {
|
|||||||
var binding_span: ?ast.Span = null;
|
var binding_span: ?ast.Span = null;
|
||||||
var binding_is_raw = false;
|
var binding_is_raw = false;
|
||||||
if (self.current.tag == .identifier and self.peekNext() == .l_brace) {
|
if (self.current.tag == .identifier and self.peekNext() == .l_brace) {
|
||||||
|
return self.fail("the onfail error binding needs parens: `onfail (e) { ... }`");
|
||||||
|
}
|
||||||
|
// `(e)` followed by `{` is the binding form; any other paren
|
||||||
|
// group is an ordinary expression cleanup (`onfail (f());`).
|
||||||
|
if (self.current.tag == .l_paren and self.tagAfterParenGroup() == .l_brace) {
|
||||||
|
self.advance();
|
||||||
|
if (self.current.tag != .identifier) {
|
||||||
|
return self.fail("expected an error binding name in `onfail (e)`");
|
||||||
|
}
|
||||||
binding = self.tokenSlice(self.current);
|
binding = self.tokenSlice(self.current);
|
||||||
binding_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
binding_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||||
binding_is_raw = self.current.is_raw;
|
binding_is_raw = self.current.is_raw;
|
||||||
self.advance();
|
self.advance();
|
||||||
|
try self.expect(.r_paren);
|
||||||
}
|
}
|
||||||
const saved_onfail = self.in_onfail_body;
|
const saved_onfail = self.in_onfail_body;
|
||||||
self.in_onfail_body = true;
|
self.in_onfail_body = true;
|
||||||
@@ -2328,7 +2338,7 @@ pub const Parser = struct {
|
|||||||
// looking THROUGH a `try` prefix / `catch` postfix / `or` fallback
|
// looking THROUGH a `try` prefix / `catch` postfix / `or` fallback
|
||||||
// and leaving the wrapper intact:
|
// and leaving the wrapper intact:
|
||||||
// a |> try f(x) → try f(a, x)
|
// a |> try f(x) → try f(a, x)
|
||||||
// a |> f(x) catch e {...} → f(a, x) catch e {...}
|
// a |> f(x) catch (e) {...} → f(a, x) catch (e) {...}
|
||||||
// a |> f(x) or default → f(a, x) or default (only f gets a)
|
// a |> f(x) or default → f(a, x) or default (only f gets a)
|
||||||
if (self.current.tag == .pipe_arrow and Prec.pipe >= min_prec) {
|
if (self.current.tag == .pipe_arrow and Prec.pipe >= min_prec) {
|
||||||
self.advance();
|
self.advance();
|
||||||
@@ -2629,21 +2639,29 @@ pub const Parser = struct {
|
|||||||
self.advance();
|
self.advance();
|
||||||
expr = try self.createNode(expr.span.start, .{ .force_unwrap = .{ .operand = expr } });
|
expr = try self.createNode(expr.span.start, .{ .force_unwrap = .{ .operand = expr } });
|
||||||
} else if (self.current.tag == .kw_catch) {
|
} else if (self.current.tag == .kw_catch) {
|
||||||
// `X catch [binding] BODY` — postfix failure handler.
|
// `X catch [(binding)] BODY` — postfix failure handler.
|
||||||
// Four shapes, disambiguated by peeking after `catch`:
|
// Four shapes, disambiguated by peeking after `catch`:
|
||||||
// catch { block } — no binding (braces required)
|
// catch { block } — no binding (braces required)
|
||||||
// catch e { block } — binding + block body
|
// catch (e) { block } — binding + block body
|
||||||
// catch e == { case ... } — binding + match body (sugar)
|
// catch (e) == { case ... } — binding + match body (sugar)
|
||||||
// catch e EXPR — binding + bare-expression body
|
// catch (e) EXPR — binding + bare-expression body
|
||||||
self.advance(); // consume 'catch'
|
self.advance(); // consume 'catch'
|
||||||
var binding: ?[]const u8 = null;
|
var binding: ?[]const u8 = null;
|
||||||
var binding_span: ?ast.Span = null;
|
var binding_span: ?ast.Span = null;
|
||||||
var binding_is_raw = false;
|
var binding_is_raw = false;
|
||||||
if (self.current.tag == .identifier) {
|
if (self.current.tag == .identifier) {
|
||||||
|
return self.fail("the catch error binding needs parens: `catch (e) { ... }`");
|
||||||
|
}
|
||||||
|
if (self.current.tag == .l_paren) {
|
||||||
|
self.advance();
|
||||||
|
if (self.current.tag != .identifier) {
|
||||||
|
return self.fail("expected an error binding name in `catch (e)`");
|
||||||
|
}
|
||||||
binding = self.tokenSlice(self.current);
|
binding = self.tokenSlice(self.current);
|
||||||
binding_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
binding_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||||
binding_is_raw = self.current.is_raw;
|
binding_is_raw = self.current.is_raw;
|
||||||
self.advance();
|
self.advance();
|
||||||
|
try self.expect(.r_paren);
|
||||||
}
|
}
|
||||||
var is_match_body = false;
|
var is_match_body = false;
|
||||||
const body: *Node = if (self.current.tag == .l_brace)
|
const body: *Node = if (self.current.tag == .l_brace)
|
||||||
@@ -4533,7 +4551,7 @@ test "E0.2 catch no binding, braced body" {
|
|||||||
test "E0.2 catch with binding, block body" {
|
test "E0.2 catch with binding, block body" {
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := foo() catch e { bar(); }; }");
|
const v = try e02FirstValue(arena.allocator(), "f :: () { v := foo() catch (e) { bar(); }; }");
|
||||||
try std.testing.expect(v.data == .catch_expr);
|
try std.testing.expect(v.data == .catch_expr);
|
||||||
try std.testing.expectEqualStrings("e", v.data.catch_expr.binding.?);
|
try std.testing.expectEqualStrings("e", v.data.catch_expr.binding.?);
|
||||||
try std.testing.expect(v.data.catch_expr.body.data == .block);
|
try std.testing.expect(v.data.catch_expr.body.data == .block);
|
||||||
@@ -4542,7 +4560,7 @@ test "E0.2 catch with binding, block body" {
|
|||||||
test "E0.2 catch with binding, bare-expression body" {
|
test "E0.2 catch with binding, bare-expression body" {
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := foo() catch e bar(); }");
|
const v = try e02FirstValue(arena.allocator(), "f :: () { v := foo() catch (e) bar(); }");
|
||||||
try std.testing.expect(v.data == .catch_expr);
|
try std.testing.expect(v.data == .catch_expr);
|
||||||
try std.testing.expectEqualStrings("e", v.data.catch_expr.binding.?);
|
try std.testing.expectEqualStrings("e", v.data.catch_expr.binding.?);
|
||||||
try std.testing.expect(v.data.catch_expr.is_match_body == false);
|
try std.testing.expect(v.data.catch_expr.is_match_body == false);
|
||||||
@@ -4552,7 +4570,7 @@ test "E0.2 catch with binding, bare-expression body" {
|
|||||||
test "E0.2 catch match-body desugars to match_expr over the binding" {
|
test "E0.2 catch match-body desugars to match_expr over the binding" {
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := foo() catch e == { case .Empty: 0; else: 1; }; }");
|
const v = try e02FirstValue(arena.allocator(), "f :: () { v := foo() catch (e) == { case .Empty: 0; else: 1; }; }");
|
||||||
try std.testing.expect(v.data == .catch_expr);
|
try std.testing.expect(v.data == .catch_expr);
|
||||||
try std.testing.expect(v.data.catch_expr.is_match_body);
|
try std.testing.expect(v.data.catch_expr.is_match_body);
|
||||||
try std.testing.expect(v.data.catch_expr.body.data == .match_expr);
|
try std.testing.expect(v.data.catch_expr.body.data == .match_expr);
|
||||||
@@ -4565,7 +4583,7 @@ test "E0.2 catch match-body desugars to match_expr over the binding" {
|
|||||||
test "E0.2 catch over a parenthesized or-chain" {
|
test "E0.2 catch over a parenthesized or-chain" {
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := (try foo() or try boo()) catch e { }; }");
|
const v = try e02FirstValue(arena.allocator(), "f :: () { v := (try foo() or try boo()) catch (e) { }; }");
|
||||||
try std.testing.expect(v.data == .catch_expr);
|
try std.testing.expect(v.data == .catch_expr);
|
||||||
try std.testing.expect(v.data.catch_expr.operand.data == .binary_op);
|
try std.testing.expect(v.data.catch_expr.operand.data == .binary_op);
|
||||||
try std.testing.expect(v.data.catch_expr.operand.data.binary_op.op == .or_op);
|
try std.testing.expect(v.data.catch_expr.operand.data.binary_op.op == .or_op);
|
||||||
@@ -4645,7 +4663,7 @@ test "E1.7 break rejected inside a defer body (transitive through a loop)" {
|
|||||||
test "E1.7 continue rejected inside an onfail body" {
|
test "E1.7 continue rejected inside an onfail body" {
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
var parser = Parser.init(arena.allocator(), "f :: () { onfail e { continue; } }");
|
var parser = Parser.init(arena.allocator(), "f :: () { onfail (e) { continue; } }");
|
||||||
try std.testing.expectError(error.ParseError, parser.parse());
|
try std.testing.expectError(error.ParseError, parser.parse());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4669,7 +4687,7 @@ test "E1.7 control-flow legal again after the cleanup body (flag restored)" {
|
|||||||
test "E0.2 onfail with binding and block body" {
|
test "E0.2 onfail with binding and block body" {
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const s = try e02FirstStmt(arena.allocator(), "f :: () { onfail e { close(h); } }");
|
const s = try e02FirstStmt(arena.allocator(), "f :: () { onfail (e) { close(h); } }");
|
||||||
try std.testing.expect(s.data == .onfail_stmt);
|
try std.testing.expect(s.data == .onfail_stmt);
|
||||||
try std.testing.expectEqualStrings("e", s.data.onfail_stmt.binding.?);
|
try std.testing.expectEqualStrings("e", s.data.onfail_stmt.binding.?);
|
||||||
try std.testing.expect(s.data.onfail_stmt.body.data == .block);
|
try std.testing.expect(s.data.onfail_stmt.body.data == .block);
|
||||||
@@ -4701,10 +4719,10 @@ test "E0.2 consumer-aware pipe: x |> try f() inserts x into the head call" {
|
|||||||
try std.testing.expectEqualStrings("x", call.data.call.args[0].data.identifier.name);
|
try std.testing.expectEqualStrings("x", call.data.call.args[0].data.identifier.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "E0.2 consumer-aware pipe: x |> f() catch e { } preserves the catch" {
|
test "E0.2 consumer-aware pipe: x |> f() catch (e) { } preserves the catch" {
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const v = try e02FirstValue(arena.allocator(), "f :: () { v := x |> g() catch e { }; }");
|
const v = try e02FirstValue(arena.allocator(), "f :: () { v := x |> g() catch (e) { }; }");
|
||||||
try std.testing.expect(v.data == .catch_expr);
|
try std.testing.expect(v.data == .catch_expr);
|
||||||
try std.testing.expectEqualStrings("e", v.data.catch_expr.binding.?);
|
try std.testing.expectEqualStrings("e", v.data.catch_expr.binding.?);
|
||||||
try std.testing.expect(v.data.catch_expr.operand.data == .call);
|
try std.testing.expect(v.data.catch_expr.operand.data == .call);
|
||||||
@@ -4741,19 +4759,19 @@ test "E0.2 round-trip print: try / or precedence / raise / catch / onfail" {
|
|||||||
try e02ExpectPrints(a, try e02FirstValue(a, "f :: () { v := try foo() or try boo(); }"), "try foo() or try boo()");
|
try e02ExpectPrints(a, try e02FirstValue(a, "f :: () { v := try foo() or try boo(); }"), "try foo() or try boo()");
|
||||||
try e02ExpectPrints(a, try e02FirstStmt(a, "f :: () { raise error.BadDigit; }"), "raise error.BadDigit");
|
try e02ExpectPrints(a, try e02FirstStmt(a, "f :: () { raise error.BadDigit; }"), "raise error.BadDigit");
|
||||||
try e02ExpectPrints(a, try e02FirstStmt(a, "f :: () { raise e; }"), "raise e");
|
try e02ExpectPrints(a, try e02FirstStmt(a, "f :: () { raise e; }"), "raise e");
|
||||||
try e02ExpectPrints(a, try e02FirstValue(a, "f :: () { v := foo() catch e bar(); }"), "foo() catch e bar()");
|
try e02ExpectPrints(a, try e02FirstValue(a, "f :: () { v := foo() catch (e) bar(); }"), "foo() catch (e) bar()");
|
||||||
try e02ExpectPrints(a, try e02FirstValue(a, "f :: () { v := foo() catch e { bar(); }; }"), "foo() catch e { bar(); }");
|
try e02ExpectPrints(a, try e02FirstValue(a, "f :: () { v := foo() catch (e) { bar(); }; }"), "foo() catch (e) { bar(); }");
|
||||||
try e02ExpectPrints(a, try e02FirstValue(a, "f :: () { v := foo() catch { bar(); }; }"), "foo() catch { bar(); }");
|
try e02ExpectPrints(a, try e02FirstValue(a, "f :: () { v := foo() catch { bar(); }; }"), "foo() catch { bar(); }");
|
||||||
try e02ExpectPrints(a, try e02FirstStmt(a, "f :: () { onfail close(h); }"), "onfail close(h)");
|
try e02ExpectPrints(a, try e02FirstStmt(a, "f :: () { onfail close(h); }"), "onfail close(h)");
|
||||||
try e02ExpectPrints(a, try e02FirstStmt(a, "f :: () { onfail e { close(h); } }"), "onfail e { close(h); }");
|
try e02ExpectPrints(a, try e02FirstStmt(a, "f :: () { onfail (e) { close(h); } }"), "onfail (e) { close(h); }");
|
||||||
}
|
}
|
||||||
|
|
||||||
test "E0.2 round-trip print: catch match-body form" {
|
test "E0.2 round-trip print: catch match-body form" {
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const a = arena.allocator();
|
const a = arena.allocator();
|
||||||
const v = try e02FirstValue(a, "f :: () { v := foo() catch e == { case .Empty: 0; else: 1; }; }");
|
const v = try e02FirstValue(a, "f :: () { v := foo() catch (e) == { case .Empty: 0; else: 1; }; }");
|
||||||
try e02ExpectPrints(a, v, "foo() catch e == { case .Empty: 0; else: 1; }");
|
try e02ExpectPrints(a, v, "foo() catch (e) == { case .Empty: 0; else: 1; }");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── ERR step E0.3 — coverage consolidation (gaps + integration) ──
|
// ── ERR step E0.3 — coverage consolidation (gaps + integration) ──
|
||||||
@@ -4788,9 +4806,9 @@ test "E0.3 or value-terminator: parse(s) or 0" {
|
|||||||
test "E0.3 full failable function parses end-to-end (all E0 forms)" {
|
test "E0.3 full failable function parses end-to-end (all E0 forms)" {
|
||||||
const source =
|
const source =
|
||||||
\\parse :: (s: string) -> (s32, !ParseErr) {
|
\\parse :: (s: string) -> (s32, !ParseErr) {
|
||||||
\\ onfail e { cleanup(s); }
|
\\ onfail (e) { cleanup(s); }
|
||||||
\\ v := try inner(s) or 0;
|
\\ v := try inner(s) or 0;
|
||||||
\\ w := other(s) catch e2 { return 0; };
|
\\ w := other(s) catch (e2) { return 0; };
|
||||||
\\ if bad(s) { raise error.BadDigit; }
|
\\ if bad(s) { raise error.BadDigit; }
|
||||||
\\ return v;
|
\\ return v;
|
||||||
\\}
|
\\}
|
||||||
|
|||||||
@@ -77,8 +77,9 @@ pub fn printExpr(node: *const Node, writer: Writer) anyerror!void {
|
|||||||
try printExpr(c.operand, writer);
|
try printExpr(c.operand, writer);
|
||||||
try writer.writeAll(" catch");
|
try writer.writeAll(" catch");
|
||||||
if (c.binding) |bnd| {
|
if (c.binding) |bnd| {
|
||||||
try writer.writeByte(' ');
|
try writer.writeAll(" (");
|
||||||
try writer.writeAll(bnd);
|
try writer.writeAll(bnd);
|
||||||
|
try writer.writeByte(')');
|
||||||
}
|
}
|
||||||
if (c.is_match_body) {
|
if (c.is_match_body) {
|
||||||
try writer.writeAll(" == ");
|
try writer.writeAll(" == ");
|
||||||
@@ -95,8 +96,9 @@ pub fn printExpr(node: *const Node, writer: Writer) anyerror!void {
|
|||||||
.onfail_stmt => |o| {
|
.onfail_stmt => |o| {
|
||||||
try writer.writeAll("onfail");
|
try writer.writeAll("onfail");
|
||||||
if (o.binding) |bnd| {
|
if (o.binding) |bnd| {
|
||||||
try writer.writeByte(' ');
|
try writer.writeAll(" (");
|
||||||
try writer.writeAll(bnd);
|
try writer.writeAll(bnd);
|
||||||
|
try writer.writeByte(')');
|
||||||
}
|
}
|
||||||
try writer.writeByte(' ');
|
try writer.writeByte(' ');
|
||||||
try printExpr(o.body, writer);
|
try printExpr(o.body, writer);
|
||||||
|
|||||||
Reference in New Issue
Block a user