feat(errors): panic(msg) + assert(cond, msg) with file:line — no raw crashes (#8)
RFC decision (the split the issue recommended): programmer bugs abort loud and located; recoverable failures become values. This ships the first half. panic(msg) prints `file:line: panic: <msg>` to stderr and aborts the process with exit code 1 — a clear, located error instead of a segfault or a silent wrong result. assert(cond, msg) is the guarded form: it aborts with `file:line: assertion failed: <msg>` only when cond is false, otherwise execution continues. The location is baked in at compile time (the call node carries its source line, g_src_name carries the file); the message is any string. Both lower in emit_call to an fprintf-to-stderr + exit(1) + unreachable tail (assert branches on the condition first). @fprintf and the format constant are declared on demand (g_uses_panic), so a program that never panics is unchanged — and the compiler's own source uses neither, so the C-free bootstrap fixpoint holds. - panic/assert registered as builtins across the vocabulary (ludic_syntax.h, the JetBrains lexer, the TextMate grammar) and documented (docs/language/builtins/) - examples/library/errors.ludic covers the success path (asserts hold, program runs to the end); a panic_case in the suite covers the failure path (non-zero exit + the located stderr message). x test is now 69 checks. Deferred: recoverable failures as `try`/`else` values (needs the tagged-union type system, #1) and a top-level `recover` for the dev game loop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ea2c6ab246
commit
c7c8e2779c
13 changed files with 5102 additions and 4778 deletions
30
docs/language/builtins/fn-assert.md
Normal file
30
docs/language/builtins/fn-assert.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
id: fn-assert
|
||||
name: assert
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: assert
|
||||
sig: assert(cond: bool, msg: string)
|
||||
tip: Abort with a located message when an invariant is false.
|
||||
order: 11
|
||||
---
|
||||
|
||||
When <code>cond</code> is false, prints <code>file:line: assertion failed: <msg></code> to standard error and aborts (exit code 1); when it is true, execution continues. It is the guarded form of <a href="fn-panic"><code>panic</code></a> — for the programmer-bug cases the language should catch loudly rather than let corrupt the world: an index that must be in range, a value that must be non-negative, a state that must hold before a step. The location is baked in at compile time, so a failure points at the exact assertion. Assertions document and enforce the invariants a system relies on; keep them for "this must be true" checks, not for recoverable runtime conditions.
|
||||
|
||||
Parameters:
|
||||
- `cond` — the invariant that must hold (a bool)
|
||||
- `msg` — a message describing the invariant (a string)
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
function withdraw(balance: int, amount: int) -> int {
|
||||
assert(amount >= 0, "amount must be non-negative")
|
||||
assert(amount <= balance, "cannot overdraw")
|
||||
return balance - amount
|
||||
}
|
||||
entry {
|
||||
print(withdraw(100, 30)) # 70
|
||||
print(withdraw(100, 250)) # aborts: Demo.ludic:4: assertion failed: cannot overdraw
|
||||
}
|
||||
}
|
||||
```
|
||||
31
docs/language/builtins/fn-panic.md
Normal file
31
docs/language/builtins/fn-panic.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
id: fn-panic
|
||||
name: panic
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: panic
|
||||
sig: panic(msg: string)
|
||||
tip: Abort with a located error message instead of crashing.
|
||||
order: 10
|
||||
---
|
||||
|
||||
Prints <code>file:line: panic: <msg></code> to standard error and aborts the process with exit code 1. It turns "something went wrong" into a clear, located message a non-expert can act on — the opposite of a raw segfault or a silent wrong result. Use it for the unrecoverable case: a corrupt asset, a broken invariant, a branch that should be impossible. The source location is baked in at compile time, so the message always points at the exact call. For a guarded check that only aborts when a condition fails, use <a href="fn-assert"><code>assert</code></a>.
|
||||
|
||||
Recoverable failures — a missing save, a bad network packet — are a different story: those should be values a game can fall back from rather than a `panic`. That value-carrying `try`/`else` path is sequenced after the type-system work (tagged unions); `panic` covers the programmer-bug half today.
|
||||
|
||||
Parameters:
|
||||
- `msg` — a message describing what went wrong (a string)
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
function pick(options: int, n: int) -> int {
|
||||
if n < 0 { panic("choice index is negative") }
|
||||
if n >= options { panic("choice index out of range") }
|
||||
return n
|
||||
}
|
||||
entry {
|
||||
print(pick(3, 1)) # 1
|
||||
print(pick(3, 9)) # aborts: Demo.ludic:6: panic: choice index out of range
|
||||
}
|
||||
}
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue