Skip to main content

bynk_syntax/
ast.rs

1//! Abstract syntax tree types for Bynk v0 (spec §9.2).
2
3use crate::span::Span;
4
5/// An identifier with its source span.
6#[derive(Debug, Clone)]
7pub struct Ident {
8    pub name: String,
9    pub span: Span,
10}
11
12/// Comment trivia attached to a declaration or statement (v1.1 LSP spec
13/// §3.5). The parser collects line comments from the token stream and
14/// attaches them to nearby AST nodes so the formatter can re-emit them.
15///
16/// - `leading` holds comments that appear immediately above the node,
17///   ordered top-to-bottom. Each entry is the body of one `--` line
18///   (the text after the marker, with its original inline whitespace
19///   preserved).
20/// - `trailing` holds a single comment that appears on the same source
21///   line as the node's final token (e.g. `expr  -- note`).
22#[derive(Debug, Clone, Default)]
23pub struct Trivia {
24    pub leading: Vec<String>,
25    pub trailing: Option<String>,
26}
27
28impl Trivia {
29    pub fn is_empty(&self) -> bool {
30        self.leading.is_empty() && self.trailing.is_none()
31    }
32}
33
34/// A whole parsed commons source file.
35///
36/// In v0.3 a commons may be split across multiple files in a directory; the
37/// resolver merges them into one logical commons. Each parsed AST instance
38/// represents the contribution from a single source file.
39#[derive(Debug, Clone)]
40pub struct Commons {
41    pub name: QualifiedName,
42    pub items: Vec<CommonsItem>,
43    /// `uses` clauses declared in this file.
44    pub uses: Vec<UsesDecl>,
45    /// Optional documentation block attached to the commons declaration.
46    pub documentation: Option<String>,
47    /// Surface form of the file: brace-delimited body or headerless fragment.
48    pub form: CommonsForm,
49    pub span: Span,
50    /// Trivia attached to the commons declaration itself — leading comments
51    /// before the `commons` keyword and a trailing comment after the header
52    /// or closing brace.
53    pub trivia: Trivia,
54    /// Comments appearing after the last item but before the file ends
55    /// (or the closing brace, for brace form). One entry per `--` line.
56    pub trailing_comments: Vec<String>,
57}
58
59/// The two surface forms in which a commons body may be parsed (v0.3 §3.1).
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum CommonsForm {
62    /// `commons name { ... }`
63    Brace,
64    /// `commons name` followed by top-level declarations to EOF.
65    Fragment,
66}
67
68/// A `uses other.commons` declaration (v0.3 §3.3).
69#[derive(Debug, Clone)]
70pub struct UsesDecl {
71    pub target: QualifiedName,
72    pub span: Span,
73    pub trivia: Trivia,
74}
75
76/// A whole parsed context source file (v0.4 §3.1).
77///
78/// Contexts are the architectural-layer declaration kind. Like commons, a
79/// context may be split across multiple files in a directory.
80#[derive(Debug, Clone)]
81pub struct Context {
82    pub name: QualifiedName,
83    pub items: Vec<CommonsItem>,
84    /// `uses` clauses declared in this file.
85    pub uses: Vec<UsesDecl>,
86    /// `consumes` clauses declared in this file.
87    pub consumes: Vec<ConsumesDecl>,
88    /// `exports` clauses declared in this file.
89    pub exports: Vec<ExportsDecl>,
90    /// Optional documentation block attached to the context declaration.
91    pub documentation: Option<String>,
92    /// Surface form of the file: brace-delimited body or headerless fragment.
93    pub form: CommonsForm,
94    pub span: Span,
95    /// Trivia attached to the context declaration itself — leading comments
96    /// before the `context` keyword.
97    pub trivia: Trivia,
98    /// Comments appearing after the last item but before the file ends
99    /// (or the closing brace, for brace form). One entry per `--` line.
100    pub trailing_comments: Vec<String>,
101}
102
103/// A `consumes other.context` declaration (v0.4 §3.2). May optionally carry
104/// an alias introduced by `consumes other.context as Alias` (v0.6 §3.1).
105#[derive(Debug, Clone)]
106pub struct ConsumesDecl {
107    pub target: QualifiedName,
108    pub alias: Option<Ident>,
109    /// v0.17: `consumes U { Cap, … }` — selected capabilities flattened into
110    /// the consumer's local capability namespace under their bare names (§3.3).
111    /// `None` for the whole-unit forms; `Some` (possibly empty) for the braced
112    /// form. Mutually exclusive with `alias`.
113    pub selected: Option<Vec<Ident>>,
114    pub span: Span,
115    pub trivia: Trivia,
116}
117
118/// An `exports visibility { names }` clause (v0.4 §3.3) or, v0.15, an
119/// `exports capability { names }` clause.
120#[derive(Debug, Clone)]
121pub struct ExportsDecl {
122    pub kind: ExportKind,
123    pub names: Vec<Ident>,
124    pub span: Span,
125    pub trivia: Trivia,
126}
127
128/// What an `exports` clause exposes: types (with a visibility) or, v0.15,
129/// capabilities offered for cross-context consumption.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum ExportKind {
132    /// `exports opaque { ... }` / `exports transparent { ... }` — type exports.
133    Type(Visibility),
134    /// `exports capability { ... }` — capabilities offered to consumers (v0.15).
135    Capability,
136}
137
138/// Visibility level for an exports clause (v0.4 §3.3).
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum Visibility {
141    /// Token-only outside the context: hold, pass, compare; no inspect, no construct.
142    Opaque,
143    /// Readable shape outside the context: inspect fields, match variants; no construct.
144    Transparent,
145}
146
147/// An `adapter qualified.name { … }` declaration (v0.17 §3.1). An adapter
148/// co-locates a capability contract with a non-Bynk binding: it may declare
149/// capabilities, the boundary types they reference, inline pure helper
150/// `type`/`fn` (and `uses`), external (bodiless) providers, `exports
151/// capability`, and exactly one `binding` clause. It may *not* declare
152/// services, agents, or bodied providers. Like commons/contexts it may be
153/// split across files in a directory.
154#[derive(Debug, Clone)]
155pub struct AdapterDecl {
156    pub name: QualifiedName,
157    pub items: Vec<CommonsItem>,
158    /// `uses` clauses declared in this file (pure-vocabulary mixin; allowed
159    /// because helpers cannot pierce containment — spec [DECISION B]).
160    pub uses: Vec<UsesDecl>,
161    /// `exports capability { … }` clauses (adapters export capabilities and
162    /// boundary types, never services).
163    pub exports: Vec<ExportsDecl>,
164    /// v0.18: `consumes U { Cap, … }` clauses — adapter-to-adapter capability
165    /// dependencies (spec §4.5, \[N\]). Braced form only; adapter targets only
166    /// (both enforced semantically, not in the parser).
167    pub consumes: Vec<ConsumesDecl>,
168    /// The `binding "<module>" requires { … }` clause, if present. Required
169    /// when the adapter declares any external provider (`bynk.adapter.no_binding`).
170    pub binding: Option<BindingDecl>,
171    pub documentation: Option<String>,
172    pub form: CommonsForm,
173    pub span: Span,
174    pub trivia: Trivia,
175    pub trailing_comments: Vec<String>,
176}
177
178/// A `binding "<module>" requires { "pkg": "range", … }` clause inside an
179/// adapter (v0.17 §3.5). `module` is the TypeScript module supplying the
180/// adapter's external provider symbols, resolved relative to the adapter's
181/// source file. `requires` declares npm dependencies folded into the
182/// generated `package.json`.
183#[derive(Debug, Clone)]
184pub struct BindingDecl {
185    /// The module path as written (the string-literal contents, no quotes).
186    pub module: String,
187    pub module_span: Span,
188    pub requires: Vec<RequiresDep>,
189    pub span: Span,
190    pub trivia: Trivia,
191}
192
193/// One `"pkg": "range"` entry in a binding's `requires { … }` map.
194#[derive(Debug, Clone)]
195pub struct RequiresDep {
196    pub package: String,
197    pub range: String,
198    pub span: Span,
199}
200
201/// Either a commons or a context — the two declaration kinds at the file
202/// level (v0.4 §3.1). v0.7 adds the test declaration kind; v0.17 the adapter.
203#[derive(Debug, Clone)]
204pub enum SourceUnit {
205    Commons(Commons),
206    Context(Context),
207    Suite(SuiteDecl),
208    /// v0.16: a `suite integration "name" { wires … }` multi-Worker integration
209    /// test. Its `name()` is synthesised from the suite name.
210    Integration(IntegrationDecl),
211    /// v0.17: an `adapter` unit — the host boundary (capability contract +
212    /// external binding).
213    Adapter(AdapterDecl),
214}
215
216impl SourceUnit {
217    pub fn name(&self) -> &QualifiedName {
218        match self {
219            SourceUnit::Commons(c) => &c.name,
220            SourceUnit::Context(c) => &c.name,
221            SourceUnit::Suite(t) => &t.target,
222            SourceUnit::Integration(i) => &i.name,
223            SourceUnit::Adapter(a) => &a.name,
224        }
225    }
226
227    pub fn span(&self) -> Span {
228        match self {
229            SourceUnit::Commons(c) => c.span,
230            SourceUnit::Context(c) => c.span,
231            SourceUnit::Suite(t) => t.span,
232            SourceUnit::Integration(i) => i.span,
233            SourceUnit::Adapter(a) => a.span,
234        }
235    }
236
237    pub fn kind_name(&self) -> &'static str {
238        match self {
239            SourceUnit::Commons(_) => "commons",
240            SourceUnit::Context(_) => "context",
241            SourceUnit::Suite(_) => "suite",
242            SourceUnit::Integration(_) => "integration test",
243            SourceUnit::Adapter(_) => "adapter",
244        }
245    }
246}
247
248/// A `test <qualified-name> { ... }` declaration (v0.7 §3.1).
249///
250/// A test targets a commons or context by qualified name and bundles a set of
251/// test cases plus optional mock declarations. As with commons and contexts, a
252/// test may be split across multiple files (fragment form).
253#[derive(Debug, Clone)]
254pub struct SuiteDecl {
255    /// The targeted commons or context.
256    pub target: QualifiedName,
257    /// `uses` clauses brought in by this test fragment.
258    pub uses: Vec<UsesDecl>,
259    /// Provider or consumed-context mocks declared for the test.
260    pub mocks: Vec<MockDecl>,
261    /// The individual test cases.
262    pub cases: Vec<Case>,
263    /// v0.114: generative `property` blocks (testing track slice 2).
264    pub properties: Vec<PropertyDecl>,
265    /// Surface form: brace-delimited body or headerless fragment.
266    pub form: CommonsForm,
267    /// Optional documentation block attached to the test declaration.
268    pub documentation: Option<String>,
269    pub span: Span,
270    pub trivia: Trivia,
271    pub trailing_comments: Vec<String>,
272}
273
274/// A `mocks Name = Impl { ops }` declaration inside a test body (v0.7 §3.2).
275#[derive(Debug, Clone)]
276pub struct MockDecl {
277    /// The capability or consumed-context alias being mocked.
278    pub target_name: Ident,
279    /// The implementation identifier (used as the TypeScript class name).
280    pub impl_name: Ident,
281    /// One operation per mock body entry.
282    pub ops: Vec<MockOp>,
283    pub documentation: Option<String>,
284    pub span: Span,
285    pub trivia: Trivia,
286}
287
288/// One operation inside a mock declaration: `fn name(params) -> T { body }`.
289#[derive(Debug, Clone)]
290pub struct MockOp {
291    pub name: Ident,
292    pub params: Vec<Param>,
293    pub return_type: TypeRef,
294    pub body: Block,
295    pub span: Span,
296    pub trivia: Trivia,
297}
298
299/// A `test "name" { body }` block inside a test declaration (v0.7 §3.3).
300#[derive(Debug, Clone)]
301pub struct Case {
302    /// The test name, taken from the string literal.
303    pub name: String,
304    /// The span of the string literal — used for diagnostics and runtime
305    /// failure reports.
306    pub name_span: Span,
307    pub body: Block,
308    pub documentation: Option<String>,
309    pub span: Span,
310    pub trivia: Trivia,
311}
312
313/// A `property "name" { for all <bindings> [where <pred>] { body } }` block
314/// inside a suite (v0.114, testing track slice 2, ADR 0149). The generative
315/// sibling of [`Case`]: the runner draws inhabitants of each binding's type from
316/// its refinement domain and evaluates the body's `expect`s over them.
317#[derive(Debug, Clone)]
318pub struct PropertyDecl {
319    /// The property name, taken from the string literal.
320    pub name: String,
321    /// The span of the string literal — used for diagnostics and reports.
322    pub name_span: Span,
323    /// The `for all` binder: the generated bindings, an optional `where` filter,
324    /// and the predicate body.
325    pub forall: ForAll,
326    pub documentation: Option<String>,
327    pub span: Span,
328    pub trivia: Trivia,
329}
330
331/// The `for all x: T, … [where <pred>] { … }` binder inside a [`PropertyDecl`].
332#[derive(Debug, Clone)]
333pub struct ForAll {
334    /// The generated bindings, `x: T` (one or more).
335    pub bindings: Vec<ForAllBinding>,
336    /// An optional `where <pred>` filter (a pure `Bool`) applied to generated
337    /// tuples before the body runs.
338    pub where_pred: Option<Expr>,
339    /// The body — one or more statements, typically `expect`s.
340    pub body: Block,
341    pub span: Span,
342}
343
344/// One `for all` binding: `name: T`, where the runner generates inhabitants of
345/// `T` from its refinements.
346#[derive(Debug, Clone)]
347pub struct ForAllBinding {
348    pub name: Ident,
349    pub type_ref: TypeRef,
350}
351
352/// A `test integration "name" { wires C1, C2, … ; cases }` declaration
353/// (v0.16 §3.1). Unlike a unit test, an integration test names a *set* of
354/// participating contexts (`wires`), stands each up as its own Worker, and
355/// exercises a flow across the real Worker boundary. It carries no `mocks`.
356#[derive(Debug, Clone)]
357pub struct IntegrationDecl {
358    /// The suite name, taken from the string literal after `integration`.
359    pub suite: String,
360    /// The span of the suite-name literal — used in diagnostics and reports.
361    pub suite_span: Span,
362    /// A synthesised qualified name (`integration <suite>`), so the unit shares
363    /// the `SourceUnit::name()` shape. Not user-written.
364    pub name: QualifiedName,
365    /// The participating contexts, in declaration order (≥ 2, validated later).
366    pub participants: Vec<QualifiedName>,
367    /// `uses` clauses bringing commons into the case bodies.
368    pub uses: Vec<UsesDecl>,
369    /// The individual test cases.
370    pub cases: Vec<Case>,
371    /// Surface form: brace-delimited body or headerless fragment.
372    pub form: CommonsForm,
373    pub documentation: Option<String>,
374    pub span: Span,
375    pub trivia: Trivia,
376    pub trailing_comments: Vec<String>,
377}
378
379/// A capability reference in a `given` clause (v0.15 §3.2). A bare name is a
380/// local capability (`given Cap`); a dotted name refers to a capability a
381/// consumed context provides (`given B.Cap` / `given Alias.Cap`).
382#[derive(Debug, Clone)]
383pub struct CapRef {
384    /// `None` for a local capability; `Some(prefix)` for a cross-context
385    /// reference where `prefix` is a consumed-context qualified name or alias.
386    pub context: Option<QualifiedName>,
387    /// The capability's simple name (also the local deps key).
388    pub name: Ident,
389    pub span: Span,
390}
391
392impl CapRef {
393    /// The local deps key / capability simple name (e.g. `Clock`).
394    pub fn key(&self) -> &str {
395        &self.name.name
396    }
397
398    /// True when this references a capability provided by a consumed context.
399    pub fn is_cross_context(&self) -> bool {
400        self.context.is_some()
401    }
402
403    /// The cross-context prefix (consumed-context qualified name or alias) as
404    /// a dotted string, if any.
405    pub fn prefix(&self) -> Option<String> {
406        self.context.as_ref().map(|q| q.joined())
407    }
408}
409
410/// A dotted name like `fitness.units`.
411#[derive(Debug, Clone)]
412pub struct QualifiedName {
413    pub parts: Vec<Ident>,
414    pub span: Span,
415}
416
417impl QualifiedName {
418    pub fn joined(&self) -> String {
419        self.parts
420            .iter()
421            .map(|p| p.name.as_str())
422            .collect::<Vec<_>>()
423            .join(".")
424    }
425}
426
427#[derive(Debug, Clone)]
428pub enum CommonsItem {
429    Type(TypeDecl),
430    Fn(FnDecl),
431    /// `capability Name { fn op(...) -> T ... }` (v0.5; contexts only).
432    Capability(CapabilityDecl),
433    /// `provides Cap = ProviderName { fn op(...) -> T { ... } ... }` (v0.5).
434    Provider(ProviderDecl),
435    /// `service Name { on call(...) -> T { ... } ... }` (v0.5).
436    Service(ServiceDecl),
437    /// `agent Name { key id: T; state { ... }; on call ... }` (v0.5).
438    Agent(AgentDecl),
439    /// `actor Name { auth = Scheme, identity = T }` (v0.45). A nominal boundary
440    /// contract consumed by a handler's `by` clause; not a runnable entity.
441    Actor(ActorDecl),
442}
443
444impl CommonsItem {
445    pub fn name(&self) -> &Ident {
446        match self {
447            CommonsItem::Type(t) => &t.name,
448            CommonsItem::Fn(f) => f.name.ident(),
449            CommonsItem::Capability(c) => &c.name,
450            CommonsItem::Provider(p) => &p.provider_name,
451            CommonsItem::Service(s) => &s.name,
452            CommonsItem::Agent(a) => &a.name,
453            CommonsItem::Actor(a) => &a.name,
454        }
455    }
456}
457
458/// A capability declaration (v0.5 §3.3). Capabilities are interface-like
459/// contracts for external dependencies, used inside contexts. They may only
460/// appear inside a `context` declaration.
461#[derive(Debug, Clone)]
462pub struct CapabilityDecl {
463    pub name: Ident,
464    pub ops: Vec<CapabilityOp>,
465    pub documentation: Option<String>,
466    pub span: Span,
467    pub trivia: Trivia,
468}
469
470/// One operation in a capability (signature only; no body).
471#[derive(Debug, Clone)]
472pub struct CapabilityOp {
473    pub name: Ident,
474    pub params: Vec<Param>,
475    pub return_type: TypeRef,
476    pub documentation: Option<String>,
477    pub span: Span,
478    pub trivia: Trivia,
479}
480
481/// A provider declaration (v0.5 §3.4). Supplies an implementation for a
482/// capability.
483#[derive(Debug, Clone)]
484pub struct ProviderDecl {
485    /// The capability being implemented.
486    pub capability: Ident,
487    /// The provider's identifier (used in tests/config to select impls).
488    pub provider_name: Ident,
489    /// v0.12: capabilities this provider depends on (`provides X = Impl given
490    /// Y, Z { … }`). The provider's operation bodies may use these. v0.15:
491    /// a dependency may be a cross-context capability (`given B.Cap`).
492    pub given: Vec<CapRef>,
493    pub ops: Vec<ProviderOp>,
494    /// v0.17: an *external* provider — `provides Cap = Name` with **no** brace
495    /// block — inside an adapter, supplied by the adapter's binding rather than
496    /// a Bynk body. When `true`, `ops` is empty and the emitter produces no
497    /// class. The absence of the brace block (not an empty one) is the signal.
498    pub external: bool,
499    pub documentation: Option<String>,
500    pub span: Span,
501    pub trivia: Trivia,
502}
503
504/// One operation in a provider (signature plus body).
505#[derive(Debug, Clone)]
506pub struct ProviderOp {
507    pub name: Ident,
508    pub params: Vec<Param>,
509    pub return_type: TypeRef,
510    pub body: Block,
511    pub span: Span,
512    pub trivia: Trivia,
513}
514
515/// A service declaration (v0.5 §3.5). Services are the boundary interface
516/// of a context.
517#[derive(Debug, Clone)]
518pub struct ServiceDecl {
519    pub name: Ident,
520    /// The protocol the service conforms to, from the `from <protocol>` header
521    /// clause (v0.44). `Call` when there is no clause.
522    pub protocol: ServiceProtocol,
523    pub handlers: Vec<Handler>,
524    pub documentation: Option<String>,
525    pub span: Span,
526    pub trivia: Trivia,
527}
528
529/// The protocol a service conforms to — declared on the header via
530/// `from <protocol>` (v0.44). `Call` is the default (no `from` clause): a
531/// contract-mediated internal-RPC surface, not a wire protocol. Multi-endpoint
532/// protocols (`Http`, `Cron`) carry no binding — the endpoint lives on each
533/// handler; single-binding `Queue` carries its queue name.
534#[derive(Debug, Clone)]
535pub enum ServiceProtocol {
536    /// No `from` clause: the service holds `on call` handlers only.
537    Call,
538    /// `from http` — many routes; each handler is `on <Method>("route")`.
539    Http,
540    /// `from cron` — many schedules; each handler is `on schedule("expr")`.
541    Cron,
542    /// `from queue("name")` — one bound queue; handlers are `on message(...)`.
543    Queue { name: String },
544    /// `from WebSocket(in: ClientFrame, out: ServerFrame)` — a held WebSocket
545    /// connection (v0.103, real-time track slice 3). `in_type` is the inbound
546    /// frame type (client→server, decoded and routed as typed agent messages);
547    /// `out_type` is the server→client frame type the held `Connection[out_type]`
548    /// carries. The service holds exactly one `on open` handler (edge auth via
549    /// `by`, then transfer of the connection to an agent).
550    WebSocket { in_type: TypeRef, out_type: TypeRef },
551}
552
553/// An agent declaration (v0.5 §3.6). Agents are state-bearing entities
554/// with their own handlers.
555#[derive(Debug, Clone)]
556pub struct AgentDecl {
557    pub name: Ident,
558    /// `key id: Type` — the identifier-typed value identifying instances.
559    pub key_name: Ident,
560    pub key_type: TypeRef,
561    /// `store` fields (v0.81, storage track) — each an access-pattern slot of a
562    /// declared storage kind (`Cell`/`Map`/…). The successor to the removed
563    /// `state { }` record (ADR 0108); every agent declares its state this way.
564    pub store_fields: Vec<StoreField>,
565    /// Invariants (v0.80 §14) — universally-quantified predicates over the
566    /// agent's `store` fields. The phase sits between the fields and the
567    /// handlers; each is checked against the state staged by a handler's writes
568    /// before it commits.
569    pub invariants: Vec<Invariant>,
570    pub handlers: Vec<Handler>,
571    pub documentation: Option<String>,
572    pub span: Span,
573    pub trivia: Trivia,
574}
575
576/// A `store` field (v0.81, storage track). Each is an access-pattern slot of a
577/// declared storage kind: `store <name>: <Kind>[…] [@annotations] [= <init>]`.
578/// The kind and its element type are carried as an ordinary [`TypeRef`]
579/// (`Cell[Int]`, `Map[K, V]`); the checker restricts which heads are storage
580/// kinds. Access-pattern annotations (`@indexed`, …) parse into [`annotations`]
581/// (v0.85, ADR 0111); the checker validates them against the closed registry.
582///
583/// [`annotations`]: StoreField::annotations
584#[derive(Debug, Clone)]
585pub struct StoreField {
586    pub name: Ident,
587    /// The storage kind and its element type(s): `Cell[Int]`, `Map[K, V]`. A
588    /// dedicated [`StoreKind`] rather than a [`TypeRef`] — storage kinds are not
589    /// value types, and the checker dispatches kind-aware operations on the head.
590    pub kind: StoreKind,
591    /// Storage annotations on the field (v0.85, ADR 0111): `@ttl(5.minutes)`,
592    /// `@indexed(by: orderId)`. Parsed in declaration order (after the kind,
593    /// before the initialiser); the checker validates names against the closed
594    /// registry and gates each to the slice that implements it.
595    pub annotations: Vec<Annotation>,
596    /// The fresh-key initial value (`= expr`), if given — same disposition as a
597    /// `state` field's initialiser (ADRs 0003/0004 carry forward).
598    pub init: Option<Expr>,
599    pub documentation: Option<String>,
600    pub span: Span,
601    pub trivia: Trivia,
602}
603
604/// A storage annotation on a `store` field (v0.85, storage track; ADR 0111):
605/// `@<name>(<args>)`. The `name` is matched against the closed registry
606/// (`@indexed`/`@ttl`/`@retain`/`@bounded`) by the checker; the grammar accepts
607/// any identifier so an unknown name is a checker diagnostic, not a parse error.
608/// Arguments are compile-time metadata, restricted to literals (and the `by:`
609/// field-name labels of `@indexed`) by the checker per ADR 0111 D4.
610#[derive(Debug, Clone)]
611pub struct Annotation {
612    pub name: Ident,
613    pub args: Vec<AnnotationArg>,
614    pub span: Span,
615}
616
617/// A single annotation argument (v0.85; ADR 0111): an optional `label:` followed
618/// by a value expression — `by: orderId` (labelled) or `5.minutes` (positional).
619/// The value is parsed as an ordinary [`Expr`] so the duration-literal form
620/// (`5.minutes`, landing with the `Duration` slice) needs no special grammar;
621/// the checker restricts it to a literal where the annotation is functional.
622#[derive(Debug, Clone)]
623pub struct AnnotationArg {
624    pub label: Option<Ident>,
625    pub value: Expr,
626    pub span: Span,
627}
628
629/// A storage kind applied to its element type(s) (v0.81): `Cell[Int]`,
630/// `Map[ReservationId, Reservation]`. The `head` is the kind name (`Cell`,
631/// `Map`, `Set`, `Log`, `Queue`, `Cache`); the checker validates it against the
632/// closed catalogue. Element types are ordinary [`TypeRef`]s. Refined element
633/// types (`Cell[Int where NonNegative]`) ride a later slice (parse_type_ref does
634/// not yet accept an inline refinement in type-argument position).
635#[derive(Debug, Clone)]
636pub struct StoreKind {
637    pub head: Ident,
638    pub args: Vec<TypeRef>,
639    pub span: Span,
640}
641
642/// An agent invariant (v0.80 §14). A named predicate over the agent's state
643/// fields that must hold of every committed state; a commit that would violate
644/// it faults (`InvariantViolation`) before the state is persisted. The
645/// predicate references state fields by bare name, mirroring the design-notes
646/// worked examples (`status == Paid implies paymentRef.isSome()`).
647#[derive(Debug, Clone)]
648pub struct Invariant {
649    pub name: Ident,
650    /// The predicate expression — an ordinary `Bool`-typed expression over the
651    /// state fields, plus `implies` and `is`. The parsed-predicate-on-a-
652    /// declaration shape mirrors [`ActorRefinement::predicate`].
653    pub predicate: Expr,
654    pub documentation: Option<String>,
655    pub span: Span,
656    pub trivia: Trivia,
657}
658
659/// An actor declaration (v0.45 §3.7). An actor is a nominal *contract type*
660/// describing an external party at a boundary — not a runnable entity. A
661/// handler consumes an actor on its `by` clause; the boundary verifies the
662/// declared `auth` scheme and mints a sealed identity (`name.identity`).
663#[derive(Debug, Clone)]
664pub struct ActorDecl {
665    pub name: Ident,
666    /// The authentication scheme from `auth = <Scheme>`, stored as the raw
667    /// identifier. The checker classifies it: `None`/`Internal`/`Bearer` are
668    /// admitted; `Signature` is reserved-and-rejected
669    /// (`bynk.actor.scheme_unsupported`); anything else is
670    /// `bynk.actor.unknown_scheme`. `None` for the refinement form.
671    pub auth: Option<Ident>,
672    /// The scheme's keyed config from `auth = Scheme(key = value, …)` (v0.47
673    /// `Bearer(secret = "…")`; v0.51 generalised for `Signature(secret, header,
674    /// timestamp?, tolerance?)`). Empty for schemes/forms with no config. The
675    /// checker validates which keys each scheme requires/allows.
676    pub auth_config: Vec<SchemeArg>,
677    /// The optional identity type from `, identity = <T>`. Absent ⇒ the
678    /// scheme default (`()` for `None`; a sealed `CallerId` for the `Internal`
679    /// `on call` channel, `()` for other `Internal` channels).
680    pub identity: Option<TypeRef>,
681    /// The reserved-and-rejected refinement form `actor Admin = Base where p`
682    /// (Q3). Parsed so the grammar is fixed now; the checker emits
683    /// `bynk.actor.refinement_unsupported`.
684    pub refinement: Option<ActorRefinement>,
685    pub documentation: Option<String>,
686    pub span: Span,
687    pub trivia: Trivia,
688}
689
690impl ActorDecl {
691    /// The value of a scheme config arg by key, if present (e.g. `secret`,
692    /// `header`).
693    pub fn scheme_arg(&self, key: &str) -> Option<&SchemeArg> {
694        self.auth_config.iter().find(|a| a.key.name == key)
695    }
696}
697
698/// One `key = value` argument in a scheme config (`Scheme(key = value, …)`).
699#[derive(Debug, Clone)]
700pub struct SchemeArg {
701    pub key: Ident,
702    pub value: SchemeArgValue,
703    /// Span of the value, for diagnostics.
704    pub span: Span,
705}
706
707/// A scheme config arg value — a string literal or an integer.
708#[derive(Debug, Clone)]
709pub enum SchemeArgValue {
710    Str(String),
711    Int(i64),
712}
713
714impl SchemeArgValue {
715    pub fn as_str(&self) -> Option<&str> {
716        match self {
717            SchemeArgValue::Str(s) => Some(s),
718            SchemeArgValue::Int(_) => None,
719        }
720    }
721    pub fn as_int(&self) -> Option<i64> {
722        match self {
723            SchemeArgValue::Int(n) => Some(*n),
724            SchemeArgValue::Str(_) => None,
725        }
726    }
727}
728
729/// The reserved refinement form `actor Admin = User where <predicate>` (Q3).
730/// Parsed in Foundations so the grammar is fixed; admission is a later slice.
731#[derive(Debug, Clone)]
732pub struct ActorRefinement {
733    /// The base actor being refined.
734    pub base: Ident,
735    /// The `where` predicate. Parsed but not yet checked.
736    pub predicate: Expr,
737    pub span: Span,
738}
739
740/// The `by (<binder>:)? <Actor>` clause on a handler (v0.45; binder optional in
741/// v0.50). Names the actor contract the handler consumes; when a `binder` is
742/// given, the verified identity binds to it and is read as `binder.identity`.
743/// Omitting the binder (`by <Actor>`) declares-and-verifies the contract without
744/// capturing the identity — for anonymous or verify-and-discard handlers. Sits
745/// after the protocol config and before the parameters.
746#[derive(Debug, Clone)]
747pub struct ByClause {
748    /// The identity binder, if the handler consumes the identity. `None` for the
749    /// binder-less `by <Actor>` form. Required when `actors` names more than one
750    /// (a sum is resolved by matching on the bound actor).
751    pub binder: Option<Ident>,
752    /// The actor contract(s) referenced — each a local actor decl or a prelude
753    /// actor. A single name is the ordinary single-actor handler; more than one
754    /// (`by who: A | B`, v0.52) is an **ordered sum of peer actors** resolved
755    /// first-wins, the body matching on the resolved actor. Always non-empty.
756    pub actors: Vec<Ident>,
757    pub span: Span,
758}
759
760impl ByClause {
761    /// The first (and, for a single-actor handler, only) actor contract named.
762    pub fn primary(&self) -> &Ident {
763        &self.actors[0]
764    }
765    /// Whether this `by` clause names an ordered sum of peer actors (`A | B`).
766    pub fn is_sum(&self) -> bool {
767        self.actors.len() > 1
768    }
769}
770
771/// A handler block — `on call(args) -> T given C1, C2 { body }`.
772/// Used by both services and agents.
773#[derive(Debug, Clone)]
774pub struct Handler {
775    pub kind: HandlerKind,
776    /// For agent handlers, the method-style handler name (e.g.
777    /// `on call addItem(...)`). For service handlers, this is None (just
778    /// `on call(...)`).
779    pub method_name: Option<Ident>,
780    /// The `by <binder>: <Actor>` clause (v0.45), if present. Service handlers
781    /// only; an absent clause inherits the protocol's default actor.
782    pub by_clause: Option<ByClause>,
783    pub params: Vec<Param>,
784    pub return_type: TypeRef,
785    pub given: Vec<CapRef>,
786    pub body: Block,
787    pub documentation: Option<String>,
788    pub span: Span,
789    pub trivia: Trivia,
790}
791
792#[derive(Debug, Clone, PartialEq, Eq)]
793pub enum HandlerKind {
794    /// `on call(...)` — typed RPC (the only kind in v0.5).
795    Call,
796    /// `on http METHOD "path"` — external-facing HTTP route (v0.9).
797    Http { method: HttpMethod, path: String },
798    /// `on cron "expr"` — scheduled task; `expr` is a 5-field cron
799    /// expression (v0.10a).
800    Cron { expr: String },
801    /// `on message(m: T)` — a message off the service's bound queue. The queue
802    /// binding lives on the service's `ServiceProtocol::Queue` (v0.44).
803    Message,
804    /// `on open ...` — the WebSocket upgrade handler (v0.103, real-time track
805    /// slice 3). Exactly one per `from WebSocket` service; carries a mandatory
806    /// `by` clause (edge auth) and receives a fresh owned `Connection[out]`.
807    Open,
808    /// `on close ...` — the WebSocket close handler (v0.106, real-time track slice
809    /// 3b-iii). Optional, ≤1 per `from WebSocket` service; runs when the socket
810    /// closes. Like `on open`, edge-authenticated (`by`), with the identity/params
811    /// recovered from the socket attachment (set at `on open`). (A `from WebSocket`
812    /// `on message` reuses [`HandlerKind::Message`], disambiguated by the protocol.)
813    Close,
814}
815
816/// HTTP methods supported by `on http` handlers (v0.9).
817#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
818pub enum HttpMethod {
819    Get,
820    Post,
821    Put,
822    Patch,
823    Delete,
824}
825
826impl HttpMethod {
827    pub fn as_str(self) -> &'static str {
828        match self {
829            HttpMethod::Get => "GET",
830            HttpMethod::Post => "POST",
831            HttpMethod::Put => "PUT",
832            HttpMethod::Patch => "PATCH",
833            HttpMethod::Delete => "DELETE",
834        }
835    }
836
837    pub fn from_ident(s: &str) -> Option<HttpMethod> {
838        match s {
839            "GET" => Some(HttpMethod::Get),
840            "POST" => Some(HttpMethod::Post),
841            "PUT" => Some(HttpMethod::Put),
842            "PATCH" => Some(HttpMethod::Patch),
843            "DELETE" => Some(HttpMethod::Delete),
844            _ => None,
845        }
846    }
847
848    /// True if this method conventionally has no request body.
849    pub fn forbids_body(self) -> bool {
850        matches!(self, HttpMethod::Get | HttpMethod::Delete)
851    }
852}
853
854/// Payload shape of an `HttpResult[T]` variant (v0.9 §3.3).
855#[derive(Debug, Clone, Copy, PartialEq, Eq)]
856pub enum HttpVariantPayload {
857    /// No payload (e.g. `NoContent`, `Unauthorized`).
858    None,
859    /// Carries a value of the `HttpResult` type parameter `T`.
860    Value,
861    /// Carries a `String` message (e.g. `BadRequest`, `Conflict`).
862    Message,
863    /// Carries a `String` target URL, emitted as a `Location` header — the
864    /// redirect variants (`Found`, `SeeOther`, `PermanentRedirect`, …).
865    Location,
866    /// Carries a `Stream[String]`, emitted as an SSE (`text/event-stream`)
867    /// streaming body — the `Streaming` (200) variant (v0.101, real-time track
868    /// slice 1).
869    Streamed,
870    /// Carries `(body: Bytes, contentType: String)` — the author-owned raw body
871    /// written straight into the response with the declared `content-type` and
872    /// **no codec** (the typed-wire guarantee is deliberately off). The `Raw`
873    /// (200) variant (v0.111); the first two-argument payload shape.
874    Raw,
875}
876
877/// One variant of the built-in `HttpResult[T]` sum (v0.9 §3.3).
878#[derive(Debug, Clone, Copy)]
879pub struct HttpVariant {
880    pub name: &'static str,
881    pub payload: HttpVariantPayload,
882    pub status: u16,
883}
884
885/// All `HttpResult[T]` variants, in declaration order (ascending status). The
886/// vocabulary tracks the common, modern HTTP status codes (RFC 9110): success
887/// and created/accepted (`Value`), redirects carrying a `Location` URL, and
888/// the client/server failures that handlers routinely return (`Message` when
889/// an explanation helps the caller, `None` for self-describing statuses).
890pub const HTTP_VARIANTS: &[HttpVariant] = &[
891    // ── 2xx success ──────────────────────────────────────────────────────
892    HttpVariant {
893        name: "Ok",
894        payload: HttpVariantPayload::Value,
895        status: 200,
896    },
897    // v0.101 (real-time track slice 1): a 200 whose body is a streamed
898    // `Stream[String]`, SSE-framed. Status precedes the body, so streaming is
899    // 200-only — pre-stream failures are ordinary variants returned instead.
900    HttpVariant {
901        name: "Streaming",
902        payload: HttpVariantPayload::Streamed,
903        status: 200,
904    },
905    // v0.111: a 200 whose body is an author-owned `Bytes` written straight into
906    // the response with the declared `content-type` — no codec runs. 200-only,
907    // like `Streaming`: it serves service-tier raw bodies (`robots.txt`,
908    // `sitemap.xml`, feeds, a QR PNG), not custom-status error pages.
909    HttpVariant {
910        name: "Raw",
911        payload: HttpVariantPayload::Raw,
912        status: 200,
913    },
914    HttpVariant {
915        name: "Created",
916        payload: HttpVariantPayload::Value,
917        status: 201,
918    },
919    HttpVariant {
920        name: "Accepted",
921        payload: HttpVariantPayload::Value,
922        status: 202,
923    },
924    HttpVariant {
925        name: "NoContent",
926        payload: HttpVariantPayload::None,
927        status: 204,
928    },
929    // ── 3xx redirection (carry a `Location` URL) ─────────────────────────
930    HttpVariant {
931        name: "MovedPermanently",
932        payload: HttpVariantPayload::Location,
933        status: 301,
934    },
935    HttpVariant {
936        name: "Found",
937        payload: HttpVariantPayload::Location,
938        status: 302,
939    },
940    HttpVariant {
941        name: "SeeOther",
942        payload: HttpVariantPayload::Location,
943        status: 303,
944    },
945    HttpVariant {
946        name: "TemporaryRedirect",
947        payload: HttpVariantPayload::Location,
948        status: 307,
949    },
950    HttpVariant {
951        name: "PermanentRedirect",
952        payload: HttpVariantPayload::Location,
953        status: 308,
954    },
955    // ── 4xx client error ─────────────────────────────────────────────────
956    HttpVariant {
957        name: "BadRequest",
958        payload: HttpVariantPayload::Message,
959        status: 400,
960    },
961    HttpVariant {
962        name: "Unauthorized",
963        payload: HttpVariantPayload::None,
964        status: 401,
965    },
966    HttpVariant {
967        name: "Forbidden",
968        payload: HttpVariantPayload::None,
969        status: 403,
970    },
971    HttpVariant {
972        name: "NotFound",
973        payload: HttpVariantPayload::None,
974        status: 404,
975    },
976    HttpVariant {
977        name: "MethodNotAllowed",
978        payload: HttpVariantPayload::None,
979        status: 405,
980    },
981    HttpVariant {
982        name: "NotAcceptable",
983        payload: HttpVariantPayload::None,
984        status: 406,
985    },
986    HttpVariant {
987        name: "RequestTimeout",
988        payload: HttpVariantPayload::None,
989        status: 408,
990    },
991    HttpVariant {
992        name: "Conflict",
993        payload: HttpVariantPayload::Message,
994        status: 409,
995    },
996    HttpVariant {
997        name: "Gone",
998        payload: HttpVariantPayload::None,
999        status: 410,
1000    },
1001    HttpVariant {
1002        name: "LengthRequired",
1003        payload: HttpVariantPayload::None,
1004        status: 411,
1005    },
1006    HttpVariant {
1007        name: "PayloadTooLarge",
1008        payload: HttpVariantPayload::Message,
1009        status: 413,
1010    },
1011    HttpVariant {
1012        name: "UnsupportedMediaType",
1013        payload: HttpVariantPayload::Message,
1014        status: 415,
1015    },
1016    HttpVariant {
1017        name: "UnprocessableEntity",
1018        payload: HttpVariantPayload::Message,
1019        status: 422,
1020    },
1021    HttpVariant {
1022        name: "TooManyRequests",
1023        payload: HttpVariantPayload::Message,
1024        status: 429,
1025    },
1026    HttpVariant {
1027        name: "UnavailableForLegalReasons",
1028        payload: HttpVariantPayload::Message,
1029        status: 451,
1030    },
1031    // ── 5xx server error ─────────────────────────────────────────────────
1032    HttpVariant {
1033        name: "ServerError",
1034        payload: HttpVariantPayload::Message,
1035        status: 500,
1036    },
1037    HttpVariant {
1038        name: "NotImplemented",
1039        payload: HttpVariantPayload::Message,
1040        status: 501,
1041    },
1042    HttpVariant {
1043        name: "BadGateway",
1044        payload: HttpVariantPayload::Message,
1045        status: 502,
1046    },
1047    HttpVariant {
1048        name: "ServiceUnavailable",
1049        payload: HttpVariantPayload::Message,
1050        status: 503,
1051    },
1052    HttpVariant {
1053        name: "GatewayTimeout",
1054        payload: HttpVariantPayload::Message,
1055        status: 504,
1056    },
1057];
1058
1059/// Find an `HttpResult[T]` variant by name. Returns the variant info or
1060/// `None` if the name doesn't match.
1061pub fn http_variant(name: &str) -> Option<HttpVariant> {
1062    HTTP_VARIANTS.iter().copied().find(|v| v.name == name)
1063}
1064
1065/// Payload shape of a `QueueResult` variant (v0.44). Non-generic — a verdict
1066/// carries no value; `Retry` carries a `String` reason for the log path.
1067#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1068pub enum QueueVariantPayload {
1069    /// No payload (`Ack`).
1070    None,
1071    /// Carries a `String` reason (`Retry`).
1072    Message,
1073}
1074
1075/// One variant of the built-in `QueueResult` sum (v0.44).
1076#[derive(Debug, Clone, Copy)]
1077pub struct QueueVariant {
1078    pub name: &'static str,
1079    pub payload: QueueVariantPayload,
1080}
1081
1082/// All `QueueResult` variants, in declaration order. `Ack` confirms the
1083/// message; `Retry` redelivers it, carrying a reason for observability.
1084pub const QUEUE_VARIANTS: &[QueueVariant] = &[
1085    QueueVariant {
1086        name: "Ack",
1087        payload: QueueVariantPayload::None,
1088    },
1089    QueueVariant {
1090        name: "Retry",
1091        payload: QueueVariantPayload::Message,
1092    },
1093];
1094
1095/// Find a `QueueResult` variant by name.
1096pub fn queue_variant(name: &str) -> Option<QueueVariant> {
1097    QUEUE_VARIANTS.iter().copied().find(|v| v.name == name)
1098}
1099
1100#[derive(Debug, Clone)]
1101pub struct TypeDecl {
1102    pub name: Ident,
1103    pub body: TypeBody,
1104    /// Documentation block attached to this declaration (v0.3).
1105    pub documentation: Option<String>,
1106    pub span: Span,
1107    pub trivia: Trivia,
1108}
1109
1110/// The right-hand side of a `type` declaration. In v0/v0.1 only the
1111/// `Refined` variant existed; v0.2 adds records and sums; v0.3 adds opaque.
1112#[derive(Debug, Clone)]
1113pub enum TypeBody {
1114    /// Refined base type: `BaseType where refinement`.
1115    Refined {
1116        base: BaseType,
1117        base_span: Span,
1118        refinement: Option<Refinement>,
1119    },
1120    /// Record type: `{ field: T where ..., ... }`.
1121    Record(RecordBody),
1122    /// Sum type: pipe-form variants or `enum { ... }` shorthand.
1123    Sum(SumBody),
1124    /// Opaque base type: `opaque BaseType (where refinement)?` (v0.3 §3.4).
1125    /// Identity is nominal; the base type is hidden outside the defining commons.
1126    Opaque {
1127        base: BaseType,
1128        base_span: Span,
1129        refinement: Option<Refinement>,
1130    },
1131}
1132
1133/// Body of a record-type declaration (v0.2 §3.1).
1134#[derive(Debug, Clone)]
1135pub struct RecordBody {
1136    pub fields: Vec<RecordField>,
1137    pub span: Span,
1138}
1139
1140/// One field of a record type declaration. Each field may carry inline
1141/// refinement, which is enforced at construction time on the field's value.
1142#[derive(Debug, Clone)]
1143pub struct RecordField {
1144    pub name: Ident,
1145    pub type_ref: TypeRef,
1146    pub refinement: Option<Refinement>,
1147    /// v0.11: an optional initial-value expression. Only meaningful on agent
1148    /// `state` fields (the field's fresh-key value); ignored / rejected on
1149    /// record-type fields by the checker.
1150    pub init: Option<Expr>,
1151    pub span: Span,
1152}
1153
1154/// Body of a sum-type declaration (v0.2 §3.2).
1155#[derive(Debug, Clone)]
1156pub struct SumBody {
1157    pub variants: Vec<Variant>,
1158    pub span: Span,
1159}
1160
1161/// One variant of a sum type. Variants may have payload fields; a
1162/// payload-less variant is a simple tag.
1163#[derive(Debug, Clone)]
1164pub struct Variant {
1165    pub name: Ident,
1166    pub payload: Vec<VariantField>,
1167    pub span: Span,
1168}
1169
1170/// One payload field of a sum variant. Variant payload fields use named
1171/// declarations like record fields, but do not carry refinement in v0.2.
1172#[derive(Debug, Clone)]
1173pub struct VariantField {
1174    pub name: Ident,
1175    pub type_ref: TypeRef,
1176    pub span: Span,
1177}
1178
1179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1180pub enum BaseType {
1181    Int,
1182    String,
1183    Bool,
1184    Float,
1185    /// `Duration` (v0.86, ADR 0112) — a span of time, a distinct base type
1186    /// erased to TS `number` carrying milliseconds (the `Clock` unit). Modelled
1187    /// on `Float`: Bynk-side-only, no implicit `Int` coercion (save the one
1188    /// sanctioned clock-math mix).
1189    Duration,
1190    /// `Instant` (v0.90, ADR 0114) — an absolute point in time, a distinct base
1191    /// type erased to TS `number` carrying Unix epoch milliseconds (the
1192    /// `Clock` unit). No literal (minted by `Clock.now()`); arithmetic composes
1193    /// with `Duration` (`Instant ± Duration -> Instant`, `Instant − Instant ->
1194    /// Duration`). Supersedes ADR 0112 D4's `Int`↔`Duration` clock-math mix.
1195    Instant,
1196    /// `Bytes` (v0.110, ADR 0142) — an immutable finite octet sequence, the
1197    /// seventh base type. Unlike its neighbours it does **not** erase to TS
1198    /// `number`: a `Bytes` lowers to a `Uint8Array`. No source literal
1199    /// (constructed via `Bytes.fromUtf8`/`fromBase64`/`empty`); `==` compares
1200    /// by content (real emitter codegen, not host `===`); wires as a base64
1201    /// JSON string; not `Map`-keyable and not orderable.
1202    Bytes,
1203}
1204
1205impl BaseType {
1206    pub fn name(self) -> &'static str {
1207        match self {
1208            BaseType::Int => "Int",
1209            BaseType::String => "String",
1210            BaseType::Bool => "Bool",
1211            BaseType::Float => "Float",
1212            BaseType::Duration => "Duration",
1213            BaseType::Instant => "Instant",
1214            BaseType::Bytes => "Bytes",
1215        }
1216    }
1217}
1218
1219/// A `Duration` literal unit (v0.86, ADR 0112) — the closed set of suffixes in a
1220/// `<int>.<unit>` literal. Each maps to a fixed millisecond factor (`Duration`
1221/// erases to `Int` milliseconds).
1222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1223pub enum DurationUnit {
1224    Milliseconds,
1225    Seconds,
1226    Minutes,
1227    Hours,
1228    Days,
1229}
1230
1231impl DurationUnit {
1232    /// Resolve a unit name (`minutes`) to its variant, or `None` if it is not one
1233    /// of the closed set. Used by the parser to recognise an `<int>.<unit>`
1234    /// literal; an unrecognised name leaves the expression a field access.
1235    pub fn from_name(name: &str) -> Option<Self> {
1236        Some(match name {
1237            "milliseconds" => DurationUnit::Milliseconds,
1238            "seconds" => DurationUnit::Seconds,
1239            "minutes" => DurationUnit::Minutes,
1240            "hours" => DurationUnit::Hours,
1241            "days" => DurationUnit::Days,
1242            _ => return None,
1243        })
1244    }
1245
1246    /// The unit name as written.
1247    pub fn name(self) -> &'static str {
1248        match self {
1249            DurationUnit::Milliseconds => "milliseconds",
1250            DurationUnit::Seconds => "seconds",
1251            DurationUnit::Minutes => "minutes",
1252            DurationUnit::Hours => "hours",
1253            DurationUnit::Days => "days",
1254        }
1255    }
1256
1257    /// The unit's value in milliseconds.
1258    pub fn millis(self) -> i64 {
1259        match self {
1260            DurationUnit::Milliseconds => 1,
1261            DurationUnit::Seconds => 1_000,
1262            DurationUnit::Minutes => 60_000,
1263            DurationUnit::Hours => 3_600_000,
1264            DurationUnit::Days => 86_400_000,
1265        }
1266    }
1267}
1268
1269/// An integer refinement bound (v0.40, ADR 0073): the parsed value plus the
1270/// bound's source span (covering a leading `-`). Value-only beyond the span —
1271/// ints have one canonical printed form, so the formatter stays idempotent
1272/// without a stored lexeme. The span backs the `InRange`-swap quick-fix.
1273#[derive(Debug, Clone)]
1274pub struct IntBound {
1275    pub value: i64,
1276    pub span: Span,
1277}
1278
1279/// A float refinement bound (v0.21): the parsed value plus the signed source
1280/// lexeme (for byte-stable emission). v0.40 (ADR 0073): also the source span,
1281/// for the `InRange`-swap quick-fix.
1282#[derive(Debug, Clone)]
1283pub struct FloatBound {
1284    pub value: f64,
1285    pub lexeme: String,
1286    pub span: Span,
1287}
1288
1289#[derive(Debug, Clone)]
1290pub struct Refinement {
1291    pub predicates: Vec<RefinementPred>,
1292    pub span: Span,
1293}
1294
1295#[derive(Debug, Clone)]
1296pub struct RefinementPred {
1297    pub kind: PredKind,
1298    pub span: Span,
1299}
1300
1301#[derive(Debug, Clone)]
1302pub enum PredKind {
1303    Matches(String),
1304    InRange(IntBound, IntBound),
1305    /// `InRange` with float bounds (v0.21) — a separate variant so every
1306    /// `Int` refinement path stays untouched. Bounds keep their source
1307    /// lexemes (including any sign) so emitted runtime checks are
1308    /// byte-stable.
1309    InRangeF(FloatBound, FloatBound),
1310    MinLength(i64),
1311    MaxLength(i64),
1312    Length(i64),
1313    NonNegative,
1314    Positive,
1315    NonEmpty,
1316}
1317
1318impl PredKind {
1319    pub fn name(&self) -> &'static str {
1320        match self {
1321            PredKind::Matches(_) => "Matches",
1322            PredKind::InRange(..) | PredKind::InRangeF(..) => "InRange",
1323            PredKind::MinLength(_) => "MinLength",
1324            PredKind::MaxLength(_) => "MaxLength",
1325            PredKind::Length(_) => "Length",
1326            PredKind::NonNegative => "NonNegative",
1327            PredKind::Positive => "Positive",
1328            PredKind::NonEmpty => "NonEmpty",
1329        }
1330    }
1331}
1332
1333/// A function type parameter (v0.20a, `fn name[A, B](…)`). A struct rather
1334/// than a bare Ident so the ADR-0028 "bound-capable" promise is a later field
1335/// addition, not a representation change.
1336#[derive(Debug, Clone)]
1337pub struct TypeParam {
1338    pub name: Ident,
1339    pub span: Span,
1340}
1341
1342/// A lambda expression (v0.20a): `(params) => expr` or `(params) => { … }`.
1343/// `=>` is the value arrow (shared with `match`); param annotations are
1344/// optional where an expected function type supplies them.
1345#[derive(Debug, Clone)]
1346pub struct LambdaExpr {
1347    pub params: Vec<LambdaParam>,
1348    pub body: Box<Expr>,
1349    pub span: Span,
1350}
1351
1352/// A lambda parameter. A separate type from [`Param`] because its annotation
1353/// is optional — `Param.type_ref` stays mandatory at every signature site.
1354#[derive(Debug, Clone)]
1355pub struct LambdaParam {
1356    pub name: Ident,
1357    pub type_ref: Option<TypeRef>,
1358    pub span: Span,
1359}
1360
1361#[derive(Debug, Clone)]
1362pub struct FnDecl {
1363    /// v0.20a: `[A, B]` type parameters; empty for non-generic functions.
1364    pub type_params: Vec<TypeParam>,
1365    /// Free function or method (`TypeName.methodName`). See [`FnName`].
1366    pub name: FnName,
1367    pub params: Vec<Param>,
1368    pub return_type: TypeRef,
1369    pub body: Block,
1370    /// True when the first parameter is the special `self` parameter. Only
1371    /// valid for method declarations.
1372    pub has_self: bool,
1373    /// Documentation block attached to this declaration (v0.3).
1374    pub documentation: Option<String>,
1375    pub span: Span,
1376    pub trivia: Trivia,
1377}
1378
1379/// A function-declaration name: either a free function `f` or a method
1380/// `T.method` (v0.2 §3.6).
1381#[derive(Debug, Clone)]
1382pub enum FnName {
1383    /// `fn name(...)` — a free function.
1384    Free(Ident),
1385    /// `fn TypeName.methodName(...)` — a method attached to a type.
1386    Method {
1387        type_name: Ident,
1388        method_name: Ident,
1389    },
1390}
1391
1392impl FnName {
1393    /// The function's short name for diagnostics. For methods returns the
1394    /// method portion only; the type prefix is recovered via `type_name`.
1395    pub fn ident(&self) -> &Ident {
1396        match self {
1397            FnName::Free(id) => id,
1398            FnName::Method { method_name, .. } => method_name,
1399        }
1400    }
1401
1402    /// For methods, the attached type's identifier; `None` for free fns.
1403    pub fn type_name(&self) -> Option<&Ident> {
1404        match self {
1405            FnName::Free(_) => None,
1406            FnName::Method { type_name, .. } => Some(type_name),
1407        }
1408    }
1409
1410    /// The displayed full name (e.g., `Money.add` or `parseSku`).
1411    pub fn display(&self) -> String {
1412        match self {
1413            FnName::Free(id) => id.name.clone(),
1414            FnName::Method {
1415                type_name,
1416                method_name,
1417            } => format!("{}.{}", type_name.name, method_name.name),
1418        }
1419    }
1420}
1421
1422/// A brace-delimited block of statements ending in a tail expression
1423/// whose value is the block's value (spec v0.1 §3.1).
1424#[derive(Debug, Clone)]
1425pub struct Block {
1426    pub statements: Vec<Statement>,
1427    pub tail: Box<Expr>,
1428    pub span: Span,
1429    /// Line comments that appear between the last statement (or the
1430    /// opening brace) and the tail expression. Preserved here because
1431    /// expressions do not carry trivia in v1.1.
1432    pub tail_leading_comments: Vec<String>,
1433}
1434
1435/// Block-level statement.
1436#[derive(Debug, Clone)]
1437pub enum Statement {
1438    /// `let name (: T)? = expr` — pure binding (v0.1).
1439    Let(LetStmt),
1440    /// `let name (: T)? <- expr` — effectful binding (v0.5).
1441    EffectLet(LetStmt),
1442    /// `expect expr` — verify a Bool predicate at test runtime (v0.7; renamed
1443    /// from `assert` in v0.112). Only valid inside test case bodies.
1444    Expect(ExpectStmt),
1445    /// `~> expr` — an asynchronous fire-and-forget send (v0.79). The caller does
1446    /// not await the reply; legal only when the reply is `Effect[()]`. No binder.
1447    Send(SendStmt),
1448    /// `name := expr` — a `Cell` store write (v0.81, storage track). The
1449    /// unconditional write form; `.update(fn)` (a method call) is the
1450    /// read-modify-write form. ADR 0108.
1451    Assign(AssignStmt),
1452}
1453
1454impl Statement {
1455    pub fn span(&self) -> Span {
1456        match self {
1457            Statement::Let(l) | Statement::EffectLet(l) => l.span,
1458            Statement::Expect(a) => a.span,
1459            Statement::Send(s) => s.span,
1460            Statement::Assign(a) => a.span,
1461        }
1462    }
1463}
1464
1465#[derive(Debug, Clone)]
1466pub struct ExpectStmt {
1467    pub value: Expr,
1468    pub span: Span,
1469    pub trivia: Trivia,
1470}
1471
1472/// `name := expr` — a `Cell` store write (v0.81, storage track). `target` is the
1473/// `Cell` field being written (a bare name for now; the checker resolves it to a
1474/// `store` field). `value` is the new value.
1475#[derive(Debug, Clone)]
1476pub struct AssignStmt {
1477    pub target: Ident,
1478    pub value: Expr,
1479    pub span: Span,
1480    pub trivia: Trivia,
1481}
1482
1483#[derive(Debug, Clone)]
1484pub struct LetStmt {
1485    pub name: Ident,
1486    pub type_annot: Option<TypeRef>,
1487    pub value: Expr,
1488    pub span: Span,
1489    pub trivia: Trivia,
1490}
1491
1492#[derive(Debug, Clone)]
1493pub struct SendStmt {
1494    /// The send target — a recipient call, e.g. `Logger.info(msg)`.
1495    pub value: Expr,
1496    pub span: Span,
1497    pub trivia: Trivia,
1498}
1499
1500#[derive(Debug, Clone)]
1501pub struct Param {
1502    pub name: Ident,
1503    pub type_ref: TypeRef,
1504    pub span: Span,
1505}
1506
1507#[derive(Debug, Clone)]
1508pub enum TypeRef {
1509    Base(BaseType, Span),
1510    Named(Ident),
1511    /// `Result[T, E]` — the built-in generic Result type (v0.1).
1512    Result(Box<TypeRef>, Box<TypeRef>, Span),
1513    /// `Option[T]` — the built-in generic Option type (v0.2).
1514    Option(Box<TypeRef>, Span),
1515    /// `Effect[T]` — the built-in generic Effect type (v0.5).
1516    Effect(Box<TypeRef>, Span),
1517    /// `HttpResult[T]` — the built-in HTTP-result sum (v0.9).
1518    HttpResult(Box<TypeRef>, Span),
1519    /// `QueueResult` — the built-in queue verdict sum (`Ack | Retry`),
1520    /// non-generic; the required return of a queue handler (v0.44).
1521    QueueResult(Span),
1522    /// `List[T]` — the built-in generic immutable list type (v0.20b).
1523    List(Box<TypeRef>, Span),
1524    /// `Map[K, V]` — the built-in generic immutable map type (v0.20b).
1525    /// Keys are confined to value-keyable types
1526    /// (`bynk.types.unkeyable_map_key`).
1527    Map(Box<TypeRef>, Box<TypeRef>, Span),
1528    /// `Query[T]` — the built-in lazy storage-read description (v0.91, ADR 0115).
1529    /// Nameable in a pure helper's return type; non-storable and non-boundary
1530    /// (like `Effect`/`Fn`).
1531    Query(Box<TypeRef>, Span),
1532    /// `Stream[T]` — the value-over-time primitive (v0.100, real-time track
1533    /// slice 0). A lazy, pull-shaped sequence produced over time; non-storable
1534    /// and non-boundary (like `Query`/`Effect`/`Fn`).
1535    Stream(Box<TypeRef>, Span),
1536    /// `Connection[F]` — a held WebSocket connection (v0.102, real-time track
1537    /// slice 2). `F` is the server→client frame type. A `Held` resource:
1538    /// non-serialisable, non-boundary, and governed by the linearity discipline
1539    /// (§2.9); storable only in `Cell[Option[Connection]]` / `Map[K, Connection]`.
1540    Connection(Box<TypeRef>, Span),
1541    /// `ValidationError` — the built-in error type used by refined-type
1542    /// constructors (v0.1).
1543    ValidationError(Span),
1544    /// `JsonError` — the built-in JSON-decode error type (v0.22b). A
1545    /// uniform record (`kind`/`path`/`message`, all `String`) the codec
1546    /// maps `BoundaryError` variants and parse failures into.
1547    JsonError(Span),
1548    /// `()` — the unit type (v0.5).
1549    Unit(Span),
1550    /// `A -> B` / `(A, B) -> C` / `() -> B` — a function type (v0.20a).
1551    /// Right-associative; effectful iff the return type is `Effect[_]`
1552    /// (the structural rule). Confined to non-boundary positions
1553    /// (`bynk.types.function_at_boundary`).
1554    Fn(Vec<TypeRef>, Box<TypeRef>, Span),
1555}
1556
1557impl TypeRef {
1558    pub fn span(&self) -> Span {
1559        match self {
1560            TypeRef::Base(_, s) => *s,
1561            TypeRef::Named(id) => id.span,
1562            TypeRef::Result(_, _, s) => *s,
1563            TypeRef::Option(_, s) => *s,
1564            TypeRef::Effect(_, s) => *s,
1565            TypeRef::HttpResult(_, s) => *s,
1566            TypeRef::QueueResult(s) => *s,
1567            TypeRef::List(_, s) => *s,
1568            TypeRef::Map(_, _, s) => *s,
1569            TypeRef::Query(_, s) => *s,
1570            TypeRef::Stream(_, s) => *s,
1571            TypeRef::Connection(_, s) => *s,
1572            TypeRef::ValidationError(s) => *s,
1573            TypeRef::JsonError(s) => *s,
1574            TypeRef::Unit(s) => *s,
1575            TypeRef::Fn(_, _, s) => *s,
1576        }
1577    }
1578}
1579
1580#[derive(Debug, Clone)]
1581pub struct Expr {
1582    pub kind: ExprKind,
1583    pub span: Span,
1584}
1585
1586#[derive(Debug, Clone)]
1587pub enum ExprKind {
1588    IntLit(i64),
1589    /// A float literal (v0.21). The lexeme is kept alongside the parsed
1590    /// value so emission and formatting are byte-stable (`1e10` must not
1591    /// normalise to `10000000000`).
1592    FloatLit {
1593        value: f64,
1594        lexeme: String,
1595    },
1596    /// A duration literal `<int>.<unit>` (v0.86, ADR 0112): `5.minutes`,
1597    /// `30.days`. The parser recognises the `IntLit . <unit>` shape and records
1598    /// the magnitude, the unit, and the resolved milliseconds (the value the
1599    /// emitter lowers to). Typed `Duration`.
1600    DurationLit {
1601        /// The integer magnitude as written (`5` in `5.minutes`).
1602        value: i64,
1603        /// The unit name (`minutes`), one of the closed set.
1604        unit: DurationUnit,
1605        /// The value in milliseconds — `value * unit factor`.
1606        millis: i64,
1607    },
1608    StrLit(String),
1609    /// An interpolated string `"… \(expr) …"` (v0.43, ADR 0075). Chunks and
1610    /// holes alternate. A plain `"…"` with no holes stays [`ExprKind::StrLit`],
1611    /// so existing code and the emitter/formatter fast-path are untouched.
1612    InterpStr(Vec<InterpPart>),
1613    BoolLit(bool),
1614    Ident(Ident),
1615    Call {
1616        name: Ident,
1617        /// v0.20a: explicit type arguments (`name[T](…)`); empty when absent.
1618        type_args: Vec<TypeRef>,
1619        args: Vec<Expr>,
1620    },
1621    /// A lambda (v0.20a). See [`LambdaExpr`].
1622    Lambda(LambdaExpr),
1623    BinOp(BinOp, Box<Expr>, Box<Expr>),
1624    UnaryOp(UnaryOp, Box<Expr>),
1625    Paren(Box<Expr>),
1626    /// `{ stmts; expr }` — block expression (v0.1).
1627    Block(Block),
1628    /// `if cond { then } else { else }` (v0.1).
1629    If {
1630        cond: Box<Expr>,
1631        then_block: Box<Block>,
1632        else_block: Box<Block>,
1633    },
1634    /// `Ok(value)` — Result success constructor (v0.1).
1635    Ok(Box<Expr>),
1636    /// `Err(error)` — Result failure constructor (v0.1).
1637    Err(Box<Expr>),
1638    /// `expr?` — propagation operator (v0.1).
1639    Question(Box<Expr>),
1640    /// `TypeName.method(args)` — qualified static call on a type
1641    /// (v0.1: only refined-type `of`; v0.2: any static method or variant
1642    /// constructor for sum types). The resolver decides which.
1643    ConstructorCall {
1644        type_name: Ident,
1645        method: Ident,
1646        args: Vec<Expr>,
1647    },
1648    /// `TypeName { field: value, ... }` — record construction (v0.2).
1649    RecordConstruction {
1650        type_name: Ident,
1651        fields: Vec<FieldInit>,
1652    },
1653    /// `receiver.field` — field access on a record value (v0.2). v0.3 adds
1654    /// `.raw` on opaque types within the defining commons.
1655    FieldAccess {
1656        receiver: Box<Expr>,
1657        field: Ident,
1658    },
1659    /// `receiver.method(args)` — instance method call (v0.2). The
1660    /// resolver determines the receiver's type and looks up the method.
1661    MethodCall {
1662        receiver: Box<Expr>,
1663        method: Ident,
1664        /// v0.22b: explicit type arguments on a qualified static
1665        /// (`Json.decode[T](…)`); empty when absent. The same-line-`[`
1666        /// rule applies as for `Call` type application (0039).
1667        type_args: Vec<TypeRef>,
1668        args: Vec<Expr>,
1669    },
1670    /// `match disc { arm+ }` — pattern matching (v0.2).
1671    Match {
1672        discriminant: Box<Expr>,
1673        arms: Vec<MatchArm>,
1674    },
1675    /// `expr is pattern` — pattern test, returns Bool (v0.2).
1676    Is {
1677        value: Box<Expr>,
1678        pattern: Pattern,
1679    },
1680    /// `Some(value)` — Option Some constructor (v0.2).
1681    Some(Box<Expr>),
1682    /// `None` — Option None constructor (v0.2).
1683    None,
1684    /// `()` — unit literal (v0.5).
1685    UnitLit,
1686    /// `TypeName { ...base, field: value, ... }` or `{ ...base, ... }` —
1687    /// record spread expression (v0.5).
1688    RecordSpread {
1689        /// Optional type prefix (`TypeName { ...base }`). Absent for the
1690        /// bare form used inside `commit`.
1691        type_name: Option<Ident>,
1692        /// The base record being spread.
1693        base: Box<Expr>,
1694        /// Field overrides (always full `name: value` form — never shorthand).
1695        overrides: Vec<FieldInit>,
1696    },
1697    /// `Effect.pure(value)` — wrap a synchronous value into `Effect[T]`
1698    /// (v0.5). Recognised in the parser as a special-form.
1699    EffectPure(Box<Expr>),
1700    /// `expect expr` — expectation as an expression of type `()` (v0.9.1;
1701    /// renamed from `assert` in v0.112). Valid only inside test bodies. Evaluates
1702    /// `expr` (must be Bool); if false, the surrounding test case fails.
1703    Expect(Box<Expr>),
1704    /// `Val[T]`, `Val[T](args)` — test-context value construction (v0.9.4).
1705    /// `args` is empty for the bare form and holds the pin arguments for
1706    /// `Val[T](...)`. The record-override form `Val[T] { ... }` is not yet
1707    /// parsed. Valid only inside test bodies; has type `T`.
1708    Val {
1709        type_ref: TypeRef,
1710        args: Vec<Expr>,
1711    },
1712    /// `[a, b, c]` — list literal (v0.20b). An empty `[]` requires an
1713    /// expected type (`bynk.types.uninferable_element_type`).
1714    ListLit(Vec<Expr>),
1715}
1716
1717/// One part of an interpolated string (v0.43, ADR 0075). An
1718/// [`ExprKind::InterpStr`] holds an alternating run of these.
1719#[derive(Debug, Clone)]
1720pub enum InterpPart {
1721    /// Literal text between holes, with escapes already resolved.
1722    Chunk(String),
1723    /// An interpolated expression `\(expr)`. Type-checked by the hole rule
1724    /// (base scalars only; see the checker) and lowered into a template-
1725    /// literal `${…}` slot.
1726    Hole(Box<Expr>),
1727}
1728
1729/// One field-initialiser inside a record construction expression:
1730/// either `name: expr` or the shorthand `name` (which requires a binding
1731/// of the same name in scope and uses its value).
1732#[derive(Debug, Clone)]
1733pub struct FieldInit {
1734    pub name: Ident,
1735    /// `None` means shorthand — the field's value is the same-named binding.
1736    pub value: Option<Expr>,
1737    pub span: Span,
1738}
1739
1740/// One arm of a `match` expression: `pattern => body`.
1741#[derive(Debug, Clone)]
1742pub struct MatchArm {
1743    pub pattern: Pattern,
1744    pub body: MatchBody,
1745    pub span: Span,
1746}
1747
1748/// The right-hand side of a match arm — either a single expression or
1749/// a block.
1750#[derive(Debug, Clone)]
1751pub enum MatchBody {
1752    Expr(Expr),
1753    Block(Block),
1754}
1755
1756impl MatchBody {
1757    pub fn span(&self) -> Span {
1758        match self {
1759            MatchBody::Expr(e) => e.span,
1760            MatchBody::Block(b) => b.span,
1761        }
1762    }
1763}
1764
1765/// A pattern (v0.2 §3.8). Patterns appear in `match` arms and as the
1766/// right-hand side of the `is` operator.
1767#[derive(Debug, Clone)]
1768pub enum Pattern {
1769    /// `_` — matches any value, no bindings.
1770    Wildcard(Span),
1771    /// `Variant` or `Variant(bindings)` or `TypeName.Variant(bindings)`.
1772    Variant {
1773        /// Optional qualifier: `TypeName.Variant`.
1774        type_name: Option<Ident>,
1775        /// The variant name.
1776        variant: Ident,
1777        /// Payload bindings (empty for nullary variants).
1778        bindings: Vec<PatternBinding>,
1779        span: Span,
1780    },
1781}
1782
1783impl Pattern {
1784    pub fn span(&self) -> Span {
1785        match self {
1786            Pattern::Wildcard(s) => *s,
1787            Pattern::Variant { span, .. } => *span,
1788        }
1789    }
1790}
1791
1792/// A single binding inside a variant pattern. Two surface forms:
1793/// `name` (positional — bind the i-th payload field) and
1794/// `fieldName: bindName` (named — bind the named payload field).
1795/// Both forms also accept `_` as the bind name to discard.
1796#[derive(Debug, Clone)]
1797pub struct PatternBinding {
1798    /// Source form: positional or named.
1799    pub kind: PatternBindingKind,
1800    pub span: Span,
1801}
1802
1803#[derive(Debug, Clone)]
1804pub enum PatternBindingKind {
1805    /// `name` (or `_`): bind the payload field at this position to `name`.
1806    Positional { name: Ident },
1807    /// `field: name` (or `field: _`): bind the named payload field to `name`.
1808    Named { field: Ident, name: Ident },
1809}
1810
1811impl PatternBinding {
1812    /// The local name introduced by this binding (used for scope).
1813    /// `_` is a sentinel for "no binding"; callers should compare against it.
1814    pub fn local_name(&self) -> &Ident {
1815        match &self.kind {
1816            PatternBindingKind::Positional { name } => name,
1817            PatternBindingKind::Named { name, .. } => name,
1818        }
1819    }
1820
1821    pub fn is_wildcard(&self) -> bool {
1822        self.local_name().name == "_"
1823    }
1824}
1825
1826#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1827pub enum BinOp {
1828    /// `P implies Q` — logical implication (v0.80). Desugars to `!P || Q`; sits
1829    /// at the lowest precedence (below `||`). Reads directionally (P → Q).
1830    Implies,
1831    Or,
1832    And,
1833    Eq,
1834    NotEq,
1835    Lt,
1836    LtEq,
1837    Gt,
1838    GtEq,
1839    Add,
1840    Sub,
1841    Mul,
1842    Div,
1843}
1844
1845impl BinOp {
1846    pub fn name(self) -> &'static str {
1847        match self {
1848            BinOp::Implies => "implies",
1849            BinOp::Or => "||",
1850            BinOp::And => "&&",
1851            BinOp::Eq => "==",
1852            BinOp::NotEq => "!=",
1853            BinOp::Lt => "<",
1854            BinOp::LtEq => "<=",
1855            BinOp::Gt => ">",
1856            BinOp::GtEq => ">=",
1857            BinOp::Add => "+",
1858            BinOp::Sub => "-",
1859            BinOp::Mul => "*",
1860            BinOp::Div => "/",
1861        }
1862    }
1863}
1864
1865#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1866pub enum UnaryOp {
1867    Neg,
1868    Not,
1869}
1870
1871impl UnaryOp {
1872    pub fn name(self) -> &'static str {
1873        match self {
1874            UnaryOp::Neg => "-",
1875            UnaryOp::Not => "!",
1876        }
1877    }
1878}