bynk-syntax 0.443.0

The syntax foundation of the Bynk compiler: lexer, parser, AST, spans, the CompileError type, and the diagnostic-code registry.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
//! Registry of reserved keywords.
//!
//! Single source of truth for the keyword list in
//! `site/src/content/docs/book/reference/keywords.md`, generated by
//! [`render_markdown`]. The test
//! `tests/keywords_reference.rs` asserts this table matches exactly the
//! alphabetic `#[token("…")]` keywords declared in `lexer.rs`, so the two
//! cannot drift.

/// One reserved keyword and a one-line description of its role.
pub struct KeywordInfo {
    pub word: &'static str,
    pub meaning: &'static str,
}

/// Every reserved keyword, sorted.
pub const KEYWORDS: &[KeywordInfo] = &[
    k("Bool", "The boolean base type."),
    k(
        "Bytes",
        "The binary base type — an immutable octet sequence, erased to `Uint8Array` (`Bytes.fromUtf8(s)`).",
    ),
    k(
        "Duration",
        "The time-span base type, in milliseconds (`5.minutes`).",
    ),
    k("Effect", "The effectful-computation type, `Effect[T]`."),
    k("Err", "The error variant of `Result`."),
    k("Float", "The floating-point base type."),
    k(
        "Instant",
        "The absolute-time base type, in epoch milliseconds (`Clock.now()`).",
    ),
    k("Int", "The integer base type."),
    k(
        "JsonError",
        "The JSON-decode error type, `Result[T, JsonError]` from `Json.decode`.",
    ),
    k("None", "The empty variant of `Option`."),
    k("Ok", "The success variant of `Result`."),
    k("Option", "The optional-value type, `Option[T]`."),
    k("Result", "The success-or-error type, `Result[T, E]`."),
    k("Some", "The present variant of `Option`."),
    k("String", "The string base type."),
    k(
        "ValidationError",
        "The error type returned by a refined type's `.of`.",
    ),
    k(
        "actor",
        "Declare an actor — a boundary contract a handler consumes via `by`.",
    ),
    k(
        "adapter",
        "Declare an adapter — the host boundary (capability contract + binding).",
    ),
    k("agent", "Declare a stateful, keyed agent inside a context."),
    k("as", "Alias a consumed context (`consumes X as Y`)."),
    k(
        "asset",
        "Declare a static file the platform serves, public by construction (`asset name = \"path\"`).",
    ),
    k(
        "binding",
        "Name an adapter's TypeScript binding module (`binding \"<module>\"`).",
    ),
    k(
        "by",
        "Name the actor a handler consumes, after the return type — or a service-level default on the header (`… -> T by <name>: <Actor>`).",
    ),
    k(
        "capability",
        "Declare a capability (a dependency interface) in a context, or a distinct instance of a platform resource (`capability Sessions = Kv`).",
    ),
    k(
        "case",
        "Declare a test case inside a `suite` (`case \"…\" { … }`).",
    ),
    k(
        "commons",
        "Declare a pure, stateless module of types and functions.",
    ),
    k(
        "consumes",
        "Declare a dependency on another context's services.",
    ),
    k(
        "context",
        "Declare a deployable context (services, agents, capabilities).",
    ),
    k(
        "cron",
        "The cron protocol on a service header (`from cron`).",
    ),
    k(
        "do",
        "Perform a unit effect as a statement (`do e` — the binder-free `let _ <- e`).",
    ),
    k(
        "else",
        "The alternative branch of an `if`, or what a block produces when a `let` pattern does not match (`let Ok(x) = r else { … }`).",
    ),
    k(
        "embed",
        "Declare a project file whose text is a `String` constant, compiled into the code and never served (`embed name = \"path\"`).",
    ),
    k(
        "ensures",
        "Declare a function postcondition — a pure `Bool` clause over the parameters and `result` (`ensures <name>: <pred>`).",
    ),
    k("enum", "Declare a payloadless sum type (`enum { A, B }`)."),
    k(
        "event",
        "Declare a typed fact a context may emit (`event Name = { fields }`), inside a context.",
    ),
    k(
        "expect",
        "Assert a predicate inside a test case (`expect <bool-predicate>`).",
    ),
    k("exports", "Declare which types a context exposes, and how."),
    k("false", "The boolean literal `false`."),
    k("fn", "Declare a function."),
    k(
        "from",
        "Name the protocol a service conforms to (`service X from http`).",
    ),
    k("given", "Declare the capabilities a handler requires."),
    k(
        "http",
        "The HTTP protocol on a service header (`from http`).",
    ),
    k("if", "A conditional expression."),
    k(
        "implies",
        "Logical implication (`P implies Q` ≡ `!P || Q`), used in invariant predicates.",
    ),
    k(
        "invariant",
        "Declare an agent invariant — a predicate that must hold of every committed state.",
    ),
    k(
        "is",
        "Test a value against a variant pattern, yielding a `Bool`.",
    ),
    k(
        "let",
        "Bind a local value (`let x = …`, or `let x <- …` for an effect), or match a pattern with a fallback (`let Ok(x) = r else { … }`).",
    ),
    k(
        "match",
        "Pattern-match over a sum type, `Result`, or `Option`.",
    ),
    k(
        "messages",
        "Declare a message bundle for one locale (`messages \"<tag>\" { \"code\" => \"template\" }`), inside a commons.",
    ),
    k(
        "on",
        "Begin a handler declaration (`on call`, `on GET(…)`, `on message`, `on open`/`on close`).",
    ),
    k(
        "opaque",
        "Declare an opaque type, or export a type opaquely.",
    ),
    k(
        "property",
        "Declare a generative test inside a `suite` (`property \"…\" { for all … }`).",
    ),
    k(
        "protocol",
        "Reserved keyword (protocols are a closed, compiler-known set).",
    ),
    k("provides", "Provide an implementation of a capability."),
    k(
        "queue",
        "The queue protocol on a service header (`from queue(\"name\")`).",
    ),
    k(
        "record",
        "Reserved keyword (records are written `type X = { … }`).",
    ),
    k(
        "requires",
        "Declare a function precondition — a pure `Bool` clause over the parameters (`requires <name>: <pred>`).",
    ),
    k("self", "The current agent instance, inside a handler."),
    k(
        "service",
        "Declare a service (a group of handlers) in a context.",
    ),
    k(
        "stub",
        "Stub a consumed capability operation at a test seam (`stub Cap.op(…) returns <v>` / `fails`).",
    ),
    k(
        "suite",
        "Declare a test suite targeting a unit (`suite <target> { case … }`).",
    ),
    k(
        "transition",
        "Declare an agent step invariant over the `old`/`new` state pair (`transition <name>: …`).",
    ),
    k(
        "transparent",
        "Export a type with its structure visible (`exports transparent { … }`).",
    ),
    k("true", "The boolean literal `true`."),
    k(
        "type",
        "Declare a type: alias, record, sum, opaque, or refined.",
    ),
    k("uses", "Bring a commons into scope."),
    k("where", "Attach refinement predicates to a base type."),
];

/// Contextual keywords — words that read as keywords in one position but stay
/// usable as ordinary identifiers elsewhere, so they are lexed as `Ident` and
/// are deliberately *absent* from [`KEYWORDS`] (which is drift-guarded to equal
/// the lexer's reserved `#[token]`s). Editor surfaces still owe them a hover and
/// a doc — the mechanical floor over this table lives in
/// `bynk-lsp/tests/editor_coverage.rs`, mirroring the reserved-keyword tooth
/// (ADR 0156 / ADR 0161).
pub const CONTEXTUAL_KEYWORDS: &[KeywordInfo] = &[
    k(
        "advance",
        "Move the test harness's clock forward in a `case` (`advance Clock by 15.minutes`).",
    ),
    k(
        "drop",
        "Inside a `migrate` step, discard a stored field's value on purpose (`drop owner`).",
    ),
    k(
        "key",
        "The agent's identity field — one per agent; keys the store.",
    ),
    k(
        "migrate",
        "An agent's migration step: carries a stored record from an older schema version to the current shape (`migrate from 1 (old: CartV1) { … }`).",
    ),
    k(
        "removed",
        "Declares an agent class gone (`removed agent Old`): its stored data is deleted at the next deploy, after a named confirmation.",
    ),
    k(
        "replaces",
        "Renames an agent class (`agent New replaces Old`): the old class's stored data and schema history move to the new one.",
    ),
    k("store", "A persisted agent-state field."),
];

/// The reserved lexer tokens the parser deliberately re-admits as identifiers
/// outside their one keyword position (`expect_ident`, ADR-tracked at
/// `parser.rs`). Unlike [`CONTEXTUAL_KEYWORDS`] these words *are* real
/// `#[token]`s and *are* members of [`KEYWORDS`] (so the lexer↔registry drift
/// guard sees them) — they simply are not rejected in identifier position. The
/// keyword reference renders them as a distinct tier so the page no longer
/// claims, falsely, that every listed word is unusable as an identifier.
///
/// Single source of truth: the `expect_ident` exemption arm (via
/// [`is_reserved_contextual`]) and the
/// `is_reserved_keyword_covers_every_lexer_keyword` drift guard both defer to
/// this list, so adding a word here is enough to make the parser admit it.
pub const RESERVED_CONTEXTUAL: &[&str] =
    &["asset", "case", "embed", "event", "messages", "on", "suite"];

/// True when `word` is a [reserved contextual keyword](RESERVED_CONTEXTUAL) —
/// a reserved token `expect_ident` re-admits as an identifier. Because each of
/// these words lexes only to its own dedicated token, matching on the source
/// text is equivalent to matching the token kind, but keeps the exemption
/// single-sourced against [`RESERVED_CONTEXTUAL`].
pub fn is_reserved_contextual(word: &str) -> bool {
    RESERVED_CONTEXTUAL.contains(&word)
}

/// Built-in type names — compiler-known type constructors the parser dispatches
/// on by identifier text in `parser/types.rs`, *outside* the keyword/token
/// system entirely (they are lexed as ordinary `Ident`s, so they are absent
/// from both the lexer's `#[token]`s and [`KEYWORDS`]). They are nonetheless
/// reserved in type position: a `type` declaration may not reuse one of these
/// names (`bynk.resolve.reserved_builtin_type`), because the parser would
/// otherwise intercept every later reference and the user's alias would be
/// silently shadowed or fail incoherently.
///
/// Single source of truth: [`is_builtin_type_name`] gates the resolver's
/// redeclaration diagnostic, and a drift guard
/// (`bynkc/tests/keywords_reference.rs`) asserts this list equals the set of
/// names the type parser dispatches on. Keep it sorted.
pub const BUILTIN_TYPE_NAMES: &[KeywordInfo] = &[
    k(
        "Asset",
        "A static file declared by `asset name = \"path\"`; its `.url` is the content-hashed URL the platform serves it at.",
    ),
    k(
        "BoundaryRejection",
        "Why the router refused a request before any handler ran; the parameter of `on rejected`.",
    ),
    k(
        "Connection",
        "A held WebSocket connection, `Connection[F]`.",
    ),
    k(
        "History",
        "A generated call-history generator, `History[Agent]` (test-only).",
    ),
    k(
        "HttpResult",
        "The HTTP handler result type, `HttpResult[T]`.",
    ),
    k("List", "The immutable list type, `List[T]`."),
    k("Map", "The immutable map type, `Map[K, V]`."),
    k("Query", "The lazy storage-read type, `Query[T]`."),
    k(
        "QueueResult",
        "The queue handler result type (non-generic).",
    ),
    k(
        "RequestHeaders",
        "An HTTP handler's read-only view of the request's headers (`headers: RequestHeaders`).",
    ),
    k("Stream", "The value-over-time primitive, `Stream[T]`."),
    k(
        "TokenClaims",
        "An `Oidc` actor's verified token claims, read through its binder (`who.claims`).",
    ),
];

/// True when `name` is a compiler-known [built-in type name](BUILTIN_TYPE_NAMES).
/// Used by the resolver to reject `type <name> = …` redeclarations.
pub fn is_builtin_type_name(name: &str) -> bool {
    BUILTIN_TYPE_NAMES.iter().any(|b| b.word == name)
}

const fn k(word: &'static str, meaning: &'static str) -> KeywordInfo {
    KeywordInfo { word, meaning }
}

/// Render the keyword reference page — four tiers, each with prose that is
/// true of *that* tier (the page used to make one blanket claim that was false
/// of two of them).
pub fn render_markdown() -> String {
    let mut out = String::new();
    out.push_str("# Keywords\n\n");
    out.push_str(
        "<!-- GENERATED FILE — do not edit by hand.\n     \
         Source: bynk-syntax/src/keywords.rs (`render_markdown`).\n     \
         Regenerate with: BYNK_BLESS=1 cargo test -p bynkc --test it keywords_reference:: -->\n\n",
    );
    out.push_str(
        "Bynk reserves names in four tiers. The first two are lexer keywords; the \
         third are words the lexer treats as plain identifiers; the fourth are \
         compiler-known type names. Only the **hard keywords** can never be used \
         as an identifier.\n\n",
    );

    // Tier 1 — hard keywords: every reserved token except the contextual ones.
    let hard: Vec<&KeywordInfo> = KEYWORDS
        .iter()
        .filter(|k| !RESERVED_CONTEXTUAL.contains(&k.word))
        .collect();
    out.push_str("## Hard keywords\n\n");
    out.push_str(&format!(
        "Reserved everywhere — these **{}** words can never be used as an \
         identifier.\n\n",
        hard.len()
    ));
    out.push_str("| Keyword | Meaning |\n|---|---|\n");
    for info in hard {
        out.push_str(&format!("| `{}` | {} |\n", info.word, info.meaning));
    }

    // Tier 2 — contextual keywords: reserved tokens the parser re-admits as
    // identifiers outside their one keyword position.
    let contextual: Vec<&KeywordInfo> = RESERVED_CONTEXTUAL
        .iter()
        .filter_map(|w| KEYWORDS.iter().find(|k| &k.word == w))
        .collect();
    out.push_str("\n## Contextual keywords\n\n");
    out.push_str(
        "Reserved only in the one position named below; elsewhere (a field, \
         parameter, or other identifier) they are ordinary names.\n\n",
    );
    out.push_str("| Keyword | Meaning |\n|---|---|\n");
    for info in contextual {
        out.push_str(&format!("| `{}` | {} |\n", info.word, info.meaning));
    }

    // Tier 3 — contextual words: not lexer keywords at all; ordinary identifiers
    // that carry meaning in one position.
    out.push_str("\n## Contextual words\n\n");
    out.push_str(
        "Not lexer keywords: the lexer treats these as ordinary identifiers, and \
         they carry meaning only in the position each entry describes (inside an \
         agent body or a `migrate` step, for instance). You can still use them as a \
         field, parameter, or other name elsewhere.\n\n",
    );
    out.push_str("| Word | Meaning |\n|---|---|\n");
    for info in CONTEXTUAL_KEYWORDS {
        out.push_str(&format!("| `{}` | {} |\n", info.word, info.meaning));
    }

    // Tier 4 — built-in type names: not lexer keywords at all, but reserved in
    // type position (a `type` declaration may not reuse one).
    out.push_str("\n## Built-in type names\n\n");
    out.push_str(
        "Compiler-known type constructors. They are not lexer keywords — you may \
         use them as an identifier in value position — but they are reserved in \
         type position: a `type` declaration may not reuse one of these names \
         (`bynk.resolve.reserved_builtin_type`).\n\n",
    );
    out.push_str("| Name | Meaning |\n|---|---|\n");
    for info in BUILTIN_TYPE_NAMES {
        out.push_str(&format!("| `{}` | {} |\n", info.word, info.meaning));
    }
    out
}