1pub struct KeywordInfo {
12 pub word: &'static str,
13 pub meaning: &'static str,
14}
15
16pub const KEYWORDS: &[KeywordInfo] = &[
18 k("Bool", "The boolean base type."),
19 k(
20 "Bytes",
21 "The binary base type — an immutable octet sequence, erased to `Uint8Array` (`Bytes.fromUtf8(s)`).",
22 ),
23 k(
24 "Decimal",
25 "The exact decimal base type — an arbitrary-precision coefficient with a scale (`1.20d`).",
26 ),
27 k(
28 "Duration",
29 "The time-span base type, in milliseconds (`5.minutes`).",
30 ),
31 k("Effect", "The effectful-computation type, `Effect[T]`."),
32 k("Err", "The error variant of `Result`."),
33 k("Float", "The floating-point base type."),
34 k(
35 "Instant",
36 "The absolute-time base type, in epoch milliseconds (`Clock.now()`).",
37 ),
38 k("Int", "The integer base type."),
39 k(
40 "JsonError",
41 "The JSON-decode error type, `Result[T, JsonError]` from `Json.decode`. Its fields are `kind` (`Malformed`, `StructuralMismatch` or `RefinementViolation`), `path` (e.g. `$.qty`) and `message`, all `String`: write `\"\\(e.kind) at \\(e.path): \\(e.message)\"`.",
42 ),
43 k("None", "The empty variant of `Option`."),
44 k("Ok", "The success variant of `Result`."),
45 k("Option", "The optional-value type, `Option[T]`."),
46 k("Result", "The success-or-error type, `Result[T, E]`."),
47 k("Some", "The present variant of `Option`."),
48 k("String", "The string base type."),
49 k(
50 "ValidationError",
51 "The error type returned by a refined type's `.of`. Its fields are `field` and `message`, both `String`: write `\"\\(e.field): \\(e.message)\"`.",
52 ),
53 k(
54 "actor",
55 "Declare an actor — a boundary contract a handler consumes via `by`.",
56 ),
57 k(
58 "adapter",
59 "Declare an adapter — the host boundary (capability contract + binding).",
60 ),
61 k("agent", "Declare a stateful, keyed agent inside a context."),
62 k("as", "Alias a consumed context (`consumes X as Y`)."),
63 k(
64 "asset",
65 "Declare a static file the platform serves, public by construction (`asset name = \"path\"`).",
66 ),
67 k(
68 "binding",
69 "Name an adapter's TypeScript binding module (`binding \"<module>\"`).",
70 ),
71 k(
72 "by",
73 "Name the actor a handler consumes, after the return type — or a service-level default on the header (`… -> T by <name>: <Actor>`).",
74 ),
75 k(
76 "capability",
77 "Declare a capability (a dependency interface) in a context, or a distinct instance of a platform resource (`capability Sessions = Kv`).",
78 ),
79 k(
80 "case",
81 "Declare a test case inside a `suite` (`case \"…\" { … }`).",
82 ),
83 k(
84 "commons",
85 "Declare a pure, stateless module of types and functions.",
86 ),
87 k(
88 "const",
89 "Declare a named constant — a static value of a declared type, in a context or commons (`const MaxRetries: Int = 3`).",
90 ),
91 k(
92 "consumes",
93 "Declare a dependency on another context's services.",
94 ),
95 k(
96 "context",
97 "Declare a deployable context (services, agents, capabilities).",
98 ),
99 k(
100 "cron",
101 "The cron protocol on a service header (`from cron`).",
102 ),
103 k(
104 "do",
105 "Perform a unit effect as a statement (`do e` — the binder-free `let _ <- e`).",
106 ),
107 k(
108 "else",
109 "The alternative branch of an `if`, or what a block produces when a `let` pattern does not match (`let Ok(x) = r else { … }`).",
110 ),
111 k(
112 "embed",
113 "Declare a project file whose text is a `String` constant, compiled into the code and never served (`embed name = \"path\"`).",
114 ),
115 k(
116 "ensures",
117 "Declare a function postcondition — a pure `Bool` clause over the parameters and `result` (`ensures <name>: <pred>`).",
118 ),
119 k("enum", "Declare a payloadless sum type (`enum { A, B }`)."),
120 k(
121 "event",
122 "Declare a typed fact a context may emit (`event Name = { fields }`), inside a context.",
123 ),
124 k(
125 "expect",
126 "Assert a predicate inside a test case (`expect <bool-predicate>`).",
127 ),
128 k("exports", "Declare which types a context exposes, and how."),
129 k("false", "The boolean literal `false`."),
130 k("fn", "Declare a function."),
131 k(
132 "from",
133 "Name the protocol a service conforms to (`service X from http`).",
134 ),
135 k("given", "Declare the capabilities a handler requires."),
136 k(
137 "http",
138 "The HTTP protocol on a service header (`from http`).",
139 ),
140 k("if", "A conditional expression."),
141 k(
142 "implies",
143 "Logical implication (`P implies Q` ≡ `!P || Q`), used in invariant predicates.",
144 ),
145 k(
146 "invariant",
147 "Declare an agent invariant — a predicate that must hold of every committed state.",
148 ),
149 k(
150 "is",
151 "Test a value against a variant pattern, yielding a `Bool`.",
152 ),
153 k(
154 "let",
155 "Bind a local value (`let x = …`, or `let x <- …` for an effect), or match a pattern with a fallback (`let Ok(x) = r else { … }`).",
156 ),
157 k(
158 "match",
159 "Pattern-match over a sum type, `Result`, or `Option`.",
160 ),
161 k(
162 "messages",
163 "Declare a message bundle for one locale (`messages \"<tag>\" { \"code\" => \"template\" }`), inside a commons.",
164 ),
165 k(
166 "on",
167 "Begin a handler declaration (`on call`, `on GET(…)`, `on message`, `on open`/`on close`).",
168 ),
169 k(
170 "opaque",
171 "Declare an opaque type, or export a type opaquely.",
172 ),
173 k(
174 "property",
175 "Declare a generative test inside a `suite` (`property \"…\" { for all … }`).",
176 ),
177 k(
178 "protocol",
179 "Reserved keyword (protocols are a closed, compiler-known set).",
180 ),
181 k("provides", "Provide an implementation of a capability."),
182 k(
183 "queue",
184 "The queue protocol on a service header (`from queue(\"name\")`).",
185 ),
186 k(
187 "record",
188 "Reserved keyword (records are written `type X = { … }`).",
189 ),
190 k(
191 "requires",
192 "Declare a function precondition — a pure `Bool` clause over the parameters (`requires <name>: <pred>`).",
193 ),
194 k("self", "The current agent instance, inside a handler."),
195 k(
196 "service",
197 "Declare a service (a group of handlers) in a context.",
198 ),
199 k(
200 "stub",
201 "Stub a consumed capability operation at a test seam (`stub Cap.op(…) returns <v>` / `returns <v> after <d>` / `fails` / `never`).",
202 ),
203 k(
204 "suite",
205 "Declare a test suite targeting a unit (`suite <target> { case … }`).",
206 ),
207 k(
208 "transition",
209 "Declare an agent step invariant over the `old`/`new` state pair (`transition <name>: …`).",
210 ),
211 k(
212 "transparent",
213 "Export a type with its structure visible (`exports transparent { … }`).",
214 ),
215 k("true", "The boolean literal `true`."),
216 k(
217 "type",
218 "Declare a type: alias, record, sum, opaque, or refined.",
219 ),
220 k("uses", "Bring a commons into scope."),
221 k("where", "Attach refinement predicates to a base type."),
222];
223
224pub const CONTEXTUAL_KEYWORDS: &[KeywordInfo] = &[
232 k(
233 "advance",
234 "Move the test harness's clock forward in a `case` (`advance Clock by 15.minutes`).",
235 ),
236 k(
237 "derives",
238 "After a type declaration's body, opts the type into a constraint (`type Priority = enum { Low, High } derives Ord`): the type is then ordered by declaration order.",
239 ),
240 k(
241 "drop",
242 "Inside a `migrate` step, discard a stored field's value on purpose (`drop owner`).",
243 ),
244 k(
245 "fixture",
246 "In a suite, declares a project file's text as a `String` constant for its cases (`fixture issue = \"fixtures/issue.json\"`): test-only, read from the `fixtures/` folder and never deployed.",
247 ),
248 k(
249 "key",
250 "The agent's identity field — one per agent; keys the store.",
251 ),
252 k(
253 "migrate",
254 "An agent's migration step: carries a stored record from an older schema version to the current shape (`migrate from 1 (old: CartV1) { … }`).",
255 ),
256 k(
257 "par",
258 "Opens a concurrent binding block (`par { let a <- e1 let b <- e2 }`): every effect is issued before any is awaited, and the block completes when all have settled.",
259 ),
260 k(
261 "private",
262 "Before a `fn`, `type`, `messages`, `asset`, `embed` or `const` declaration in a commons or context: visible only within its own unit (and that unit's suites), never through `uses` (`private fn helper(…)`).",
263 ),
264 k(
265 "removed",
266 "Declares an agent class gone (`removed agent Old`): its stored data is deleted at the next deploy, after a named confirmation.",
267 ),
268 k(
269 "replaces",
270 "Renames an agent class (`agent New replaces Old`): the old class's stored data and schema history move to the new one.",
271 ),
272 k("store", "A persisted agent-state field."),
273];
274
275pub const RESERVED_CONTEXTUAL: &[&str] = &[
288 "asset", "case", "const", "embed", "event", "messages", "on", "suite",
289];
290
291pub fn is_reserved_contextual(word: &str) -> bool {
297 RESERVED_CONTEXTUAL.contains(&word)
298}
299
300pub const BUILTIN_TYPE_NAMES: &[KeywordInfo] = &[
314 k(
315 "Asset",
316 "A static file declared by `asset name = \"path\"`; its `.url` is the content-hashed URL the platform serves it at.",
317 ),
318 k(
319 "BoundaryRejection",
320 "Why the router refused a request before any handler ran; the parameter of `on rejected`.",
321 ),
322 k(
323 "Connection",
324 "A held WebSocket connection, `Connection[F]`.",
325 ),
326 k(
327 "History",
328 "A generated call-history generator, `History[Agent]` (test-only).",
329 ),
330 k(
331 "HttpResult",
332 "The HTTP handler result type, `HttpResult[T]`.",
333 ),
334 k("List", "The immutable list type, `List[T]`."),
335 k("Map", "The immutable map type, `Map[K, V]`."),
336 k("Query", "The lazy storage-read type, `Query[T]`."),
337 k(
338 "QueueResult",
339 "The queue handler result type (non-generic).",
340 ),
341 k(
342 "RequestHeaders",
343 "An HTTP handler's read-only view of the request's headers (`headers: RequestHeaders`).",
344 ),
345 k(
346 "RequestQuery",
347 "An HTTP handler's read-only view of the request's query string (`query: RequestQuery`).",
348 ),
349 k("Set", "The immutable set type, `Set[T]`."),
350 k("Stream", "The value-over-time primitive, `Stream[T]`."),
351 k(
352 "TokenClaims",
353 "An `Oidc` actor's verified token claims, read through its binder (`who.claims`).",
354 ),
355];
356
357pub const BUILTIN_FUNCTIONS: &[KeywordInfo] = &[
362 k(
363 "todo",
364 "Placeholder for unfinished code, `todo(message: String)`: type-checks anywhere, faults when run, and raises the `bynk.fault.todo` warning so `check --deny-warnings` can refuse it.",
365 ),
366 k(
367 "unreachable",
368 "Mark a case that cannot happen, `unreachable(message: String)`: type-checks anywhere and faults when evaluated, so a broken assumption is loud instead of masked by a made-up default.",
369 ),
370];
371
372pub fn is_builtin_type_name(name: &str) -> bool {
375 BUILTIN_TYPE_NAMES.iter().any(|b| b.word == name)
376}
377
378const fn k(word: &'static str, meaning: &'static str) -> KeywordInfo {
379 KeywordInfo { word, meaning }
380}
381
382pub fn render_markdown() -> String {
386 let mut out = String::new();
387 out.push_str("# Keywords\n\n");
388 out.push_str(
389 "<!-- GENERATED FILE — do not edit by hand.\n \
390 Source: bynk-syntax/src/keywords.rs (`render_markdown`).\n \
391 Regenerate with: BYNK_BLESS=1 cargo test -p bynkc --test it keywords_reference:: -->\n\n",
392 );
393 out.push_str(
394 "Bynk reserves names in four tiers. The first two are lexer keywords; the \
395 third are words the lexer treats as plain identifiers; the fourth are \
396 compiler-known type names. Only the **hard keywords** can never be used \
397 as an identifier. A last section lists the ambient built-in functions, \
398 which reserve nothing.\n\n",
399 );
400 out.push_str(
401 "A hard keyword is still allowed where a name is a *label* rather than a \
402 binding, so a field can carry the key a third-party payload uses \
403 (`type`, `from`, `self`):\n\n\
404 - a record field, a construction initialiser, a record or named pattern \
405 field and a variant payload field, when the keyword is followed by `:` \
406 (`{ type: String }`, `Account { type: \"Org\" }`, `Account { type: t, .. }`, \
407 `Push(from: String)`). `kind` stays reserved as a variant payload field.\n\
408 - a member after `.`, a field access or a method call, and a method \
409 declaration, when the keyword is on the same line as the `.` \
410 (`a.type`, `fn Sorted.from(…)`, `Sorted.from(xs)`).\n\n\
411 A keyword is never a parameter, a `let` or lambda binding or a bare \
412 reference, so field shorthand is refused: `Account { type, .. }` and \
413 `Account { type }` are `bynk.parse.keyword_field_shorthand`. Write \
414 `type: type_` in a pattern and `type: <value>` in a construction.\n\n",
415 );
416
417 let hard: Vec<&KeywordInfo> = KEYWORDS
419 .iter()
420 .filter(|k| !RESERVED_CONTEXTUAL.contains(&k.word))
421 .collect();
422 out.push_str("## Hard keywords\n\n");
423 out.push_str(&format!(
424 "Reserved everywhere — these **{}** words can never be used as an \
425 identifier.\n\n",
426 hard.len()
427 ));
428 out.push_str("| Keyword | Meaning |\n|---|---|\n");
429 for info in hard {
430 out.push_str(&format!("| `{}` | {} |\n", info.word, info.meaning));
431 }
432
433 let contextual: Vec<&KeywordInfo> = RESERVED_CONTEXTUAL
436 .iter()
437 .filter_map(|w| KEYWORDS.iter().find(|k| &k.word == w))
438 .collect();
439 out.push_str("\n## Contextual keywords\n\n");
440 out.push_str(
441 "Reserved only in the one position named below; elsewhere (a field, \
442 parameter, or other identifier) they are ordinary names.\n\n",
443 );
444 out.push_str("| Keyword | Meaning |\n|---|---|\n");
445 for info in contextual {
446 out.push_str(&format!("| `{}` | {} |\n", info.word, info.meaning));
447 }
448
449 out.push_str("\n## Contextual words\n\n");
452 out.push_str(
453 "Not lexer keywords: the lexer treats these as ordinary identifiers, and \
454 they carry meaning only in the position each entry describes (inside an \
455 agent body or a `migrate` step, for instance). You can still use them as a \
456 field, parameter, or other name elsewhere.\n\n",
457 );
458 out.push_str("| Word | Meaning |\n|---|---|\n");
459 for info in CONTEXTUAL_KEYWORDS {
460 out.push_str(&format!("| `{}` | {} |\n", info.word, info.meaning));
461 }
462
463 out.push_str("\n## Built-in type names\n\n");
466 out.push_str(
467 "Compiler-known type constructors. They are not lexer keywords — you may \
468 use them as an identifier in value position — but they are reserved in \
469 type position: a `type` declaration may not reuse one of these names \
470 (`bynk.resolve.reserved_builtin_type`).\n\n",
471 );
472 out.push_str("| Name | Meaning |\n|---|---|\n");
473 for info in BUILTIN_TYPE_NAMES {
474 out.push_str(&format!("| `{}` | {} |\n", info.word, info.meaning));
475 }
476
477 out.push_str("\n## Built-in functions\n\n");
480 out.push_str(
481 "Functions the compiler provides without a declaration. They reserve \
482 nothing: a `fn`, parameter or local of the same name shadows the built-in.\n\n",
483 );
484 out.push_str("| Name | Meaning |\n|---|---|\n");
485 for info in BUILTIN_FUNCTIONS {
486 out.push_str(&format!("| `{}` | {} |\n", info.word, info.meaning));
487 }
488 out
489}