proposal/16-syntax.md
Committed-Core Surface Syntax
The complete surface for Strata's committed core, designed against
15-syntax-requirements.md R1–R36 and reconciled from the syntax
probe wave: probes/syntax-grammar/ (resilient-LL +
tree-sitter grammar, 26 GAP designs), probes/syntax-corpus-core/ (hardware corpus, 31-entry gap
ledger), probes/syntax-corpus-verif/ (verification/protocol corpus, G-S1–G-S38). Charter picks
(D-077) are SETTLED; reg forms are SETTLED (D-079); the grammar-evidence decisions are SETTLED
(D-080). The whole document is DRAFT-COMPLETE (D-078) pending the fmt dry-run and blind-regeneration
passes. Standing tiebreaker: Rust style when unsure.
The 2026-07-21 systems-safety wave added requirements R37–R50 but did not run a syntax/corpus probe for them. This document therefore does not pretend to settle spellings for health contracts, mapping leases, progress summaries, memory events, observers, power/reconfiguration, physical certificate binding, numeric budgets, DFT authority, or assurance cases. Their architectural homes and syntax constraints are settled provisionally; their surface forms require a dedicated corpus, grammar, formatter, and blind-regeneration wave before joining this specification (D-091).
1. Design rules
- Ride the Rust distribution (P-11): agents are the first users; ~55% of LLM Verilog failures are syntax-class errors concentrated where Verilog diverges from software convention — divergence from Rust is spent only on hardware semantics (
component,inst,reg, domains, events, protocols). - Keyword-anchored, locally resolvable: every major form opens with a unique keyword — resilient-LL recovery points, tree-sitter fidelity, and generation commitment points are the same property. No symbol-operator polysemy (Chisel's
:=/<>zoo is the cautionary tale). - Redundancy that checks: named-only connection, named invariants, mandatory reasons — every redundant name is a slot where a hallucinated connection fails loudly. Never make anyone write the same fact twice unchecked: infer it or check the duplicate (R2).
- No layout semantics: explicit terminators and braces survive generation truncation and keep D-052 structural identity trivial.
- One way to write each thing: where sugar exists (punning, elided clocks,
T @ 'E) it is the fmt-canonical form, so corpus and formatter agree.
2. Settled charter (Veronica, 2026-07-17) + D-079
| Fork | Pick |
|---|---|
| Component flavor | Value-flow: inputs as parameters, outputs as return type; no in/out/inout keywords; bidirectionality via protocol endpoint types |
| Generics | Rust split: bare <> in type position, mandatory ::<> turbofish in expression position; lexer emits single >, parser glues |
| Bindings | let / reg / inst triad, comptime/ghost prefixes — binder form makes phase syntactically recoverable per token (14 §19) |
| Terminators | Semicolons for statements (including requires); comma-lists for all data-shaped declaration bodies (fmt trailing-comma steering, 14 §2) |
| Registers (D-079) | reg name: T reset(v); declares; name <- expr; is the next-cycle write. No = expr next-value form at the declaration. One meaning per token: = binds combinational values, <- is the visible Later-boundary write; multi-arm updates stay natural (sequential last-write-wins per body, elaborating to a priority mux) |
| Tiebreaker | Rust when unsure — resolves: #[attr], where, :: paths, //+/// comments, ../..= ranges, match/pattern syntax, snake_case/CamelCase |
3. Lexical structure
Identifiers. [A-Za-z_][A-Za-z0-9_]*. Convention (lint-enforced, not grammar): CamelCase types/variants/evidence constructors, snake_case values/functions/schemas, lowercase keywords.
Apostrophe labels. 'ident is one token: temporal validity — clock domains ('core) and events ('Exec) share the grammar, deliberately resonant with Rust lifetimes. There are no char literals; 'x is always a label.
Keywords (hard-reserved). Every form-opening word plus the four soft keywords the grammar probe flagged as recovery hazards (state, on, preserve, at_most — D-080):
| 1 | accept await bundle by claim clock_domain comptime component |
| 2 | const contract counter differential_test discharge div domain |
| 3 | elastic else enum experiment expect false fn for |
| 4 | from ghost halt handshake if impl in inst |
| 5 | interface invariant is latch let loop match mem |
| 6 | mod msg mut no_reset on opaque ordering port |
| 7 | preserve process property protocol prove pub quiesce recv |
| 8 | reg repeat require requires resource retain return role |
| 9 | rule sealed send sequence session solve stage stamped |
| 10 | state state_machine static strategy stream struct to |
| 11 | true type unsafe use where while* with |
| 12 | at_most cycleobs |
(while reserved unused, Rust-lean.) Soft (position-only) words, ordinary identifiers elsewhere: param, fixed, reset, related_by, key_set, binds, scope, over, under, evidence, assume, guarantee, liveness, arbitrate, sweep, as, at, cycle and the unit idents. narrow, weighted, config are special only as ident!.
Integer literals. Unsuffixed integers are static-phase polymorphic naturals checked to fit exactly (too wide = error, never truncation — D-030); 0x/0b/decimal with _ separators. No tick-literals ever (8'hFF needs context-sensitive lexing and truncates silently — D-048). Explicit form where inference is absent: UInt::<8>(0xFF).
Quantity literals (verif corpus, G-S5): NUMBER unit where unit comes from the closed, edition-versioned unit table, legal only in quantity positions (evidence arguments, flow entries, #[schedule], experiment entries): 17.2e9 txns, 842 MHz, 1/cycle. N/cycle is a grammatical Rate, not division — / exists nowhere else in the language. Decimal-fraction/exponent numerals (17.2e9, 0.3, 1.0e-6) are legal only inside quantity and metadata positions; there are no hardware floats.
| Unit class | v1 closed table |
|---|---|
| rate denominator | cycle |
| count | txns, evals |
| frequency | MHz, GHz |
| time | ns, us, ms |
String literals. Metadata positions only (reason =, attribute args, tool versions like "vivado-2026.1" — bare vivado-2026.1 is lexer poison). No hardware string type exists.
Comments. //, /// doc (item-anchored), //! module doc — anchored trivia per D-053; never column-significant.
The > discipline (D-080). The lexer never emits >> or >=: it emits single > (and =); the parser glues byte-adjacent > > into shift and > = into >= in expression-operator position only. Consumer<ready_valid<T>> needs no splitting — each > closes one level. <, <=, <<, ::< lex directly. ..=/.. are single tokens. No context-sensitive lexing anywhere (the tree-sitter co-grammar, D-066, depends on this).
4. Items
Every item form opens with a unique keyword (design rule 2). Items take #[attributes], /// docs, and visibility pub / pub(package) (module-private default; pub(crate) etc. deferred).
component — value-flow: inputs are parameters, the output is the return type. Multi-output components use named tuple returns (G-S35: contracts and require guarantee must name outputs):
| 1 | pub component MemoryController<const OUTSTANDING: Nat1, const BANKS: Nat1 = 1>( |
| 2 | requests: Consumer<ready_valid<MemRequest>>, |
| 3 | ) -> (resp: Producer<ready_valid<MemResponse>>, dram_cmd: Producer<ready_valid<DramCmd>>) |
| 4 | where OUTSTANDING <= 64 |
| 5 | { ... } |
Statefulness is visible at the use site: components instantiate with inst, functions call bare. A component's operation surface (port items, §5) is reachable through a borrowed handle parameter table: &TxnTable; an inst value coerces to its value-flow result where one is expected (core G19). Item-position unsafe surface: pub unsafe(clock_crossing) component AsyncFifo<'src, 'dst, T, const DEPTH: Nat1>(...).
fn / comptime fn — pure/combinational functions and generators. ghost fn declares monitor-phase functions (relations, predicates); refinement/ghost integers are mathematical, so bare - is legal there (never on hardware integers, R8).
interface / sealed interface / impl — Rust-trait-shaped (D-038): member fn ...; signatures, associated type Grant;, impl Path for Type { }. sealed interface is the protocol-catalog mechanism.
enum — Rust bodies plus explicit encodings (G-S1): the ascription makes it Encoded<Opcode, Bits<7>> — encoding is semantics, not a #[repr] layout hint. Discriminants checked to fit exactly.
| 1 | pub enum Opcode: Bits<7> { |
| 2 | Load = 0b000_0011, |
| 3 | Store = 0b010_0011, |
| 4 | } |
struct — Rust bodies, comma-lists, unit structs struct Active;, grade-0 phantom type params legal unused.
opaque type / type alias — pub(package) opaque type TxnId = Handle<...>; (declaring module owns mint/open); plain pub type Addr = UInt<44>;.
const — const TXN_ENTRIES: Nat1 = 16; — item-position, implicitly static-phase (comptime let is the body-local form).
clock_domain — declared once, referenced by name (R2): clock_domain core { clock: CoreClock, reset: CoreReset { polarity: active_low, deassert: sync }, }; no_reset as a marker entry declares a reset-free (unit-epoch) domain.
resource — resource adder: Unit<Latency<0>, II<1>> = share(wrapping_add); — abstract, = share(f) bound, or arrays [Unit<...>; N] = replicate(f) (replication always explicit).
protocol / bundle / stream — §10. state_machine / strategy fn / property / differential_test / experiment — §11.
use / mod — Rust use-trees (use a::b::{C, D};, ::*, as); pkg:: is the current package root; pub mod x; + pub use re-exports define the package surface (13 §6).
discharge — statement-positioned, §5.
Instantiation is named-only with punning — positional connection does not exist:
| 1 | let fifo = inst Fifo::<LoadReq, 8> { push }; // pun connects `push` |
| 2 | let out = fifo; // value-flow result |
One protocol-connect form, checked by duality (04 §3): passing a producer value to a Consumer<P> parameter is the connection. A standalone connect statement was drafted in the core corpus and never needed — rejected (§15).
5. Statements
Component/fn bodies are statement lists terminated by ;; block-like expressions (match/if/for/domain/blocks) in statement position take no ;. Comma-lists are reserved for data-shaped braces (struct/enum/contract/arbitrate/protocol/bundle/stream/clock_domain bodies) — the two disciplines never share a brace pair.
| 1 | let decoded = decode(instr); // combinational; IMMUTABLE (R1) |
| 2 | let mut acc = init; // mutation is marked, rare |
| 3 | let (a, b, c) = triple; // n-ary destructuring (R34) |
| 4 | if let Some(item) = pushed { ... } // rust if-let |
| 5 | |
| 6 | reg rd_ptr: Index<DEPTH> reset(0); // D-079: declaration, reset mandatory* |
| 7 | rd_ptr <- rd_ptr.incr_wrap(); // next-cycle write; last-write-wins per body |
| 8 | buf[wr_ptr] <- item; // element write (same statement form) |
| 9 | retain reg saved: ConfigState reset(dflt);// survives warm reset; reset() = POR only |
| 10 | mem buf: [T; DEPTH]; // un-reset storage; reads need an |
| 11 | // initializedness argument or unsafe(uninitialized_use) |
| 12 | |
| 13 | inst — expression form only: `let x = inst C { ... };` (bare `inst x = ...;` rejected, §15) |
| 14 | comptime let LANES = cfg.lanes; // static phase |
| 15 | comptime for i in 0..LANES { ... } // static loop; also an expression (array per iteration) |
| 16 | comptime param STAGES: Nat in 1..=6; // design-space knob (07 §7) |
| 17 | comptime fixed DEPTH: Nat = cfg.depth; // user-owned config |
| 18 | ghost let trace = observe(bus); // ghost phase, erased |
* reset(v) is mandatory unless the ambient domain is no_reset or the register is explicitly uninit (02 §6). Unwritten paths hold. reg is never inferred from code shape (kills latch inference and blocking/nonblocking outright).
Obligations and discharge (named forms — verif G-S2/G-S3; ; canonical, D-080/GAP-19):
| 1 | requires solve mem_has_base_reg(opcode: Bits<7>) { |
| 2 | if entry_for_encoding(opcode).class is Mem { entry_for_encoding(opcode).has_rs1 } |
| 3 | }; |
| 4 | discharge mem_has_base_reg by comptime_exhaustive(opcode); |
| 5 | requires static { DEPTH mod 2 == 0 }; |
| 6 | requires prove home_serves_all { every_request_eventually_responds(requests, resp) }; |
Names are mandatory on solve/prove (discharge and diagnostics address by name), optional on static. The binder on the requires is the finite domain the discharge strategy enumerates. narrow!(cond); at dataflow boundaries only (R4); the weakened-guard diagnostic quotes the dropped conjunct. require guarantee mem.no_duplicate_response with evidence >= BoundedProved; states a consumer's evidence floor (§11).
Pipeline and time. stage; is the bare separator; stage 'Name; additionally binds the event label used by .at('Name) claims. Values crossing a stage; are auto-piped; inserted registers are named in the structure report. Region markers: elastic { ... } (TransactionEquivalent bound) and cycleobs { ... } (CycleExact), R35.
Multi-cycle control (core G24): sequence { ... } elaborates to an FSM; await bool_expr; waits; await process_expr runs a Process to completion yielding Result<S, F>; halt diverges (types as !); loop { } as in Rust. Typestate transitions construct Process values with the process { step:, done:, ok:, err:, } block (core G23).
Handshake endpoints (core G11/G12): the value-flow introduction/elimination forms for protocol endpoints —
| 1 | let (out, popped) = handshake ready_valid::<T> { valid: !empty, payload: head }; |
| 2 | let (out, _sent) = handshake ready_valid::<T> from opt_value; // Option drives valid/payload |
| 3 | let pushed = push.accept(ready = !full); // drives ready, yields Option<T> |
accept returning Some implies the ready expression held that cycle (a stated protocol-semantics fact feeding guard inference).
Other statements: port name(args) -> Ret { body } (component-body only; elaborates to a request_response endpoint; body-less port log(pkt: stamped TracePkt); declares); domain 'core { ... } blocks (expressions; escaping values carry their domain tag); unsafe(category, reason = "...") { expr } granular unsafe; return expr;; assert expr; (ghost/test phase only); contract { ... } blocks (§11); resource declarations; expect exists only as the #[expect] attribute (§9), never a statement.
6. Expressions
match/if are expressions (R3/R5); blocks yield their trailing expression. Variant names resolve against the scrutinee's declared enum type (R2).
Precedence (low → high; comparisons non-associative, Rust-lean):
| Level | Operators |
|---|---|
| assign | = (to let mut targets; lvalue-checked) |
| range | .., ..= |
| or | || |
| and | && |
| compare / test | == != < <= > >= · is variant-set |
| concat | ++ (bit concat; widths sum in the D-037 algebra) |
| shift | <<, >> (glued) |
| additive | +, - (grammatical; hardware integers reject bare - — R8) |
| multiplicative | * · div/mod with ConstAtom right operand by grammar (the R7 Presburger fence) |
| unary | !, - |
| postfix | call, .method, ::<T> turbofish, [i] index/slice, ? |
- Variant-set tests:
op is Load | Store, closed under&&/||/!(R3); diagnostics renderop ∈ {Load, Store}(R5). Idiom the docs and lints teach: bind then match —let d = decode(raw); match d { ... }— let-bound scrutinees and predicate aliases are what guard inference traces. - Arithmetic is named methods:
wrapping_add,full_add,checked_add,saturating_add; subtraction forces the choice —checked_sub(Option) vsmonus(floors at zero). Conversions:wrap_to::<N>(),saturate_to,checked_to,sign_extend_to(noas). No compound assignment (+=) — exception: session counters (§10), which are math naturals. - Bit slicing: little-endian bit weight, half-open ranges —
x[0..8]is the low byte,x[msb]sugar; never Verilog[7:0].x[0..k] ++ x[k..N]widths sum. NamedEncodedfields displace raw slicing (lint on raw slices of typed values). ?propagates stall/fault through the canonicalBypass/Poisonnode: aBubble<T>unwraps or kills the dominated region;Bubble::of(x)re-wraps (core G28). Alsoqueue.reserve()?.- Turbofish is mandatory in expression position (
UInt::<8>(0xFF),x.wrap_to::<4>(),overlaps::<W>inside relation args — F15's fix-it: "expression position: write::<"). Domain labels ride along:AsyncFifo::<'core, 'dbg, T, 8>. - Event expressions:
'Exec,'G + 2; resource claimsadder.at('Exec)(a, b)— guards on conditional claims are never written (R2), they are inferred fromif/matchpath conditions and rendered in set form with the harvested path condition (R5/R6). - Struct literals are FORBIDDEN at the top level of
if/match/forheaders and match-arm guards (D-080; Rust's rule):if x == S { }parsesSas a path and{ }as the body; parenthesize to opt in —if a == (Grant { slot: 0 }) { b }. Functional-record-update..restis legal (CacheAddress { offset: 0, ..arbitrary() }). - arbitrate is an expression — a named architectural node whose output flows on (G-S23, resolving both 15 §3 flags:
guarantee/livenessjoin the comma-list with mandatory names; eligibility/keys are explicit closures, never a magicr):
| 1 | let granted = arbitrate dram_issue { |
| 2 | inputs: [read_q, write_q], |
| 3 | eligibility: |r| r.ready, |
| 4 | policy: oldest_first(key = |r| r.age, tie_break = round_robin), |
| 5 | resource: dram_cmd_port, |
| 6 | guarantee exclusive_grant: at_most_one_grant_per_cycle(), |
| 7 | liveness weak_fairness: fair_grants() under continuously_ready(dram_cmd_port), |
| 8 | }; |
- Predicate position shares the expression grammar with two deltas: implication sugar
if cond { pred }means!cond || pred(R11; predicate position only — the expression-side error must say so), anddiv/modkeep the syntactic fence. Interpreted predicate symbols (closed set):clog2,max,min,prev(R9 stability),accepted,transferred,outstanding,same_epoch. - Phase operators are plain calls:
reflect(x),reify(x),symbolize(x),observe(x).
7. Types
| 1 | Type = ["stamped"] ['domain] TypeCore ["@" 'Event] |
| 2 | TypeCore = Path<GenericArgs> | (T1, T2) | [T; N] | fn(A) -> B // fn types comptime-phase only |
- Generic parameters: type params with Rust bounds
T: Ord + Encode; const paramsconst DEPTH: Nat1with optional rangesconst N: Nat in 1..=6; label params'src, 'dst(domains/events — the Rust-lifetime grammar accepts all four positions unchanged: params, type argsEpoch<'core>, signal prefixesx: 'core Bool, turbofish). wheretakes both interface bounds and Tier-1 infix predicates:where DEPTH <= 1024, T: Encode(D-080: bound-vs-predicate needs LL(2), committed). Where-lists take no trailing comma (not a braced body).- Positivity is a type (R19):
Nat1is the standard depth/count parameter type (where N >= 1also legal;Nat1is the path of least resistance). Width expressions in<>follow the D-037 closed algebra (clog2/max/minspelled exactly so, R21); leaving it triggers the "left Presburger" fix-it (R20). - The angle-bracket sub-language (D-080): inside
<...>the grammar excludes comparisons, shifts,&&/||, and struct literals, so>always closes and,always separates — bare<>in type position needs no symbol table and no brace fallback. A bare path argument is type-or-const until name resolution (as in Rust). Trailing commas are legal in<>lists (fmt wrap rule, §13). Schema config blocks are type-position only:burst_dyn<Flit> { max_len: 8 }(expression position would collide with struct literals — same class as the header rule). - Refinement types:
Occupancy<0..=ENTRIES>(state refinement, 06 §1),Index<N>,Bits<N>,UInt<N>,OneHot<N>,Gray<N>,Encoded<T,E>,Handle<R,Slot,Gen,Epoch>. stamped T— the one epoch keyword users write (§12).T @ 'Eavailability sugar forAt<T, 'E>is adopted (§15): it parsed cleanly through both grammar implementations and is the fmt-canonical form; annotation only for deviation from the ambient stage.- Endpoints:
Consumer<P>/Producer<P>for binary protocols;View<Bundle, Role>is the per-role projection for ≥3-role bundles (G-S22; binary duality is provably no participant's view of a 3-role body).
8. Patterns
Rust patterns: _, literals, ranges 0..=3 =>, or-patterns A | B, tuple (n-ary, R34), Path(p1, p2), Path { field, .. }, mut x, if let. A bare lowercase path binds; a bare CamelCase path is a unit variant, resolved against the scrutinee's declared enum (R2 — wave-1's only bugs were hand-written variant-index typos). Match arms: Pattern [if guard] => expr, — exhaustive over enums; arm attributes legal.
9. Attributes and the Meta micro-grammar
#[attr] (F6, tiebreaker), declaration-position only. The Meta micro-grammar is committed (D-080 — attribute/expect metadata is structured, never a balanced-token soup):
| 1 | Attribute = "#" "[" Meta "]" |
| 2 | Meta = Path [ "(" CommaList(MetaItem) ")" ] |
| 3 | MetaItem = Meta | IDENT "=" MetaValue | Predicate | Literal |
| 4 | MetaValue = Literal | QuantityLit | Rate | Path | "derived" | IDENT "(" CommaList(MetaValue) ")" |
The canonical inhabitants:
#[budget(comptime_steps = 20_000_000, per_eval = derived)]— budgets are attributes and only attributes (R22); no raise API exists syntactically;per_eval = <n>is the declared-path override (R23).#[schedule(latency <= 2, throughput >= 1/cycle)]— on architectural nodes.#[expect(lint = ..., reason = "...", evidence = ..., review_by = edition(2028))]— suppression (D-057); on any item, statement, or declaration entry; never an inner attribute; one lint per attribute (G-S30).#[derive(Pack, Arbitrary, Shrink, Waveform, Symbolic)]— the five artifact families (08 §1).#[metamorphic(relation = f)],#[symmetric](checked, R15),#[encoding(auto where preserves_debug_identity)].
10. Protocols, sessions, ordering
Two protocol dialects, one item (G-S10 rule; never mixed in one body): role-less binary bodies use the sequential dialect; bodies declaring roles use the msg + state session dialect.
Sequential dialect (catalog-style):
| 1 | protocol Burst<const N: Nat1, T> { |
| 2 | const granule = 64, // R16 shared constants |
| 3 | send Header { length: N }, |
| 4 | repeat N { send Beat<T> }, // R10: no hand-rolled beat counters |
| 5 | if broadcast { send Snoop }, // R11 conditional refinement |
| 6 | recv Completion, |
| 7 | quiesce, // R17 acceptance point |
| 8 | invariant beats_bounded: accepted(Beat) <= N, // names mandatory (R12) |
| 9 | } |
Session dialect (D-027's first customer — roles, families, ghost state, guarded FSM):
| 1 | protocol ChiReadSharedTxn<const N_RN: Nat1> { |
| 2 | role Requester, |
| 3 | role Home, |
| 4 | role Peer[N_RN - 1], // role family; cardinality monomorphized |
| 5 | msg ReadShared: ReqFlit<N_RN> from Requester to Home, |
| 6 | msg Snoop: SnpFlit<N_RN> from Home to Peer[tgt], // indexed by a payload field |
| 7 | counter snp_sent: 0..=N_RN - 1, // session-scoped ghost naturals |
| 8 | counter snp_pend[peer: Index<N_RN>]: 0..=1, // keyed family |
| 9 | latch requester: Index<N_RN>, // open-time binding |
| 10 | invariant resp_bounded: snp_rcvd <= snp_sent, |
| 11 | accept state Idle { // `accept` marks retirement states |
| 12 | on ReadShared(req) => Snooping { requester = req.src, snp_sent = 0 }, |
| 13 | }, |
| 14 | state Snooping { |
| 15 | on SnpResp(r) if snp_pend[r.src] == 1 => Snooping { snp_rcvd += 1, snp_pend[r.src] = 0 }, |
| 16 | on CompData(_) if snp_rcvd == snp_sent && if bcast { snp_sent == N_RN - 1 } => AwaitAck, |
| 17 | }, |
| 18 | } |
Counter updates after the target state are name = e / name += e (-= never arises: math naturals, total assignments or increments). from/to tags are mandatory once ≥2 roles exist (R18).
bundle — channels with roles, shared constants, cross-channel invariants (named, R12), session attachments, and quiesce points:
| 1 | bundle ChiPort<const N_RN: Nat1> { |
| 2 | const granule = 64, |
| 3 | role Rn, role Hn, role Sn, |
| 4 | req: credit<ReqFlit<N_RN>, 4> from Rn to Hn, |
| 5 | dat: layer<burst<4, DatFlit>, credit<4>> from Hn to Rn, // layer combinator (type-level) |
| 6 | vc: [layer<burst_dyn<Flit> { max_len: 8 }, credit<4>>; VCS] from Sn to Hn, // array channel; |
| 7 | // invariants apply per element |
| 8 | invariant comp_bounded_by_req: accepted(dat) <= accepted(req), |
| 9 | |
| 10 | session<txn = txn> lifecycle: ChiReadSharedTxn<N_RN> over { // spawn-per-key + projection |
| 11 | ReadShared: req, Snoop: snp, CompData: dat, CompAck: ack, |
| 12 | }, |
| 13 | session<line = addr div granule> line_serial: hazard_lock { // R13; div fence applies |
| 14 | binds: txn, // per-message key bindings (addr-less messages inherit at open) |
| 15 | scope: lifecycle, // lock acquire/release rides the named session's open/retire |
| 16 | }, |
| 17 | quiesce maintenance_barrier: at(maint.Accept), // R17; end-of-trace implicit |
| 18 | } |
The session<key = expr> clause is its own mini-grammar (a diagnostic label binding, not generic args — tree-sitter uses a dedicated session_key node).
Ordering — a bundle/stream clause: PreservesOrder<A.Accept, B.Transfer, key = TxnId>, group = id, related_by = overlaps::<W> (relations #[symmetric]-checked, R15), key_set = lines(addr, len) (R14 — one event joins every keyed order; start-line-only keying is machine-refuted), CommitsIn<ProgramOrder>. Relation args are the most heterogeneous bracket context in the language; they get a dedicated relation_args production (F12).
stream — every entry name: value, (G-S36): payload:, domain: Core (a clock_domain reference), protocol: ready_valid { payload_stable_while_blocked }, flow: { acceptance: at_most(1/cycle), outstanding: 0..=8, latency: dynamic }, identity:, ordering:, authority: { speculative: BranchEpoch, cancellation: BranchRecovery }.
11. Verification surface
contract blocks (core G13 + verif G-S4 merged) — component-body position, comma-list entries, three entry kinds plus flavors:
| 1 | contract { |
| 2 | assume payload_stable: requests.payload_stable_while_blocked(), |
| 3 | guarantee no_duplicate_response: exactly_one_response_per_request(requests, resp) |
| 4 | with evidence [ |
| 5 | Tested<17.2e9 txns>, |
| 6 | BoundedProved<depth = 256>, |
| 7 | MutationValidated<duplicate_response_mutants>, |
| 8 | ], |
| 9 | claim frequency: Measured<842 MHz, vu19p, "vivado-2026.1">, |
| 10 | } |
| 11 | contract cdc { ordering: preserved, capacity: DEPTH, ... } |
| 12 | contract death { drain_within_cycles: 64 } // or drain_within_responses: N |
claim carries physical facts — evidence about an artifact, never a guarantee of the term (D-075). Evidence literals: Ctor<args> with quantity literals, idents, name = literal pairs, strings (G-S5); evidence is a set (D-017), bracketed. Contract predicates take the ports/outputs they constrain — which is what forces named returns (G-S35). Inside contracts and requires, result names a single anonymous output (core G14).
Consumer evidence floor: require guarantee mem.no_duplicate_response with evidence >= BoundedProved; — >= compares against the set's best entry on the relevant axis.
Obligations: requires static/solve/prove + named discharge (§5). State refinements over runtime values are 06 §1's second sort; "monitors watch wires, they don't take tokens" (R33) fires on linear values in bounds. Failure-arm disposition diagnostics point at the arm (R32 — checker duty).
Generators: strategy fn name(state: &Model) -> T { ... } with weighted! { 25 => e, ... } arms (G-S24); generator binders param in strategy_expr in rule/property parameter lists, bare name: Type = the type's derived default strategy (G-S25).
state_machine (rule-based lockstep testing, G-S26 — distinct from session FSMs):
| 1 | state_machine CacheLockstep { |
| 2 | state { model: CacheModel, dut: DutHandle<L1Cache>, } |
| 3 | rule load(addr in cache_address(&model)) if dut.can_accept() { ...; } |
| 4 | invariant committed_loads_match_model { assert_eq(dut.x(), model.x()); } |
| 5 | } |
property — #[metamorphic(relation = f)] property name(binders) { stmts; assert rel(a, b); }. assert is the ghost/test-phase assertion (hardware claims stay requires/narrow!).
differential_test — comma-list block: dut: RvCore::<Rv32iMinCfg>, reference:, interface:, lockstep:, strategies: [..], coverage: [..], on_divergence: [..],.
experiment (G-S29 — designed from nothing):
| 1 | experiment mem_ctrl_depth_sweep on MemoryController { |
| 2 | target: vu19p, |
| 3 | variants: sweep { OUTSTANDING: [8, 16, 32], BANKS: 1..=2 }, // or any comptime Sweep<C> expr |
| 4 | workloads: [stream_copy(bytes = 1_048_576, seed = 7)], // explicit seeds (P-6) |
| 5 | measures: [fmax, luts, occupancy(of = self.occupancy)], // self = the DUT instance |
| 6 | faults: [dropped_response(rate = 1.0e-6, seed = 23)], |
| 7 | budget: { builds: 24, board_hours: 8 }, |
| 8 | evidence: Measured<vu19p, "vivado-2026.1">, |
| 9 | } |
Every sweep point must satisfy the DUT's where bounds up front (D-042).
12. Epoch surface
The entire user-facing epoch vocabulary is three forms (R24–R26) plus the reset witness:
| 1 | port log(pkt: stamped TracePkt); // R24: the ONE epoch keyword users write |
| 2 | let sealed = stamp(head); // introduction — legal only where the surrounding |
| 3 | // port/return type says `stamped` |
| 4 | match rebind(pkt) { // R25: bind-epoch as match; whole coordinate |
| 5 | Fresh(p) => consume(p), |
| 6 | Stale(p) => { stale_drops <- stale_drops.wrapping_add(1); drop_it(p) }, |
| 7 | } |
| 8 | requires solve diff_same(a, b) { same_epoch(a, b) }; // R26 |
- Everything else is inferred (pinning inference is local+principal, 05 §4): no epoch annotations in user signatures;
Stamped<T,D>andCrossesare elaboration-owned (R27); the CDC mirror chain is synthesized fromrebindover a stamped stream (R30); token states carry facets structurally (R29). pkt.map(|p| ...)transforms the payload under a sealed coordinate — transport stays epoch-oblivious (core G22).- Unit-epoch normalization rule (core F8, committed):
stampedover ano_reset(unit-epoch) domain is the identity — so a multi-hop crossing that would producestamped (stamped T)through a unit-epoch fabric collapses, and the inner coordinate is the one that survives. - ResetWitness (R28):
ResetWitness::mint()at the lifecycle transition,&wω-borrows at flush sites,w.close()linear consumption — one death event per assertion, unified with the squash contract.retain regreads elaborate as stamped (the register legally outlives reset, core G30).
13. fmt interactions (pointers into 14)
Additions the corpus waves demanded; normative text lives in 14:
- Decode-table row idiom: fixed-arity row constructors (
entry(...)) keep table rows one-per-line under no-alignment; raw struct-literal rows explode 7×-vertical. Style-guide idiom +pedanticlint (verif verdict 1). on-arm wrap rule: session-FSM guards that exceed 100 cols break before=>, indent one level (verif verdict 4).- Generic-arg wrap rule: over-long
<...>lists break after<, one argument per line, trailing comma legal in<>lists (verif verdict 5 — new relative to the charter). - Manifest proof sub-tables:
[proofs.<name>]one-key-per-line is the canonical form (inline tables can't fit a content hash in 100 cols) — D-054 spec addition (verif verdict 8). - Where-lists take no trailing comma (not braced bodies);
requiresnormalizes to;; data bodies steer via trailing comma, statement bodies are immune — the disciplines never collide in one brace pair. - Known-awkward but accepted: the three-line
guarantee/predicate/with evidencestanza; positional booleans in row constructors (style guide: prefer small enums).
14. Requirement traceability
Committed core (R1–R36 → section)
| R | Section | R | Section | R | Section |
|---|---|---|---|---|---|
| R1 immutability | §5 | R13 session keys | §10 | R25 rebind | §12 |
| R2 checked names / inferred guards | §4 §6 §8 | R14 key_set | §10 | R26 same_epoch | §12 |
| R3 is-sets, bind-then-match | §6 | R15 related_by symmetric | §10 §9 | R27 Crosses elab-owned | §12 |
| R4 narrow! | §5 | R16 protocol consts | §10 | R28 ResetWitness | §12 |
| R5 set rendering | §6 | R17 quiesce | §10 | R29 structural facets | §12 |
| R6 path-condition identity | §6 §11 | R18 role tags | §10 | R30 mirror chain | §12 |
| R7 infix Presburger + fence | §6 §7 | R19 Nat1 | §7 | R31 CommitProof wording | lands in 05 |
| R8 monus vs checked_sub | §6 | R20 left-Presburger fix-it | §7 | R32 arm-anchored diags | checker |
| R9 prev(x) | §6 | R21 clog2, closed symbols | §3 §7 | R33 monitor diagnostic | §11 |
| R10 repeat N | §10 | R22 budget attrs only | §9 | R34 n-ary destructuring | §5 §8 |
| R11 conditional refinement | §6 §10 | R23 no overrun ctor | §9 | R35 region markers | §5 |
| R12 named invariants | §10 | R24 stamped | §12 | R36 pass/semver/pinning | 07/13 formats; §13.4 |
Fence caveat (grammar probe): the grammar fences out compound div/mod right operands (the non-linearity); const-ness of a bare identifier is a trivial name-resolution check — non-constant expressions are grammatically impossible; non-const names are a resolution error.
Systems-safety extension (R37–R50)
R37–R50 remain requirements, not syntax. The extension wave must demonstrate all of the following before adding keywords or grammar productions here:
- Sparse opt-in surface: a FIFO using no advanced contract family has an unchanged AST and summary.
- Separation checks: occurrence/detection/containment, eventual/bounded progress, implementation-equivalence/noninterference, intended/bound physical constraints, and evidence/assurance cannot collapse into ambiguous forms.
- One contract envelope: families reuse
contract, named entries, assumptions/guarantees,requires, evidence sets, and metadata rather than each inventing a top-level mini-language unless a corpus proves that impossible. - Stable artifact references: content hash, tool/target/mode/design pins, owner, validity, and expiry fit the existing Meta micro-grammar or force one explicit revision.
- Parser and formatter evidence: resilient-LL and tree-sitter agree; mutation recovery remains local; representative MSHR, CHI/NoC, DMA–IOMMU, power/reconfiguration, DFT, numeric, and assurance examples survive blind regeneration.
15. Rejected and deferred constructs
Rejected (recorded so they are not re-invented):
- Standalone
connect a => b;— drafted in the core corpus, never needed: named-argument instantiation is the duality-checked connect (G6; one way to write each thing). reg ... = nextdeclaration-site next-value form — D-079: one meaning per token;<-is the only Later-boundary write. (Supersedes core G7's optional inline form and verif G-S34.)- Brace fallback for generic args — the restricted angle-bracket algebra suffices (D-080).
- Always-allow struct literals in headers — measured ambiguous (GLR-only); forbid + parenthesize (D-080).
- Bare
inst name = expr;binder (grammar GAP-18 sugar) — corpus never used it;let x = inst ...is the one form. - Bare
discharge by ...without a target name (core F5) and unnamedguarantee {}/liveness {}blocks inarbitrate— named entries are mandatory (G-S23). - Implicit-
rarbitrate fields (key = r.age) — explicit closures only (F6/F10). session<txn>shorthand — label ≠ field in the computed case (verif F5).- Named call arguments — Rust has none; the divergence budget is spent on hardware semantics (verif F9).
- Statement-form
expect(...)—#[expect]attribute only (G-S30). Tick-literals,ascasts, bare-on hardware integers,+=outside session counters, vertical alignment — per charter and 14.
Resolved this pass: T @ 'E availability sugar is ADOPTED — it parsed cleanly in both the resilient-LL and tree-sitter implementations (GAP-24) and is the fmt-canonical spelling of At<T, 'E>.
Deferred, with reasons:
is_pow2in the width algebra — candidate interpreted symbol awaiting its D-037 confluence-interaction argument; until then the blessed idiom isclog2(DEPTH + 1) == clog2(DEPTH) + 1(core F9).pub(crate)-style finer visibility beyondpub(package)— no corpus demand (GAP-04, G-S33).at_most 1 per cyclealternative rate spelling — only needed if/ever wants to mean division at comptime (grammar note).- Conservation-justified state-refinement discharge (core F4) and event provenance in purely combinational components (core F11) — real gaps, semantics-side; tracked for 03/06, not syntax.
16. Appendix: the complete grammar
The reconciled surface grammar. This EBNF is normative and matches the prose above; it updates the
probe's GRAMMAR.ebnf for D-079 (reg/<-), D-080 hard-reservations, and every corpus-won
construct. Notation: ISO-flavored EBNF; CommaList(X) = [ X { "," X } [ "," ] ].
| 1 | (* ---------- 0. Lexical ---------- *) |
| 2 | IDENT = ident_start { ident_continue } ; |
| 3 | LABEL = "'" IDENT ; (* domains and events; never a char literal *) |
| 4 | INT_LITERAL = dec_literal | hex_literal | bin_literal ; (* "_" separators legal *) |
| 5 | NUM_LITERAL = dec_literal [ "." dec_literal ] [ ("e"|"E") ["-"] dec_literal ] ; |
| 6 | (* fraction/exponent forms legal ONLY inside QuantityLit / MetaValue *) |
| 7 | STRING_LITERAL = '"' { string_char | escape } '"' ; (* metadata positions only *) |
| 8 | BOOL_LITERAL = "true" | "false" ; |
| 9 | QuantityLit = NUM_LITERAL UnitIdent ; (* 842 MHz, 17.2e9 txns — closed unit table *) |
| 10 | Rate = ConstExpr "/" "cycle" ; (* the only "/" in the language *) |
| 11 | COMMENT = "//" rest | "///" rest | "//!" rest ; (* anchored trivia *) |
| 12 | (* Lexer: never emits ">>" or ">="; parser glues adjacent ">" ">" / ">" "=" in |
| 13 | expression-operator position only. "..="/".." are single tokens. |
| 14 | Hard-reserved keywords per 16 §3; soft words are keywords only in the |
| 15 | positions shown below. No context-sensitive lexing. *) |
| 16 | |
| 17 | (* ---------- 1. Source file and items ---------- *) |
| 18 | SourceFile = { Item } ; |
| 19 | Item = { Attribute } { DOC_COMMENT } [ Visibility ] [ UnsafeQual ] ItemKind ; |
| 20 | Visibility = "pub" [ "(" "package" ")" ] ; |
| 21 | UnsafeQual = "unsafe" "(" IDENT ")" ; (* item-position unsafe surface *) |
| 22 | ItemKind = Component | FnItem | GhostFn | StrategyFn | Protocol | Bundle | Stream |
| 23 | | Interface | ImplItem | EnumItem | StructItem | OpaqueType | TypeAlias |
| 24 | | ClockDomain | ResourceItem | Experiment | StateMachine | Property |
| 25 | | DifferentialTest | UseItem | ModItem | ConstItem ; |
| 26 | |
| 27 | Component = "component" IDENT [ GenericParams ] ParamClause |
| 28 | [ "->" ReturnType ] [ WhereClause ] Block ; |
| 29 | ReturnType = Type | "(" CommaList( IDENT ":" Type ) ")" ; (* named tuple returns, G-S35 *) |
| 30 | FnItem = [ "comptime" ] "fn" IDENT [ GenericParams ] ParamClause |
| 31 | [ "->" Type ] [ WhereClause ] ( Block | ";" ) ; (* ";" in interfaces only *) |
| 32 | GhostFn = "ghost" FnItem ; |
| 33 | StrategyFn = "strategy" FnItem ; (* G-S24; body may use weighted! *) |
| 34 | ParamClause = "(" CommaList( Param ) ")" ; |
| 35 | Param = { Attribute } IDENT ( ":" Type | "in" Expr ) ; |
| 36 | (* "in strategy_expr" generator binders: rule/property/strategy params only *) |
| 37 | GenericParams = "<" CommaList( GenericParam ) ">" ; |
| 38 | GenericParam = IDENT [ ":" Bound ] |
| 39 | | "const" IDENT ":" Type [ "=" ConstExpr ] [ "in" RangeConst ] |
| 40 | | LABEL ; (* domain/event params: <'src, 'dst, T> *) |
| 41 | Bound = Path { "+" Path } ; |
| 42 | WhereClause = "where" WherePred { "," WherePred } ; (* NO trailing comma *) |
| 43 | WherePred = IDENT ":" Bound | Predicate ; |
| 44 | |
| 45 | Interface = [ "sealed" ] "interface" IDENT [ GenericParams ] [ WhereClause ] |
| 46 | "{" { InterfaceMember } "}" ; |
| 47 | InterfaceMember= { Attribute } ( FnItem | ConstItem | "type" IDENT ";" ) ; |
| 48 | ImplItem = "impl" [ GenericParams ] Path [ GenericArgs ] [ "for" Type ] |
| 49 | [ WhereClause ] "{" { Item } "}" ; |
| 50 | |
| 51 | EnumItem = "enum" IDENT [ GenericParams ] [ ":" Type ] (* encoding ascription, G-S1 *) |
| 52 | "{" CommaList( EnumVariant ) "}" ; |
| 53 | EnumVariant = { Attribute } IDENT [ "(" CommaList( Type ) ")" |
| 54 | | "{" CommaList( Field ) "}" |
| 55 | | "=" INT_LITERAL ] ; (* checked to fit exactly *) |
| 56 | StructItem = "struct" IDENT [ GenericParams ] [ WhereClause ] |
| 57 | ( ";" | "{" CommaList( { Attribute } [ Visibility ] Field ) "}" ) ; |
| 58 | Field = IDENT ":" Type ; |
| 59 | OpaqueType = "opaque" "type" IDENT [ GenericParams ] "=" Type ";" ; |
| 60 | TypeAlias = "type" IDENT [ GenericParams ] "=" Type ";" ; |
| 61 | ConstItem = "const" IDENT [ ":" Type ] "=" Expr ";" ; |
| 62 | UseItem = "use" UseTree ";" ; |
| 63 | UseTree = Path [ "::" ( "*" | "{" CommaList( UseTree ) "}" ) ] [ "as" IDENT ] ; |
| 64 | ModItem = "mod" IDENT ( ";" | "{" { Item } "}" ) ; |
| 65 | |
| 66 | ClockDomain = "clock_domain" IDENT "{" CommaList( DomainEntry ) "}" ; |
| 67 | DomainEntry = "clock" ":" Type |
| 68 | | "reset" ":" Type [ "{" CommaList( IDENT ":" IDENT ) "}" ] |
| 69 | | "no_reset" ; |
| 70 | ResourceItem = "resource" IDENT ":" Type [ "=" Expr ] ";" ; (* share(f) / replicate(f) *) |
| 71 | |
| 72 | (* ---------- 2. Protocols / bundles / streams (16 §10) ---------- *) |
| 73 | Protocol = "protocol" IDENT [ GenericParams ] [ WhereClause ] |
| 74 | "{" CommaList( SeqEntry ) "}" (* role-less: sequential *) |
| 75 | | "protocol" IDENT [ GenericParams ] [ WhereClause ] |
| 76 | "{" CommaList( SessEntry ) "}" ; (* roles present: session *) |
| 77 | (* one node kind, two dialects; never mixed (G-S10) *) |
| 78 | SeqEntry = ("send" | "recv") IDENT [ GenericArgs ] [ "{" CommaList( Field | FieldBind ) "}" ] |
| 79 | | "repeat" ConstExpr "{" CommaList( SeqEntry ) "}" |
| 80 | | "if" Predicate "{" CommaList( SeqEntry ) "}" |
| 81 | | "const" IDENT "=" ConstExpr |
| 82 | | "quiesce" |
| 83 | | "invariant" IDENT ":" Predicate ; |
| 84 | FieldBind = IDENT ":" ConstExpr ; |
| 85 | SessEntry = "role" IDENT [ "[" ConstExpr "]" ] (* role family *) |
| 86 | | "msg" IDENT ":" Type "from" RoleRef "to" RoleRef |
| 87 | | "counter" IDENT [ "[" IDENT ":" Type "]" ] ":" RangeConst |
| 88 | | "latch" IDENT ":" Type |
| 89 | | "invariant" IDENT ":" Predicate |
| 90 | | "const" IDENT "=" ConstExpr |
| 91 | | [ "accept" ] "state" IDENT [ "{" CommaList( OnArm ) "}" ] ; |
| 92 | RoleRef = IDENT [ "[" IDENT "]" ] ; (* Peer[tgt]: payload field *) |
| 93 | OnArm = "on" IDENT "(" Pattern ")" [ "if" Predicate ] "=>" IDENT |
| 94 | [ "{" CommaList( CounterUpdate ) "}" ] ; |
| 95 | CounterUpdate = CounterRef ( "=" | "+=" ) Expr ; |
| 96 | CounterRef = IDENT [ "[" Expr "]" ] ; |
| 97 | |
| 98 | Bundle = "bundle" IDENT [ GenericParams ] "{" CommaList( BundleEntry ) "}" ; |
| 99 | BundleEntry = "const" IDENT "=" ConstExpr |
| 100 | | "role" IDENT [ "[" ConstExpr "]" ] |
| 101 | | IDENT ":" ChannelType [ "from" RoleRef "to" RoleRef ] |
| 102 | | "invariant" IDENT ":" Predicate |
| 103 | | SessionAttach |
| 104 | | "quiesce" IDENT ":" "at" "(" Expr ")" |
| 105 | | "ordering" ":" "{" CommaList( IDENT ":" OrderingRel ) "}" ; |
| 106 | ChannelType = Type | "[" Type ";" ConstExpr "]" ; (* array channels, G-S38 *) |
| 107 | SessionAttach = "session" "<" IDENT "=" KeyExpr ">" IDENT ":" Type |
| 108 | ( "over" "{" CommaList( IDENT ":" IDENT ) "}" |
| 109 | | "{" CommaList( IDENT ":" Expr ) "}" ) ; (* hazard_lock config *) |
| 110 | KeyExpr = ConstExpr ; (* div fence applies (R13) *) |
| 111 | OrderingRel = Path "<" CommaList( RelationArg ) ">" ; (* dedicated node kind (F12) *) |
| 112 | RelationArg = EventPath | IDENT "=" ( Expr | Path Turbofish ) ; |
| 113 | EventPath = IDENT { "." IDENT } ; |
| 114 | |
| 115 | Stream = "stream" IDENT [ GenericParams ] "{" CommaList( StreamEntry ) "}" ; |
| 116 | StreamEntry = IDENT ":" ( Type | Expr | "{" CommaList( StreamEntry ) "}" ) ; |
| 117 | (* payload/domain/protocol/flow/identity/ordering/authority; |
| 118 | flow entries: acceptance: at_most(Rate), outstanding: range *) |
| 119 | |
| 120 | (* ---------- 3. Verification items (16 §11) ---------- *) |
| 121 | StateMachine = "state_machine" IDENT [ GenericParams ] "{" |
| 122 | "state" "{" CommaList( Field ) "}" |
| 123 | { RuleItem | SmInvariant } |
| 124 | "}" ; |
| 125 | RuleItem = "rule" IDENT ParamClause [ "if" Expr ] Block ; |
| 126 | SmInvariant = "invariant" IDENT Block ; |
| 127 | Property = "property" IDENT ParamClause Block ; (* + #[metamorphic] attr *) |
| 128 | DifferentialTest = "differential_test" IDENT "{" CommaList( IDENT ":" Expr ) "}" ; |
| 129 | Experiment = "experiment" IDENT "on" Path "{" CommaList( ExpEntry ) "}" ; |
| 130 | ExpEntry = IDENT ":" ( Expr | SweepLit | "{" CommaList( IDENT ":" Expr ) "}" |
| 131 | | "[" CommaList( Expr ) "]" | EvidenceLit ) ; |
| 132 | SweepLit = "sweep" "{" CommaList( IDENT ":" ( "[" CommaList(ConstExpr) "]" | RangeConst ) ) "}" ; |
| 133 | EvidenceLit = Path "<" CommaList( EvidenceArg ) ">" ; |
| 134 | EvidenceArg = QuantityLit | IDENT | IDENT "=" Literal | STRING_LITERAL ; |
| 135 | |
| 136 | (* ---------- 4. Attributes: the committed Meta micro-grammar (16 §9) ---------- *) |
| 137 | Attribute = "#" "[" Meta "]" ; |
| 138 | Meta = Path [ "(" CommaList( MetaItem ) ")" ] ; |
| 139 | MetaItem = Meta | IDENT "=" MetaValue | Predicate | Literal ; |
| 140 | MetaValue = Literal | QuantityLit | Rate | Path | "derived" |
| 141 | | IDENT "(" CommaList( MetaValue ) ")" ; |
| 142 | |
| 143 | (* ---------- 5. Statements (16 §5) ---------- *) |
| 144 | Block = "{" { Stmt } [ Expr ] "}" ; |
| 145 | Stmt = LetStmt | RegStmt | RegUpdate | MemStmt | ComptimeStmt | GhostStmt |
| 146 | | RequiresStmt | DischargeStmt | RequireGuarantee | NarrowStmt | AssertStmt |
| 147 | | StageStmt | PortStmt | SequenceStmt | ForStmt | ReturnStmt | RegionStmt |
| 148 | | ContractBlock | ResourceItem | Item | ExprStmt | ";" ; |
| 149 | LetStmt = "let" [ "mut" ] Pattern [ ":" Type ] "=" Expr ";" ; |
| 150 | RegStmt = [ "retain" ] "reg" IDENT ":" Type [ "reset" "(" Expr ")" ] [ "uninit" ] ";" ; |
| 151 | (* D-079: reset mandatory unless no_reset domain or uninit; NO "=" form *) |
| 152 | RegUpdate = LValue "<-" Expr ";" ; (* next-cycle write; last-write-wins per body *) |
| 153 | LValue = IDENT { "[" Expr "]" | "." IDENT } ; |
| 154 | MemStmt = "mem" IDENT ":" "[" Type ";" ConstExpr "]" ";" ; |
| 155 | ComptimeStmt = "comptime" ( LetStmt | ForStmt | Block |
| 156 | | "param" IDENT ":" Type "in" RangeConst ";" |
| 157 | | "fixed" IDENT ":" Type "=" Expr ";" ) ; |
| 158 | GhostStmt = "ghost" LetStmt ; |
| 159 | RequiresStmt = "requires" ("static" | "solve" | "prove") [ IDENT [ ParamClause ] ] |
| 160 | "{" Predicate "}" ";" ; (* name mandatory on solve/prove; |
| 161 | ";" canonical, "," accepted + fmt-normalized *) |
| 162 | DischargeStmt = "discharge" IDENT "by" IDENT "(" CommaList( IDENT | Expr ) ")" ";" ; |
| 163 | RequireGuarantee = "require" "guarantee" Path "with" "evidence" ">=" Path ";" ; |
| 164 | NarrowStmt = "narrow" "!" "(" Predicate ")" ";" ; |
| 165 | AssertStmt = "assert" Expr ";" ; (* ghost/test phase only *) |
| 166 | StageStmt = "stage" [ LABEL ] ";" ; |
| 167 | PortStmt = "port" IDENT ParamClause [ "->" Type ] ( Block | ";" ) ; |
| 168 | SequenceStmt = "sequence" Block ; (* await/halt/loop legal inside *) |
| 169 | ForStmt = "for" Pattern "in" Expr Block ; |
| 170 | ReturnStmt = "return" [ Expr ] ";" ; |
| 171 | RegionStmt = ( "elastic" | "cycleobs" ) Block ; |
| 172 | ContractBlock = "contract" [ IDENT ] "{" CommaList( ContractEntry ) "}" ; |
| 173 | ContractEntry = "assume" IDENT ":" Predicate |
| 174 | | "guarantee" IDENT ":" Predicate [ "with" "evidence" "[" CommaList(EvidenceLit) "]" ] |
| 175 | | "claim" IDENT ":" EvidenceLit |
| 176 | | IDENT ":" ( Expr | EvidenceLit ) ; (* flavored: cdc / death entries *) |
| 177 | ExprStmt = Expr ";" | BlockLikeExpr ; (* match/if/for/domain/blocks need no ";" *) |
| 178 | |
| 179 | (* ---------- 6. Expressions (16 §6) ---------- *) |
| 180 | Expr = AssignExpr ; |
| 181 | AssignExpr = RangeExpr [ "=" AssignExpr ] ; (* lvalue-checked post-parse *) |
| 182 | RangeExpr = OrExpr [ ( ".." | "..=" ) OrExpr ] | ( ".." | "..=" ) OrExpr ; |
| 183 | OrExpr = AndExpr { "||" AndExpr } ; |
| 184 | AndExpr = CmpExpr { "&&" CmpExpr } ; |
| 185 | CmpExpr = ConcatExpr [ CmpOp ConcatExpr ] | ConcatExpr "is" VariantSet ; |
| 186 | CmpOp = "==" | "!=" | "<" | "<=" | ">" | ">=" ; (* ">"/" >=" glued *) |
| 187 | VariantSet = Path { "|" Path } ; |
| 188 | ConcatExpr = ShiftExpr { "++" ShiftExpr } ; |
| 189 | ShiftExpr = AddExpr { ( "<<" | ">>" ) AddExpr } ; |
| 190 | AddExpr = MulExpr { ( "+" | "-" ) MulExpr } ; |
| 191 | MulExpr = UnaryExpr { "*" UnaryExpr | ( "div" | "mod" ) ConstAtom } ; |
| 192 | (* THE PRESBURGER FENCE (R7): div/mod right operand is a ConstAtom |
| 193 | by grammar; const-ness of a Path is a resolution check *) |
| 194 | ConstAtom = INT_LITERAL | Path ; |
| 195 | UnaryExpr = ( "!" | "-" ) UnaryExpr | PostfixExpr ; |
| 196 | PostfixExpr = PrimaryExpr { Postfix } ; |
| 197 | Postfix = "." IDENT [ Turbofish ] [ CallArgs ] | Turbofish CallArgs | CallArgs |
| 198 | | "[" Expr "]" | "?" ; |
| 199 | Turbofish = "::" "<" CommaList( GenericArg ) ">" ; (* mandatory in expr position *) |
| 200 | CallArgs = "(" CommaList( Expr | ClosureExpr | IDENT "=" Expr ) ")" ; |
| 201 | (* IDENT "=" only in builtin forms: accept(ready = e), policy keys, measures *) |
| 202 | PrimaryExpr = Literal | QuantityLit (* quantity positions only *) |
| 203 | | Path | Path StructLiteralBody (* restricted: see CondExpr *) |
| 204 | | "(" CommaList( Expr ) ")" | "[" CommaList( Expr ) "]" |
| 205 | | Block | IfExpr | IfLetExpr | MatchExpr | LoopExpr |
| 206 | | InstExpr | HandshakeExpr | ArbitrateExpr | DomainExpr | ProcessExpr |
| 207 | | AwaitExpr | "halt" | ClosureExpr | UnsafeExpr | MacroExpr |
| 208 | | LABEL [ ("+"|"-") ConstAtom ] ; (* event expr: 'G + 2 *) |
| 209 | Path = IDENT { "::" IDENT } ; |
| 210 | StructLiteralBody = "{" CommaList( IDENT [ ":" Expr ] | ".." Expr ) "}" ; (* punning + FRU *) |
| 211 | InstExpr = "inst" Path [ Turbofish ] StructLiteralBody ; (* named-only + punning *) |
| 212 | HandshakeExpr = "handshake" Path Turbofish |
| 213 | ( StructLiteralBody | "from" Expr ) ; (* core G11 *) |
| 214 | ArbitrateExpr = "arbitrate" [ IDENT ] "{" CommaList( ArbEntry ) "}" ; |
| 215 | ArbEntry = "inputs" ":" Expr | "eligibility" ":" ClosureExpr |
| 216 | | "policy" ":" Expr | "resource" ":" Expr |
| 217 | | "guarantee" IDENT ":" Predicate |
| 218 | | "liveness" IDENT ":" Predicate [ "under" Expr ] ; (* names mandatory, G-S23 *) |
| 219 | DomainExpr = "domain" LABEL Block ; (* an expression *) |
| 220 | ProcessExpr = "process" "{" CommaList( IDENT ":" ( Stmt-fragment | Expr ) ) "}" ; |
| 221 | (* step: <reg-update>, done: Bool, ok: S, err: Option<F> — core G23 *) |
| 222 | AwaitExpr = "await" Expr ; (* inside sequence only *) |
| 223 | LoopExpr = "loop" Block ; |
| 224 | IfExpr = "if" CondExpr Block [ "else" ( IfExpr | Block ) ] ; |
| 225 | IfLetExpr = "if" "let" Pattern "=" CondExpr Block [ "else" Block ] ; |
| 226 | MatchExpr = "match" CondExpr "{" CommaList( MatchArm ) "}" ; |
| 227 | MatchArm = { Attribute } Pattern [ "if" CondExpr ] "=>" Expr ; |
| 228 | CondExpr = Expr ; (* StructLiteralBody FORBIDDEN at top level (D-080); |
| 229 | transitively through operators, not through parens/args *) |
| 230 | ClosureExpr = "|" CommaList( Pattern [ ":" Type ] ) "|" Expr ; |
| 231 | UnsafeExpr = "unsafe" "(" IDENT [ "," "reason" "=" STRING_LITERAL ] ")" Block ; |
| 232 | MacroExpr = IDENT "!" "{" CommaList( MacroArm | IDENT ":" Expr ) "}" ; |
| 233 | MacroArm = INT_LITERAL "=>" Expr ; (* weighted! / config! *) |
| 234 | Literal = INT_LITERAL | BOOL_LITERAL | STRING_LITERAL ; |
| 235 | |
| 236 | (* ---------- 7. Predicates ---------- *) |
| 237 | Predicate = Expr | "if" Expr "{" Predicate "}" ; |
| 238 | (* implication sugar, predicate position only (R11): !cond || pred. |
| 239 | Predicates share the Expr grammar (one precedence table); tier |
| 240 | membership is semantic EXCEPT the div/mod fence, which is syntactic. |
| 241 | Interpreted symbols (closed): clog2 max min prev accepted |
| 242 | transferred outstanding same_epoch. *) |
| 243 | |
| 244 | (* ---------- 8. Types (16 §7) ---------- *) |
| 245 | Type = [ "stamped" ] [ LABEL ] TypeCore [ "@" EventExpr ] (* T @ 'E adopted *) |
| 246 | | "&" Type ; (* port-surface borrows, ghost borrows *) |
| 247 | TypeCore = Path [ GenericArgs ] [ TypeConfigBlock ] |
| 248 | | "(" CommaList( Type ) ")" |
| 249 | | "[" Type ";" ConstExpr "]" |
| 250 | | "fn" "(" CommaList( Type ) ")" [ "->" Type ] ; (* comptime-phase only *) |
| 251 | TypeConfigBlock= "{" CommaList( IDENT [ ":" ConstExpr ] ) "}" ; (* schema config; TYPE POSITION ONLY *) |
| 252 | EventExpr = LABEL [ ("+"|"-") ConstAtom ] ; |
| 253 | GenericArgs = "<" CommaList( GenericArg ) ">" ; |
| 254 | GenericArg = IDENT "=" ConstExpr | RangeConst | TypeOrConst | LABEL ; |
| 255 | TypeOrConst = Type | ConstExpr ; (* unified on Path prefix; elaboration sorts *) |
| 256 | RangeConst = [ ConstExpr ] ( ".." | "..=" ) ConstExpr ; |
| 257 | (* Angle-bracket sub-language: NO comparisons, shifts, "&&"/"||", struct literals |
| 258 | inside "<...>" — ">" always closes, "," always separates; no symbol table, |
| 259 | no brace fallback (D-080). *) |
| 260 | ConstExpr = ConstMul { ("+" | "-") ConstMul } ; (* "-" = monus at type level *) |
| 261 | ConstMul = ConstPrimary { "*" ConstPrimary | ("div" | "mod") ConstAtom } ; |
| 262 | ConstPrimary = INT_LITERAL | Path [ GenericArgs ] |
| 263 | | ("clog2" | "max" | "min") "(" CommaList( ConstExpr ) ")" |
| 264 | | "(" ConstExpr ")" ; |
| 265 | |
| 266 | (* ---------- 9. Patterns (16 §8) ---------- *) |
| 267 | Pattern = PatternNoAlt { "|" PatternNoAlt } ; |
| 268 | PatternNoAlt = "_" | Literal |
| 269 | | INT_LITERAL ("..=" | "..") INT_LITERAL |
| 270 | | Path [ "(" CommaList( Pattern ) ")" | "{" CommaList( IDENT [ ":" Pattern ] | ".." ) "}" ] |
| 271 | | "(" CommaList( Pattern ) ")" | "mut" IDENT ; |
| 272 | |
| 273 | (* ---------- 10. Resilient-LL anchors (informative) ---------- *) |
| 274 | (* Every ItemKind and most statements open with a unique hard keyword; the |
| 275 | item-recovery set is FIRST(Item), the statement set adds the statement |
| 276 | openers + ";" "}". ERROR nodes wrap skipped tokens; MISSING tokens are |
| 277 | zero-width; text(tree) == input on ALL inputs (D-062). *) |
Register
D-077 (charter, SETTLED — Veronica), D-078 (this spec, DRAFT-COMPLETE), D-079 (reg forms,
SETTLED), D-080 (grammar-evidence decisions, SETTLED) in decisions.md. Remaining
gates before D-078 settles: the fmt dry-run (D-049–D-053 over both corpora, §13 rules) and the
blind-regeneration pass (a fresh agent rewrites the corpora from this document alone; divergence
measures spec completeness).