ERR/E5.3: specs.md §12 Error Handling (fold locked design into spec)
Add a top-level §12 Error Handling distilling the locked error design +
surface syntax: failable signatures (-> (T,!) / -> ! / multi-value),
named `error { }` + inferred `!` sets, raise/try/catch/or/onfail, the
path-marker rule, set widening, error.X as a value, discard rejection +
flow-check, closures-with-!, return traces, and the u32-last-slot ABI.
Renumber Grammar §12→§13 and Open Questions §13→§14 (insert sits after
§10.5, so §3/§10.5 — the only section numbers referenced from CLAUDE.md
— stay valid). Cross-link the `!` channel from the Keywords list,
Operator Precedence, Function Definition, and §11 Program Structure;
extend the §13 grammar with error_decl, raise_stmt, onfail_stmt, a
catch_expr tier, `try` in unary, and failable type productions.
Pure docs; no compiler change. Gates: build, test, run_examples (293/0).
This commit is contained in:
346
specs.md
346
specs.md
@@ -45,10 +45,12 @@ GLSL;
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Keywords
|
### Keywords
|
||||||
`if`, `else`, `then`, `while`, `for`, `break`, `continue`, `true`, `false`, `enum`, `struct`, `union`, `case`, `return`, `defer`, `push`, `ufcs`, `in`, `xx`, `and`, `or`
|
`if`, `else`, `then`, `while`, `for`, `break`, `continue`, `true`, `false`, `enum`, `struct`, `union`, `case`, `return`, `defer`, `push`, `ufcs`, `in`, `xx`, `and`, `or`, `raise`, `try`, `catch`, `onfail`, `error`
|
||||||
|
|
||||||
> Note: `enum` is used for both payload-less and payload-bearing sum types (tagged unions). `union` is reserved for C-style untagged unions (memory overlays).
|
> Note: `enum` is used for both payload-less and payload-bearing sum types (tagged unions). `union` is reserved for C-style untagged unions (memory overlays).
|
||||||
|
|
||||||
|
> Note: `raise`, `try`, `catch`, `onfail`, and `error` are the error-handling keywords. `or` is reused as the failable-fallback / chain operator. See [§12 Error Handling](#12-error-handling).
|
||||||
|
|
||||||
### Operators
|
### Operators
|
||||||
|
|
||||||
| Operator | Meaning |
|
| Operator | Meaning |
|
||||||
@@ -1227,10 +1229,15 @@ name :: (params) -> return_type {
|
|||||||
```
|
```
|
||||||
|
|
||||||
- Parameters: `name: type` separated by commas
|
- Parameters: `name: type` separated by commas
|
||||||
- Return type: `-> type` (omit for void)
|
- Return type: `-> type` (omit for void). A multi-value return is a tuple: `-> (T1, T2)`.
|
||||||
- Body: block of statements; last expression is the implicit return value
|
- Body: block of statements; last expression is the implicit return value
|
||||||
- No `return` keyword needed (last expression = return value)
|
- No `return` keyword needed (last expression = return value)
|
||||||
|
|
||||||
|
A trailing `!` in the return type marks the function **failable** — it adds a
|
||||||
|
separate error channel alongside the normal returns (`-> (T, !)`, `-> !`,
|
||||||
|
`-> (T1, T2, !)`). The `!` is not a wrapper around the value; it is one more
|
||||||
|
return slot. See [§12 Error Handling](#12-error-handling).
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```sx
|
```sx
|
||||||
compute :: (x: s32) -> s32 {
|
compute :: (x: s32) -> s32 {
|
||||||
@@ -1421,7 +1428,12 @@ Everything in `sx` is expression-oriented where possible.
|
|||||||
| 4 | `^` | bitwise XOR |
|
| 4 | `^` | bitwise XOR |
|
||||||
| 3 | `\|` | bitwise OR |
|
| 3 | `\|` | bitwise OR |
|
||||||
| 2 | `and` | logical AND (short-circuit) |
|
| 2 | `and` | logical AND (short-circuit) |
|
||||||
| 1 (lowest) | `or` | logical OR (short-circuit) |
|
| 1 (lowest) | `or` | logical OR (short-circuit) / failable fallback (§12) |
|
||||||
|
|
||||||
|
`try` is a unary prefix in the same tier as `xx` / `@` / `-` / `!` / `~`
|
||||||
|
(tighter than every binary operator, including `or`); `catch` is a postfix
|
||||||
|
attached to a failable expression. So `try foo() or try boo()` parses as
|
||||||
|
`(try foo()) or (try boo())`. See [§12 Error Handling](#12-error-handling).
|
||||||
|
|
||||||
### Arithmetic
|
### Arithmetic
|
||||||
Standard infix: `+`, `-`, `*`, `/` with usual precedence (`*`/`/` before `+`/`-`).
|
Standard infix: `+`, `-`, `*`, `/` with usual precedence (`*`/`/` before `+`/`-`).
|
||||||
@@ -2274,18 +2286,327 @@ main :: () {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`main` takes no arguments and returns void. The process exit code is 0 unless otherwise specified.
|
`main` takes no arguments. Its return type may be any of: void (`()`,
|
||||||
|
`-> ()`, `-> void`, or no annotation), an integer type (POSIX exit code),
|
||||||
|
`-> !` (pure failable), or `-> (int_type, !)` (value-carrying failable).
|
||||||
|
The exit code is `0` for void / `-> !` success, the integer return
|
||||||
|
truncated to `u8` otherwise. An error that escapes a failable `main`
|
||||||
|
prints the unhandled-error header + return trace to stderr and exits `1`.
|
||||||
|
See [§12 Error Handling](#12-error-handling).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12. Grammar (informal)
|
## 12. Error Handling
|
||||||
|
|
||||||
|
sx models recoverable errors as a **separate return channel**, not a wrapped
|
||||||
|
result type. A trailing `!` in a function's return type adds one extra return
|
||||||
|
slot — a `u32` error tag — alongside the normal value slots. This keeps sx's
|
||||||
|
native multi-return ergonomics: `-> (s32, s64, !)` is a function returning two
|
||||||
|
values *and* an error, with no tuple-in-a-wrapper.
|
||||||
|
|
||||||
|
This section is the canonical surface reference. The design rationale,
|
||||||
|
trade-offs, and implementation breakdown live in `current/PLAN-ERR.md`.
|
||||||
|
|
||||||
|
### Failable signatures
|
||||||
|
|
||||||
|
```sx
|
||||||
|
parse_digit :: (s: string) -> (s32, !) { ... } // one value + error
|
||||||
|
parse :: (s: string) -> (s32, s64, !) { ... } // multi-value + error
|
||||||
|
must_init :: () -> ! { ... } // pure failable, no value
|
||||||
|
divide :: (a: s32, b: s32) -> (s32, !MathErr) { ... } // named set
|
||||||
|
```
|
||||||
|
|
||||||
|
The `!` is always the **last** slot. `0` in the error slot means "no error";
|
||||||
|
non-zero is an interned global tag id.
|
||||||
|
|
||||||
|
### Error sets
|
||||||
|
|
||||||
|
Two forms of error set:
|
||||||
|
|
||||||
|
```sx
|
||||||
|
// Named set — declared once, referenced by name from signatures.
|
||||||
|
ParseErr :: error { BadDigit, Overflow, Empty };
|
||||||
|
|
||||||
|
// Inferred set — bare `!` collects whatever tags the body raises.
|
||||||
|
quick :: () -> (s32, !) {
|
||||||
|
if cond raise error.SomeAdHocTag; // mints into the inferred set
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- An `error { ... }` set is an opaque type; tags are referenced as `error.X`.
|
||||||
|
- A declared empty set `error { }` is **rejected**.
|
||||||
|
- **Inferred sets are whole-program.** The compiler runs an SCC fix-point pass
|
||||||
|
over the entire call graph to converge each bare-`!` function's set
|
||||||
|
(matching sx's whole-program compilation model). Callers see the converged
|
||||||
|
union, not bare `!`.
|
||||||
|
- A top-level (non-`main`) function declared `!` that never errors warns
|
||||||
|
("declared `!` but never errors — drop the `!`"). Closures and
|
||||||
|
function-type slots with an empty `!` do **not** warn.
|
||||||
|
|
||||||
|
**Tag identity is the name, globally (Zig-style).** Two sets that both list
|
||||||
|
`NotFound` reference the *same* tag id; `if e == error.NotFound` matches every
|
||||||
|
`NotFound` regardless of which set raised it. Use distinct names
|
||||||
|
(`FsNotFound` / `HttpNotFound`) when subsystems must be distinguishable.
|
||||||
|
|
||||||
|
### `raise`
|
||||||
|
|
||||||
|
Statement form. Terminates the immediately enclosing failable function (like
|
||||||
|
`return`), setting the error slot; value slots are left undefined.
|
||||||
|
|
||||||
|
```sx
|
||||||
|
if bad raise error.BadDigit; // literal tag
|
||||||
|
|
||||||
|
v := foo() catch e {
|
||||||
|
if e == error.Specific return default;
|
||||||
|
raise e; // variable tag — re-raise
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
`raise EXPR` accepts any tag-typed expression. EXPR's set must be ⊆ the
|
||||||
|
enclosing function's error set (for a named set), or is absorbed into the
|
||||||
|
inferred set (for bare `!`). `raise` inside an inline expression is rejected
|
||||||
|
(`v := if cond raise error.X else 0;` — compile error). A closure body is its
|
||||||
|
own function boundary: `raise` inside a closure terminates the *closure*.
|
||||||
|
|
||||||
|
### `try`
|
||||||
|
|
||||||
|
Expression form. `try X` requires `X` to be failable; on `X`'s failure it
|
||||||
|
routes control to the nearest enclosing fallback target:
|
||||||
|
|
||||||
|
- inside an `or` chain → the next `or` operand;
|
||||||
|
- otherwise → the function's error return (propagation, like Zig's `try`).
|
||||||
|
|
||||||
|
```sx
|
||||||
|
v := try parse_digit(s); // propagate on failure
|
||||||
|
v2, n := try parse(s); // multi-value
|
||||||
|
try must_init(); // statement form, discard values
|
||||||
|
v3 := try foo() or try bar(); // chain: foo fails → try bar
|
||||||
|
return try transform(try parse(s)); // nests in any value position
|
||||||
|
```
|
||||||
|
|
||||||
|
`try` works in any value-producing position (argument, struct/array literal,
|
||||||
|
`if`-condition); evaluation is left-to-right and short-circuits on the first
|
||||||
|
failure, so no partial aggregate is ever built. `try`'s body never binds the
|
||||||
|
tag — use `catch` for that.
|
||||||
|
|
||||||
|
### `catch`
|
||||||
|
|
||||||
|
Expression form. Handles the error inline. The binding is a **bare name, no
|
||||||
|
parens** (`catch e`), and is **optional**. Four shapes, disambiguated by the
|
||||||
|
token after `catch`:
|
||||||
|
|
||||||
|
| Form | Binding | Body |
|
||||||
|
|---|---|---|
|
||||||
|
| `catch { ... }` | none (tag ignored) | block — braces required |
|
||||||
|
| `catch e { ... }` | `e` | block |
|
||||||
|
| `catch e EXPR` | `e` | bare expression (no braces) |
|
||||||
|
| `catch e == { case ... }` | `e` | match over `e` (sugar for `{ if e == { ... } }`) |
|
||||||
|
|
||||||
|
```sx
|
||||||
|
v := parse_digit(s) catch e {
|
||||||
|
log.warn("bad input: {}", e);
|
||||||
|
return default; // noreturn body
|
||||||
|
};
|
||||||
|
|
||||||
|
v := parse_digit(s) catch e compute_fallback(e); // value-producing body
|
||||||
|
|
||||||
|
v, n := parse(s) catch e {
|
||||||
|
log.warn("parse failed: {}", e);
|
||||||
|
(0, 0) // tuple body for a multi-value failable
|
||||||
|
};
|
||||||
|
|
||||||
|
v := parse(s) catch e == { // match-body form
|
||||||
|
case .Empty: 0;
|
||||||
|
case .BadDigit: -1;
|
||||||
|
else: raise e;
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
success tuple type, or be `noreturn` (the `noreturn` arm subsumes `return` /
|
||||||
|
`raise` / `break` / `continue` / `unreachable` / noreturn calls). For a
|
||||||
|
multi-value failable the body must produce a tuple of matching arity and
|
||||||
|
element types. A non-diverging body that produces no value is a compile error.
|
||||||
|
|
||||||
|
### `or` (fallback / chain)
|
||||||
|
|
||||||
|
Expression form (the same operator as optional-unwrap). LHS must be failable;
|
||||||
|
the RHS shape decides the result:
|
||||||
|
|
||||||
|
- **plain value of the success type** — terminate; the chain becomes
|
||||||
|
non-failable; on LHS failure the result is the RHS value (LHS tag discarded);
|
||||||
|
- **`try EXPR`** — chain; on LHS failure, attempt the RHS (its `try` defines
|
||||||
|
the next fallback target);
|
||||||
|
- **bare failable** — allowed only when its error path hits a marker
|
||||||
|
downstream (see the path-marker rule).
|
||||||
|
|
||||||
|
`or` is **left-associative**, evaluated left-to-right with short-circuit.
|
||||||
|
|
||||||
|
```sx
|
||||||
|
v := parse_digit(s) or 0; // value terminator → non-failable
|
||||||
|
v := try foo() or try boo(); // chain, propagate if both fail
|
||||||
|
v := foo() or boo() or 0; // bare operands, 0 absorbs all
|
||||||
|
v, n := parse_pair(s) or (0, 0); // tuple terminator (multi-value)
|
||||||
|
```
|
||||||
|
|
||||||
|
A **void** failable (`-> !`) rejects a plain-value RHS (no success type to
|
||||||
|
fall back to); `must_init() or must_other()` (chain) and `must_init() catch {}`
|
||||||
|
(absorb) are the legal forms.
|
||||||
|
|
||||||
|
### Path-marker rule
|
||||||
|
|
||||||
|
A failable expression `X` may appear **bare** (no `try`) iff its error path
|
||||||
|
passes through at least one explicit marker before reaching the function
|
||||||
|
boundary. The markers are: a `try` keyword, a `catch` handler, an `or` value
|
||||||
|
terminator, or a destructure binding (`v, err := X`). Otherwise `try` (or one
|
||||||
|
of the other markers directly on `X`) is required.
|
||||||
|
|
||||||
|
```sx
|
||||||
|
a := parse(s) or 0; // OK — terminator on the path
|
||||||
|
a := parse(s) catch e {...}; // OK — catch marks
|
||||||
|
v, err := failable(); // OK — destructure marks
|
||||||
|
a := try foo() or try boo(); // OK — each try marks its own exit
|
||||||
|
|
||||||
|
a := foo() or boo(); // ERROR — no marker on the way to the function
|
||||||
|
a := foo(); // ERROR — bare, no marker downstream
|
||||||
|
```
|
||||||
|
|
||||||
|
### Set widening
|
||||||
|
|
||||||
|
Widening is checked **only at subexpressions whose failure escapes to the
|
||||||
|
function** (propagation). For a **named** caller `!CallerErr`, the escape set
|
||||||
|
must be ⊆ `CallerErr` (no auto-widening). For an **inferred** caller `!`, the
|
||||||
|
escape set is absorbed into the converged union. Failures absorbed by a
|
||||||
|
downstream chain operand / `catch` / terminator / destructure don't contribute.
|
||||||
|
|
||||||
|
### `error.X` as a value
|
||||||
|
|
||||||
|
`error.X` is a first-class value outside `raise`:
|
||||||
|
|
||||||
|
```sx
|
||||||
|
default_err : ParseErr = error.BadDigit; // typed as the named set
|
||||||
|
tag_id : u32 = error.BadDigit; // untyped context → global tag id
|
||||||
|
if e == error.Empty { ... } // compare against a literal
|
||||||
|
```
|
||||||
|
|
||||||
|
- Against a **named-set** destination, `error.X` is valid only if `X ∈` the set
|
||||||
|
(typo-checked). A comparison to a literal not in the set is a compile error
|
||||||
|
(it could never be true). For **inferred** sets this check is skipped.
|
||||||
|
- An error-set value compares (`==` / `!=`) only with an `error.X` literal or
|
||||||
|
another error-set value — **never a raw integer** (`e == 42` is rejected).
|
||||||
|
Coerce explicitly (`(xx e) == id`) to use the raw id.
|
||||||
|
- **Interpolation renders the tag name.** `{}` on an error-set value prints the
|
||||||
|
tag name (`BadDigit`), never the raw id, via a tag-name table that is
|
||||||
|
**always linked, even in release builds**.
|
||||||
|
|
||||||
|
### Discard rejection & flow-check
|
||||||
|
|
||||||
|
Dropping the error slot is a compile error:
|
||||||
|
|
||||||
|
```sx
|
||||||
|
v, _ := failable(); // ERROR: the error slot cannot be dropped — handle it
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
value-carrying failable, the value slot is live only where the compiler can
|
||||||
|
prove the error slot is null (path-sensitive flow-check).
|
||||||
|
|
||||||
|
### `onfail` (error-path cleanup)
|
||||||
|
|
||||||
|
Statement form. Block-rooted (Zig-aligned): legal in any block inside a
|
||||||
|
failable function. **Fires when an error propagates out of its enclosing
|
||||||
|
block**, regardless of whether an outer `catch` / terminator later absorbs it.
|
||||||
|
On success exit (fall-through, `return`, `break` / `continue` without an error)
|
||||||
|
it is skipped — only `defer` runs.
|
||||||
|
|
||||||
|
```sx
|
||||||
|
make_handle :: () -> (Handle, !) {
|
||||||
|
h := try open();
|
||||||
|
onfail close(h); // close ONLY on a subsequent failure
|
||||||
|
try configure(h); // fails → onfail runs → close(h)
|
||||||
|
return h; // success → onfail skipped; caller owns h
|
||||||
|
}
|
||||||
|
|
||||||
|
open :: (path: string) -> (Handle, !) {
|
||||||
|
h := try sys_open(path);
|
||||||
|
onfail e { log.warn("init failed for {}: {}", path, e); sys_close(h); }
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ordering with `defer`.** Both run in reverse declaration order, interleaved.
|
||||||
|
On block-error exit both kinds run (newest-first); on block-success exit only
|
||||||
|
`defer`s run.
|
||||||
|
|
||||||
|
**Restrictions.** `raise` / `try` / `return` / `break` / `continue` are
|
||||||
|
rejected inside an `onfail` (and a `defer`) body — a cleanup body has no
|
||||||
|
control-transfer target. A failable call in cleanup must be absorbed locally
|
||||||
|
(`close(h) catch {};` or `flush(buf) or 0`). `onfail` outside a failable
|
||||||
|
function, or at top level, is rejected.
|
||||||
|
|
||||||
|
### Closures with `!`
|
||||||
|
|
||||||
|
- **Explicit annotation required.** A closure literal's value type is inferred
|
||||||
|
as today, but if its body raises or `try`-escapes, the `!` channel is **not**
|
||||||
|
inferred — declare it (`closure((x: s32) -> (s32, !) { ... })`). This keeps
|
||||||
|
adding a `raise` from silently changing a lambda's type.
|
||||||
|
- **Program-wide union per shape.** All `Closure(<sig>) -> (T, !)` occurrences
|
||||||
|
with the same signature share one inferred-set node; the SCC pass unions
|
||||||
|
every closure flowing into any matching slot.
|
||||||
|
- **FFI boundary.** A failable closure cannot be assigned to a non-failable
|
||||||
|
function-type slot — foreign code can't observe the error channel. Wrap and
|
||||||
|
absorb the error instead.
|
||||||
|
- **Non-failable → failable widening is allowed** (∅ ⊆ any set). A
|
||||||
|
non-failable closure assigned to a failable slot contributes ∅; a single
|
||||||
|
coalesced adapter thunk `(v) → (v, 0)` reconciles the 1-slot vs 2-slot ABI at
|
||||||
|
the crossing point.
|
||||||
|
|
||||||
|
### Return traces
|
||||||
|
|
||||||
|
A failable that reaches the function boundary unhandled carries a **return
|
||||||
|
trace** — the chain of `raise` / `try` sites the error passed through.
|
||||||
|
|
||||||
|
- **Storage:** a thread-local fixed-cap ring (32 frames; newest survive on
|
||||||
|
overflow). `raise` and each failing `try` push a frame; every absorbing site
|
||||||
|
(`catch`, a succeeding chain attempt, a value terminator, a destructure)
|
||||||
|
clears the buffer.
|
||||||
|
- **Resolution is in-process — no DWARF, no OS symbolizer.** A runtime frame is
|
||||||
|
a pointer to a compile-time-interned `Frame { file, line, col, func, line_text }`
|
||||||
|
stamped at the push site; the formatter reads it directly (deterministic,
|
||||||
|
identical across OS/target, works under the JIT and a signed iOS `.app`). A
|
||||||
|
comptime frame is `(func_id, ir_offset)` resolved via the interpreter's
|
||||||
|
in-memory IR/source tables.
|
||||||
|
- **Mode.** On by default in debug; release no-ops the push points
|
||||||
|
(opt back in with `--release-traces`). **Comptime (`#run`) is always traced.**
|
||||||
|
- **Formatting** lives in `library/modules/trace.sx` (`trace.print_current()`),
|
||||||
|
rendering `func at file:line:col` per frame plus the source line and a `^`
|
||||||
|
caret. DWARF line-info is still emitted (debug, strippable) so `lldb` / `gdb`
|
||||||
|
can step sx source — that is a debugger artifact, separate from trace
|
||||||
|
resolution.
|
||||||
|
|
||||||
|
### ABI
|
||||||
|
|
||||||
|
The error slot is a `u32`, always the last slot of the multi-return tuple, in
|
||||||
|
both register- and stack-return conventions. `0` = no error; non-zero = an
|
||||||
|
interned global tag id (pool capacity ~4.3 billion; fixed 32-bit, no dynamic
|
||||||
|
widening across builds). Errors are a pure value channel — no coupling to the
|
||||||
|
implicit `context`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Grammar (informal)
|
||||||
|
|
||||||
```
|
```
|
||||||
program = top_level*
|
program = top_level*
|
||||||
top_level = decl | import_decl
|
top_level = decl | import_decl
|
||||||
import_decl = '#import' STRING ';'
|
import_decl = '#import' STRING ';'
|
||||||
| IDENT '::' '#import' STRING ';'
|
| IDENT '::' '#import' STRING ';'
|
||||||
decl = const_decl | var_decl | fn_decl | enum_decl | struct_decl
|
decl = const_decl | var_decl | fn_decl | enum_decl | struct_decl | error_decl
|
||||||
|
error_decl = IDENT '::' 'error' '{' IDENT (',' IDENT)* ','? '}' ';'
|
||||||
const_decl = IDENT '::' expr ';'
|
const_decl = IDENT '::' expr ';'
|
||||||
| IDENT ':' type ':' expr ';'
|
| IDENT ':' type ':' expr ';'
|
||||||
var_decl = IDENT ':=' expr ';'
|
var_decl = IDENT ':=' expr ';'
|
||||||
@@ -2301,10 +2622,12 @@ params = param (',' param)* ','?
|
|||||||
param = IDENT ':' type ('=' expr)?
|
param = IDENT ':' type ('=' expr)?
|
||||||
block = '{' stmt* '}'
|
block = '{' stmt* '}'
|
||||||
stmt = decl | assignment ';' | multi_assign ';' | return_stmt | defer_stmt | insert_stmt
|
stmt = decl | assignment ';' | multi_assign ';' | return_stmt | defer_stmt | insert_stmt
|
||||||
| push_stmt | break_stmt | continue_stmt | expr ';'
|
| push_stmt | break_stmt | continue_stmt | raise_stmt | onfail_stmt | expr ';'
|
||||||
return_stmt = 'return' expr? ';'
|
return_stmt = 'return' expr? ';'
|
||||||
break_stmt = 'break' ';'
|
break_stmt = 'break' ';'
|
||||||
continue_stmt = 'continue' ';'
|
continue_stmt = 'continue' ';'
|
||||||
|
raise_stmt = 'raise' expr ';'
|
||||||
|
onfail_stmt = 'onfail' IDENT? block
|
||||||
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
|
||||||
@@ -2314,8 +2637,9 @@ lvalue = IDENT | postfix '.' IDENT
|
|||||||
expr = if_expr | match_expr | while_expr | for_expr | lambda | binary
|
expr = if_expr | match_expr | while_expr | for_expr | lambda | binary
|
||||||
while_expr = 'while' expr block
|
while_expr = 'while' expr block
|
||||||
for_expr = 'for' expr ':' '(' IDENT [',' IDENT] ')' block
|
for_expr = 'for' expr ':' '(' IDENT [',' IDENT] ')' block
|
||||||
binary = unary (binop unary)*
|
binary = catch_expr (binop catch_expr)* // binop includes `or` (fallback / chain)
|
||||||
unary = ('-' | '!' | 'xx' | 'cast' '(' type ')') postfix
|
catch_expr = unary ('catch' IDENT? (block | '==' '{' case_arm* else_arm? '}' | unary))?
|
||||||
|
unary = ('-' | '!' | 'xx' | 'try' | 'cast' '(' type ')') postfix
|
||||||
| postfix
|
| postfix
|
||||||
postfix = primary ('(' args? ')' | '.' IDENT | '.{' field_init_list '}')*
|
postfix = primary ('(' args? ')' | '.' IDENT | '.{' field_init_list '}')*
|
||||||
primary = INT | HEX_INT | BIN_INT | FLOAT | STRING | BOOL | IDENT | '---'
|
primary = INT | HEX_INT | BIN_INT | FLOAT | STRING | BOOL | IDENT | '---'
|
||||||
@@ -2333,11 +2657,13 @@ lambda = '(' params? ')' ('->' type)? '=>' expr
|
|||||||
args = expr (',' expr)* ','?
|
args = expr (',' expr)* ','?
|
||||||
type = '$' IDENT | 's32' | 'f32' | 'f64' | 'bool' | 'string'
|
type = '$' IDENT | 's32' | 'f32' | 'f64' | 'bool' | 'string'
|
||||||
| 'Any' | 'Type' | '..' type | '[' expr ']' type | IDENT
|
| 'Any' | 'Type' | '..' type | '[' expr ']' type | IDENT
|
||||||
|
| '(' type (',' type)* ',' '!' IDENT? ')' // value-carrying failable
|
||||||
|
| '!' IDENT? // pure failable (`!` / `!Named`)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 13. Open Questions
|
## 14. Open Questions
|
||||||
|
|
||||||
- **Nested functions**: Can functions be defined inside other functions?
|
- **Nested functions**: Can functions be defined inside other functions?
|
||||||
- **Operator overloading**: Not shown — presumably no.
|
- **Operator overloading**: Not shown — presumably no.
|
||||||
|
|||||||
Reference in New Issue
Block a user