Syntax & grammar
The annotated grammar reference: every Bynk construct, with its production, what it means, the diagnostics that govern it, and an example. The verbatim machine grammar — every production in one block — is the complete grammar appendix.
Notation & conventions
Section titled “Notation & conventions”Productions are written in EBNF:
"x" a literal token · /x/ a regular expression · ( … )? optional ·
( … )* zero or more · ( … )+ one or more · a | b choice · ε empty.
- Nonterminals are the unquoted names; each is defined by its own entry on this page. Names are the display names of the grammar rules: a leading underscore (an internal helper rule) is dropped and trivial wrappers are collapsed, so productions read as language rather than parser internals. The raw rules and the byte-exact grammar live in the appendix.
- Every production on this page is generated from the
tree-sitter-bynkgrammar, so it cannot drift from that grammar; a cross-parser conformance test keeps that grammar in agreement with the compiler’s own parser. - A production says what parses. A Static semantics block lists the
bynk.*diagnostics that constrain a construct beyond parsing; each links by code to the diagnostic index. A construct with no such diagnostics says so.
Lexical grammar
Section titled “Lexical grammar”The terminals: identifiers, literals, comments, and the trivia ignored between tokens.
identifier
Section titled “identifier”identifier ::= /[A-Za-z][A-Za-z0-9_]*/A name: a letter followed by letters, digits, or underscores. Used for declarations, parameters, fields, and bindings.
Static semantics. {{#grammar-semantics identifier}}
constant_name
Section titled “constant_name”constant_name ::= /[A-Z][A-Za-z0-9_]*/An upper-case-initial name, used for sum-type variants and enum constants.
number_literal
Section titled “number_literal”number_literal ::= /[0-9]+(_[0-9]+)*/A non-negative integer literal.
Static semantics. {{#grammar-semantics number_literal}}
float_literal
Section titled “float_literal”float_literal ::= /[0-9]+(_[0-9]+)*\.[0-9]+(_[0-9]+)*([eE][+-]?[0-9]+(_[0-9]+)*)?|[0-9]+(_[0-9]+)*[eE][+-]?[0-9]+(_[0-9]+)*/A Float literal: a fraction with a digit required on both sides of the .
(1.0, 0.5 — 1. and .5 are rejected), an exponent (1e10, 1.5e-3),
or both. A literal that does not fit a finite 64-bit float (1e999) is
rejected at lex time.
Static semantics. {{#grammar-semantics float_literal}}
string_literal
Section titled “string_literal”string_literal ::= """ (/[^"\\\n]/ | /\\[nt"\\]/ | string_interpolation)* """A double-quoted string. The escapes \n, \t, \", and \\ are recognised.
A string may also contain \(expr) interpolation holes (v0.43); see
string_interpolation.
Static semantics. {{#grammar-semantics string_literal}}
string_interpolation
Section titled “string_interpolation”string_interpolation ::= "\(" expression ")"An interpolation hole \(expr) inside a string literal (v0.43). The body is an
ordinary expression; the hole’s parentheses balance, so \(f(x)) takes f(x).
A bare \( was previously an invalid escape, so this is backward-compatible
(\\( is an escaped backslash followed by a literal (). The hole-typing rule
is in §5.2 well-typedness.
boolean_literal
Section titled “boolean_literal”boolean_literal ::= "true" | "false"The two Bool values, true and false.
unit_literal
Section titled “unit_literal”unit_literal ::= "(" ")"The unit value () — the single value of the unit type.
line_comment
Section titled “line_comment”line_comment ::= "--" /[^\n]*/A comment from -- to end of line. Bynk uses --, never //. Comments are
trivia: ignored between tokens. A -- opens a comment only at the start of input
or when preceded by whitespace — adjacent to a token, a--b is a - -b
(subtraction), not a comment (spec §3.3.1).
A --- … --- doc-block is an external token attached to the following
declaration; its markers are lines of three or more hyphens, and there is no
standalone --- divider (an unclosed marker is bynk.lex.unclosed_doc_block). A
doc-block, whitespace, and line comments are the trivia ignored between tokens
(see the appendix’s Tokens & trivia).
See also. Keywords.
Top-level & modules
Section titled “Top-level & modules”A source file is a commons (pure, shareable code) or a context (an isolated
bounded context), or test declarations. The helper fragment rules are entry
points the tooling uses to parse incomplete input; they are not written
directly.
source_file
Section titled “source_file”source_file ::= (commons_decl | context_decl | adapter_decl | suite_decl)+ | item_fragment+ | expr_fragmentA whole file: one or more top-level declarations, or a fragment (used by editor tooling).
Example.
commons shop { type Status = | Pending | Shipped(tracking: String) | Cancelled(reason: String)
fn describe(s: Status) -> String { match s { Pending => "awaiting shipment" Shipped(tracking: t) => t Cancelled(reason: r) => r } }}See also. How a Bynk program is shaped · Lay out a project.
item_fragment
Section titled “item_fragment”item_fragment ::= context_body_item | handler | store_field | key_declA tooling entry point: a single body item parsed in isolation. Not written by hand.
expr_fragment
Section titled “expr_fragment”expr_fragment ::= statement+ expression? | expressionA tooling entry point: statements and/or an expression parsed in isolation. Not written by hand.
commons_decl
Section titled “commons_decl”commons_decl ::= "commons" qualified_name ("{" commons_body_item* "}" | commons_body_item*)A commons module: pure, dependency-free declarations (types, functions,
capabilities) shareable across contexts. Body braces are optional at file scope.
context_decl
Section titled “context_decl”context_decl ::= "context" qualified_name ("{" context_body_item* "}" | context_body_item*)A context: a bounded context with its own services, agents, and provided
capabilities, isolated behind its boundary.
Example.
context reaper
service sweeper from cron { on schedule("*/5 * * * *") (at: Int) -> Effect[Result[(), String]] { Ok(()) }}Static semantics. {{#grammar-semantics context_decl}}
See also. How a Bynk program is shaped.
adapter_decl
Section titled “adapter_decl”adapter_decl ::= "adapter" qualified_name ("{" adapter_body_item* "}" | adapter_body_item*)An adapter: the host boundary. It co-locates a capability contract with a
non-Bynk binding, declaring capabilities, boundary types, inline pure helpers,
and external (bodiless) providers. The only place host code may enter a program.
Example.
adapter tokens { binding "./tokens.binding.ts" requires { "jose": "^5" } exports capability { Jwt } exports transparent { Claims } type Claims = { sub: String, exp: Int } capability Jwt { fn sign(claims: Claims, secret: String) -> Effect[String] } provides Jwt = JoseJwt}Static semantics. {{#grammar-semantics adapter_decl}}
See also. Adapters · Wrap a library as an adapter.
suite_decl
Section titled “suite_decl”suite_decl ::= "suite" qualified_name ("as" ("unit" | "integration" | "system"))? ("{" test_body_item* "}" | test_body_item*)A suite block targeting a commons or context, holding its cases and
stub clauses. An optional as <tier> classifier — unit, integration, or
system — records the test level (the tier words are contextual, not keywords).
Static semantics. {{#grammar-semantics suite_decl}}
See also. Testing · Write tests and stub collaborators.
commons_body_item
Section titled “commons_body_item”commons_body_item ::= uses_decl | type_decl | fn_decl | messages_decl | capability_decl | provider_decl | service_decl | agent_decl | actor_decl | event_declThe declarations allowed in a commons (no consumes or exports).
context_body_item
Section titled “context_body_item”context_body_item ::= uses_decl | consumes_decl | exports_decl | type_decl | fn_decl | capability_decl | provider_decl | service_decl | agent_decl | actor_decl | messages_decl | event_declThe declarations allowed in a context, including consumes and exports.
adapter_body_item
Section titled “adapter_body_item”adapter_body_item ::= binding_decl | uses_decl | consumes_decl | exports_decl | type_decl | fn_decl | capability_decl | provider_decl | service_decl | agent_decl | actor_decl | messages_decl | event_declThe declarations allowed in an adapter: a binding clause, capabilities,
boundary types, inline pure helpers and uses, external providers, and
exports (no consumes).
test_body_item
Section titled “test_body_item”test_body_item ::= uses_decl | consumes_decl | stub_clause | case | property_declThe declarations allowed in a suite block, including stub clauses and
cases.
qualified_name
Section titled “qualified_name”qualified_name ::= identifier ("." identifier)*A dotted name, e.g. shop.orders — used to name modules and reference them.
uses_decl
Section titled “uses_decl”uses_decl ::= "uses" qualified_nameImports a commons so its public names are in scope.
Static semantics. {{#grammar-semantics uses_decl}}
See also. Define sum, record, and opaque types.
consumes_decl
Section titled “consumes_decl”consumes_decl ::= "consumes" qualified_name ("as" identifier | "{" (identifier ("," identifier)*)? ","? "}")?Declares that a context depends on another context’s (or adapter’s) services or
capabilities — whole and qualified, aliased (as), or with selected
capabilities flattened to bare names ({ Cap, … }).
Static semantics. {{#grammar-semantics consumes_decl}}
See also. Consume another context’s services.
binding_decl
Section titled “binding_decl”binding_decl ::= "binding" string_literal ("requires" "{" (binding_requirement ("," binding_requirement)*)? ","? "}")?Names an adapter’s TypeScript binding module (resolved relative to the adapter’s source file) and, optionally, the npm dependencies it requires. Pinned version ranges only.
Static semantics. {{#grammar-semantics binding_decl}}
See also. Adapters.
binding_requirement
Section titled “binding_requirement”binding_requirement ::= string_literal ":" string_literalOne "package": "range" entry in a binding’s requires { … } map; folded into
the generated package.json.
exports_decl
Section titled “exports_decl”exports_decl ::= "exports" ("opaque" | "transparent" | "capability") "{" (identifier ("," identifier)*)? ","? "}"Controls a context’s boundary: which types are exported opaquely or transparently, and which capabilities are exported.
Static semantics. {{#grammar-semantics exports_decl}}
See also. Share a capability across contexts.
Types & refinements
Section titled “Types & refinements”Type declarations and the type references that appear in signatures.
type_decl
Section titled “type_decl”type_decl ::= "type" identifier ("[" identifier ("," identifier)* "]")? "=" type_bodyNames a type as a record, sum, enum, opaque, or refined type.
Example.
type Status = | Pending | Shipped(tracking: String) | Cancelled(reason: String)Static semantics. {{#grammar-semantics type_decl}}
See also. Type system · Define sum, record, and opaque types · The type-system philosophy.
type_body
Section titled “type_body”type_body ::= opaque_type | refined_type | record_type | sum_type | enum_typeThe right-hand side of a type declaration: one of the five type forms.
opaque_type
Section titled “opaque_type”opaque_type ::= "opaque" base_type ("where" refinement)?A type whose representation is hidden outside its defining module; constructed and inspected only through its API.
See also. Define sum, record, and opaque types.
refined_type
Section titled “refined_type”refined_type ::= base_type ("where" refinement)?A base or named type narrowed by a where refinement, e.g. Int where Positive.
Example.
type Quantity = Int where InRange(1, 100)Static semantics. {{#grammar-semantics refined_type}}
See also. Refined-type API · Define and validate untrusted input.
record_type
Section titled “record_type”record_type ::= "{" (record_field ("," record_field)*)? ","? "}"A product type: named fields, each with a type and optional refinement and default.
Static semantics. {{#grammar-semantics record_type}}
record_field
Section titled “record_field”record_field ::= identifier ":" type_ref ("where" refinement)? ("=" expression)?One field of a record: a name, a type, an optional inline refinement, and an optional default value.
Static semantics. {{#grammar-semantics record_field}}
sum_type
Section titled “sum_type”sum_type ::= sum_variant+ ("embeds" type_ref "as" constant_name ("," type_ref "as" constant_name)*)?A tagged union of variants, each optionally carrying a payload.
Static semantics. {{#grammar-semantics sum_type}}
See also. Type system.
sum_variant
Section titled “sum_variant”sum_variant ::= "|" constant_name ("(" (variant_payload_field ("," variant_payload_field)*)? ","? ")")?One variant of a sum type: a constant name with an optional payload.
variant_payload_field
Section titled “variant_payload_field”variant_payload_field ::= identifier ":" type_refA named field in a sum-variant payload.
enum_type
Section titled “enum_type”enum_type ::= "enum" "{" (constant_name ("," constant_name)*)? ","? "}"A sum type whose variants all have no payload.
refinement
Section titled “refinement”refinement ::= refinement_pred ("&&" refinement_pred)*One or more predicates joined by &&, narrowing a type to the values that
satisfy them. This is the closed type-refinement catalogue — a fixed set of
built-in predicates (predicate_name), not user
expressions.
The three
where/ predicate tiers.where(and the other predicate positions) look uniform but come in three tiers with different vocabularies:
- Type refinement —
type T = Base where <catalogue>(refined_type, this rule). A closed grammar: the built-inpredicate_names joined by&&. No user expressions.- Actor claim —
actor A = Base where <predicate>(actor_decl). A full expression that a static-semantics rule restricts to the closed actor-claim catalogue (hasClaim/claimEqualscomposed with&&/||/!).- Boolean contracts & tests — a function
requires/ensures, an agentinvariant/transition, a testexpect(ADR 0144’s “one predicate surface”). The open tier: any pureBoolexpression overis/implies/ the operators / pure value methods.Tier 1 is closed in the grammar; tier 2 is closed by static semantics over a parsed expression; tier 3 is open. ADR 0144’s “one predicate surface” names tier 3 — the two closed catalogues (1 and 2) are deliberately narrower.
Static semantics. {{#grammar-semantics refinement}}
See also. The refined-literal admission model.
refinement_pred
Section titled “refinement_pred”refinement_pred ::= pred_call | predicate_nameA single refinement predicate: a predicate call or a bare predicate.
pred_call
Section titled “pred_call”pred_call ::= predicate_name "(" (pred_arg ("," pred_arg)*)? ")"A predicate with arguments, e.g. InRange(1, 100) or Matches("…").
predicate_name
Section titled “predicate_name”predicate_name ::= "Matches" | "InRange" | "MinLength" | "MaxLength" | "Length" | "NonNegative" | "Positive" | "NonEmpty"The built-in refinement predicates.
Static semantics. {{#grammar-semantics predicate_name}}
pred_arg
Section titled “pred_arg”pred_arg ::= "-"? number_literal | "-"? float_literal | string_literalAn argument to a predicate: a number or string literal.
base_type
Section titled “base_type”base_type ::= "Int" | "String" | "Bool" | "Float" | "Duration" | "Instant" | "Bytes"The primitive types Int, String, Bool, Float, and Duration. Duration
(v0.86, ADR 0112) is a span of time in milliseconds, written with a literal
<int>.<unit> (5.minutes, 30.days); its closed unit set is milliseconds,
seconds, minutes, hours, days.
Static semantics. {{#grammar-semantics base_type}}
type_ref
Section titled “type_ref”type_ref ::= function_type_ref | base_type | unit_type | validation_error_type | generic_type_ref | applied_type_ref | identifierA type as it appears in a signature: a base type, a unit, a validation-error type, a generic application, or a named type.
unit_type
Section titled “unit_type”unit_type ::= "(" ")"The unit type ().
validation_error_type
Section titled “validation_error_type”validation_error_type ::= "ValidationError"ValidationError — the error produced when refined-type validation fails.
generic_type_ref
Section titled “generic_type_ref”generic_type_ref ::= ("Option" | "Effect" | "HttpResult" | "List" | "Stream" | "Query" | "Connection" | "History") "[" type_ref "]" | ("Result" | "Map") "[" type_ref "," type_ref "]"A generic type applied to arguments: Result[T, E], Option[T], Effect[T],
HttpResult[T], or Stream[T].
Static semantics. {{#grammar-semantics generic_type_ref}}
See also. Work with Result and optional values.
applied_type_ref
Section titled “applied_type_ref”applied_type_ref ::= identifier "[" type_ref ("," type_ref)* "]"An application of a user-declared generic type (v0.157) to bracketed type
arguments: Paginated[User], Keyed[String, Int]. The head is a user type
name declared type Name[T, …] = { … }; the built-in generics have their own
generic_type_ref. A generic type must be applied to
exactly its declared number of arguments, and a generic record is a
non-boundary value — it cannot appear in a record field, sum payload,
handler signature, agent state, or JSON codec target.
Static semantics. {{#grammar-semantics applied_type_ref}}
function_type_ref
Section titled “function_type_ref”function_type_ref ::= (base_type | unit_type | validation_error_type | generic_type_ref | applied_type_ref | identifier | "(" type_ref ("," type_ref)* ","? ")") "->" type_refA function type (v0.20a): Int -> Int, (Int, String) -> Bool, () -> Int.
The arrow is right-associative (A -> B -> C is A -> (B -> C)), and a
function type is effectful exactly when its return type is Effect[_] — the
same structural rule that classifies function declarations. Function types are
confined to non-boundary positions: fn/lambda parameters, returns, and
locals; they are rejected in record fields, sum payloads, handler and
capability signatures, agent state, and anything else that would serialise or
cross a boundary.
Static semantics. {{#grammar-semantics function_type_ref}}
Functions, capabilities & providers
Section titled “Functions, capabilities & providers”Pure functions and methods, the capability interfaces an effectful program depends on, and the providers that implement them.
fn_decl
Section titled “fn_decl”fn_decl ::= "fn" (method_name | identifier) ("[" identifier ("," identifier)* "]")? "(" params? ")" "->" type_ref requires_clause* ensures_clause* blockA function or method: a name, parameters, a return type, and a block body.
Example.
commons demo { type Id = Int
fn add(a: Int, b: Int) -> Int { a + b }}Static semantics. {{#grammar-semantics fn_decl}}
See also. Operators & built-ins.
requires_clause
Section titled “requires_clause”requires_clause ::= "requires" identifier ":" expressionA function precondition (v0.115): requires <name>: <predicate>, between the
return type and the body. A pure Bool predicate over the parameters — the one
predicate surface. Checked at every call in the dev/test build and used to filter
the runner’s generated arguments; result is not in scope
(bynk.contract.result_in_requires).
See also. Contracts.
ensures_clause
Section titled “ensures_clause”ensures_clause ::= "ensures" identifier ":" expressionA function postcondition (v0.115): ensures <name>: <predicate>. A pure
Bool predicate over the parameters and result (the return value; the
awaited element for an Effect). Checked at every call in the dev/test build and
generated against by the runner (a contract is a property that is always on). A
case/property that merely restates it is flagged
(bynk.contract.restated_by_test).
See also. Contracts.
method_name
Section titled “method_name”method_name ::= identifier "." identifierA method name, Type.method, defining a method on a named type.
params
Section titled “params”params ::= (self_param | param) ("," param)* ","?A parameter list: an optional self parameter followed by named parameters.
self_param
Section titled “self_param”self_param ::= "self"The self receiver of a method or handler.
param ::= identifier ":" type_refOne parameter: a name and a type.
Static semantics. {{#grammar-semantics param}}
capability_decl
Section titled “capability_decl”capability_decl ::= "capability" identifier "{" capability_op* "}"A capability: an interface of effectful operations a context can depend on.
Example.
context demo
capability Logger { fn info(message: String) -> Effect[()] }capability Greeter { fn greet() -> Effect[()] }
provides Logger = ConsoleLogger { fn info(message: String) -> Effect[()] { Effect.pure(()) }}
provides Greeter = PoliteGreeter given Logger { fn greet() -> Effect[()] { let _ <- Logger.info("hello") Effect.pure(()) }}Static semantics. {{#grammar-semantics capability_decl}}
See also. Capabilities & providers.
capability_op
Section titled “capability_op”capability_op ::= "fn" identifier ("[" identifier ("," identifier)* "]")? "(" (param ("," param)*)? ","? ")" "->" type_refOne operation in a capability: a name, parameters, and a return type (no body).
Static semantics. {{#grammar-semantics capability_op}}
messages_decl
Section titled “messages_decl”messages_decl ::= "messages" string_literal store_annotation* "{" message_entry* "}"A message bundle for one locale (message-bundles track, slice 1): messages <tag> @reference { "code" => "template" ... }. tag is a plain identifier —
its LocaleTag refinement is a checker concern, not a grammar one.
Annotations reuse the same @name(args) shape as a store field’s
(store_annotation); the parser stays permissive on their count (@reference
appears zero or more times syntactically) — exactly one per bundle is a
checker rule (bynk.messages.missing_reference / multiple_reference), not a
parse error. Commons-only placement is likewise checked, not parsed: this rule
is syntactically admitted inside a context or adapter body too, the same
way service_decl/agent_decl are admitted inside an adapter — the checker
reports bynk.messages.outside_commons precisely instead.
message_entry
Section titled “message_entry”message_entry ::= string_literal "=>" string_literal ","?One "code" => "template" entry inside a messages block. Both sides are
plain string literals — a template’s {name} placeholders are resolved by a
compile-time string scan during lowering, not parsed as expressions.
event_decl
Section titled “event_decl”event_decl ::= "event" identifier store_annotation* "=" record_typeevent Name = { fields } (Events track, spine #936) — a typed fact a context
may emit and other contexts’ subscriber services may receive, via the
Events capability (Events.emit[Name](...)) and from Events(Name).
Record body only. Context-only placement is a checker concern
(bynk.event.outside_context), not a grammar one — this rule is
syntactically permitted in a commons/adapter body too, the same way
service_decl/agent_decl are. An optional @schema(N) annotation (slice
3b) reuses store_annotation; N’s legality (a positive Int literal,
@schema alone, at most once) is a checker concern, not a grammar one.
See also. Understand events.
provider_decl
Section titled “provider_decl”provider_decl ::= "provides" identifier "=" identifier given_clause? ("{" provider_op* "}")?A provides block implementing a capability, optionally given other
capabilities it depends on.
Static semantics. {{#grammar-semantics provider_decl}}
See also. Compose a provider from other capabilities.
provider_op
Section titled “provider_op”provider_op ::= "fn" identifier "(" (param ("," param)*)? ","? ")" "->" type_ref blockOne operation implementation in a provider: a capability operation with a body.
Static semantics. {{#grammar-semantics provider_op}}
given_clause
Section titled “given_clause”given_clause ::= "given" qualified_name ("," qualified_name)*Declares the capabilities a handler or provider may use.
Static semantics. {{#grammar-semantics given_clause}}
Actors (v0.45)
Section titled “Actors (v0.45)”An actor is a nominal boundary contract — a closed, compiler-known
authentication scheme plus an optional sealed identity. A handler consumes one
on its by clause; the boundary verifies the scheme and mints the identity
before the body runs.
actor_decl
Section titled “actor_decl”actor_decl ::= "actor" identifier ("{" "auth" "=" scheme scheme_config? ("," "identity" "=" type_ref)? "}" | "=" identifier "where" expression)A boundary contract: actor Name { auth = <Scheme> }, optionally
, identity = <Type> (a context-ownable, sealed identity type). The refinement
form actor Admin = Base where <predicate> narrows a base actor by an
authorisation claim — e.g. actor Admin = User where hasClaim("admin"). The
predicate is a full expression (like a function contract), restricted by a
static-semantics rule to the closed actor-claim catalogue — hasClaim(…) /
claimEquals(…, …) composed with && / || / ! — over a Bearer base
(bynk.actor.refinement_predicate_unsupported / …refinement_base_unsupported).
This is one of the three where predicate tiers (see
refinement). Actors are context-only.
scheme
Section titled “scheme”scheme ::= "None" | "Internal" | "Bearer" | "Signature" | "Oidc"The closed authentication-scheme set: None (anonymous; identity ()),
Internal (in-system/platform trust), Bearer (a JWT in Authorization, v0.47),
and Signature (an HMAC over the request body, for webhooks, v0.51). The
authenticated schemes carry a scheme_config.
scheme_config
Section titled “scheme_config”scheme_config ::= "(" scheme_arg ("," scheme_arg)* ")"The keyed-args config an authenticated scheme carries — Bearer(secret = "<ENV>")
or Signature(secret = "<ENV>", header = "<Header>", (timestamp = "<Header>", tolerance = <seconds>)?). The checker validates which keys each scheme admits.
scheme_arg
Section titled “scheme_arg”scheme_arg ::= identifier "=" (string_literal | number_literal)One key = value pair in a scheme_config; the value is a
string or integer literal (e.g. an integer tolerance in seconds).
by_clause
Section titled “by_clause”by_clause ::= "by" (identifier ":")? identifier ("|" identifier)*by <binder>: <Actor> on a handler, after the return type and alongside given
(v0.155 relocated it from before the parameter list). The verified actor binds to
<binder>; its identity is
<binder>.identity. The binder is optional (v0.50): by <Actor> verifies the
contract fail-closed but captures no identity (anonymous / verify-and-discard) —
the canonical form for an identity-less scheme like Signature (by Webhook (body: T)). Omitting by entirely inherits the protocol’s default actor — except
on HTTP, where by is required (bynk.actor.missing_by_on_http).
Services & handlers
Section titled “Services & handlers”A service groups the handlers that respond to calls and external triggers.
service_decl
Section titled “service_decl”service_decl ::= "service" identifier service_protocol? by_clause? given_clause? "{" cors_policy? security_policy? limits_policy? (store_annotation* handler)* "}"A service: a named group of handlers inside a context. The header may carry
service-level by/given defaults (v0.155), after the protocol clause — the
ambient contract every handler inherits unless it declares its own. A handler’s
own by (or given) overrides the default outright; it does not merge.
Example. The Visitor default is stated once; the second route overrides it.
context notes
service api from http by Visitor { on GET("/ping") () -> Effect[HttpResult[String]] { Ok("pong") }
on GET("/notes/:id") (id: String) -> Effect[HttpResult[String]] { NotFound }}Static semantics. {{#grammar-semantics service_decl}}
service_protocol
Section titled “service_protocol”service_protocol ::= "from" ("http" | "cron" | "queue" "(" string_literal ")" | "websocket" "(" "in" ":" type_ref "," "out" ":" type_ref ","? ")" | "Events" "(" type_ref event_pattern? ")" schema_dispatch_clause?)The from <protocol> header clause (v0.44): from http, from cron,
from queue("<name>"), from websocket(in: I, out: O) (v0.103, binding the
inbound/outbound frame types), or from Events(E) (Events track, spine #936),
optionally filtered by a structural event_pattern
(slice 1) and/or a schema_dispatch_clause
(slice 4). Absent ⇒ a contract-mediated, on call-only service.
event_pattern
Section titled “event_pattern”event_pattern ::= "{" (event_pattern_field ",")* ".." "}"The structural delivery filter on a from Events(E { field: value, .. })
subscription header (Events track slice 1, spine #936): every emission of E
still reaches the fan-out mechanism (deliver-and-filter), and the
subscriber’s own generated handler evaluates this as a runtime guard before
running its body — the handler’s parameter is not narrowed to the matched
values. A trailing .. is required whenever any field is listed;
from Events(E) (no braces) is the unfiltered form.
event_pattern_field
Section titled “event_pattern_field”event_pattern_field ::= identifier ":" event_pattern_valueOne name: value entry in an event_pattern.
event_pattern_value
Section titled “event_pattern_value”event_pattern_value ::= "-"? number_literal | string_literal | boolean_literal | (identifier ".")? identifierThe value a pattern field is matched against: an Int/String/Bool
literal, or a nullary sum-type variant reference, bare (Domestic) or
qualified (Region.Domestic). A variant that carries a payload is rejected
by the checker — no nested sub-patterns in this slice.
schema_dispatch_clause
Section titled “schema_dispatch_clause”schema_dispatch_clause ::= "via" "schema" "(" "-"? number_literal ")"via schema(N) on a from Events(...) subscription header (Events track
slice 4, spine #936), after the closing ) — dispatch by the envelope’s
schemaVersion rather than the payload, parallel to
event_pattern but independent of it (a service may
carry either, both, or neither). Nested inside the Events protocol arm, so
via on http/cron/queue/websocket is a syntax error, not a checker
diagnostic. Delivery is deliver-and-filter, unchanged: every emission still
reaches every subscriber of the event type, and this is one more
independently-evaluated guard inside the subscriber’s own generated
handler — sibling subscribers with the same or overlapping N are not
diagnosed as ambiguous. N’s legality (a positive Int literal) is a
checker concern (bynk.event.bad_schema_dispatch), not a grammar one.
Literal only in this slice; a range pattern (via schema(2..)) is a future
slice’s unbuilt grammar.
See also. Understand events.
cors_policy
Section titled “cors_policy”cors_policy ::= "cors" "{" (cors_field ","?)* "}"The optional cross-origin (CORS) policy of a from http service (v0.131), in
header position before the handlers. cors is a contextual keyword (like
store/key), so it stays an ordinary identifier elsewhere.
Example.
context api
service api from http { cors { origins: ["https://app.example.com"], credentials: false, maxAge: 1.hours, }
on GET("/ping") () -> Effect[HttpResult[String]] by v: Visitor { Ok("pong") }}The checker validates the closed field set (origins/headers/credentials/
maxAge) and rejects credentials: true with the wildcard origin ["*"]
(§5.7.1). Access-Control-Allow-Methods is
derived from the routes, not declared.
cors_field
Section titled “cors_field”cors_field ::= identifier ":" expressionOne name: value field inside a cors { } policy.
security_policy
Section titled “security_policy”security_policy ::= "security" "{" (security_field ","?)* "}"The optional security-headers policy of a from http service (v0.141), in header
position beside cors. security is a contextual keyword, so it stays an
ordinary identifier elsewhere.
Example.
context api
service api from http { security { hsts: 180.days, nosniff: true, }
on GET("/ping") () -> Effect[HttpResult[String]] by v: Visitor { Ok("pong") }}X-Content-Type-Options: nosniff is stamped by default on every response;
hsts opts in to Strict-Transport-Security. The checker validates the closed
field set (hsts/nosniff) and requires the section be on a from http service
(§5.7.3).
security_field
Section titled “security_field”security_field ::= identifier ":" expressionOne name: value field inside a security { } policy.
limits_policy
Section titled “limits_policy”limits_policy ::= "limits" "{" (limits_field ","?)* "}"The optional request-limits policy of a from http service (v0.142), in header
position beside cors and security. limits is a contextual keyword, so it
stays an ordinary identifier elsewhere.
Example.
context api
service api from http { limits { maxBody: 1_048_576, }
on GET("/ping") () -> Effect[HttpResult[String]] by v: Visitor { Ok("pong") }}The checker validates the closed field set and requires the section be on a
from http service.
limits_field
Section titled “limits_field”limits_field ::= identifier ":" expressionOne name: value field inside a limits { } policy.
handler
Section titled “handler”handler ::= call_handler | http_handler | cron_handler | queue_handler | ws_open_handler | ws_close_handler | event_handlerA handler: a call, HTTP, cron, queue, WebSocket (on open/on close, with
on message shared with the queue form), or Events (on event) entry point.
Static semantics. {{#grammar-semantics handler}}
call_handler
Section titled “call_handler”call_handler ::= "on" "call" identifier? "(" (param ("," param)*)? ","? ")" "->" type_ref by_clause? given_clause? blockon call — an in-process entry point, optionally named, callable across
contexts.
http_handler
Section titled “http_handler”http_handler ::= "on" http_method "(" string_literal ")" "(" (param ("," param)*)? ","? ")" "->" type_ref by_clause? given_clause? blockfrom http — an HTTP route handler returning Effect[HttpResult[T]].
Static semantics. {{#grammar-semantics http_handler}}
See also. HTTP · Handle an HTTP request.
http_method
Section titled “http_method”http_method ::= "GET" | "POST" | "PUT" | "PATCH" | "DELETE"The HTTP methods a route may handle.
Static semantics. {{#grammar-semantics http_method}}
cron_handler
Section titled “cron_handler”cron_handler ::= "on" "schedule" "(" string_literal ")" "(" (param ("," param)*)? ","? ")" "->" type_ref by_clause? given_clause? blockfrom cron — a scheduled handler returning Effect[Result[(), E]].
Static semantics. {{#grammar-semantics cron_handler}}
See also. Cron · Run a task on a schedule.
queue_handler
Section titled “queue_handler”queue_handler ::= "on" "message" "(" (param ("," param)*)? ","? ")" "->" type_ref by_clause? given_clause? blockfrom queue — a queue-message handler returning Effect[Result[(), E]].
Static semantics. {{#grammar-semantics queue_handler}}
See also. Queue · Process a queued message.
ws_open_handler
Section titled “ws_open_handler”ws_open_handler ::= "on" "open" "(" (param ("," param)*)? ","? ")" "->" type_ref by_clause? given_clause? blockfrom websocket — the upgrade handshake (v0.103). Exactly one per service; it
names its actor with by and receives an owned connection: Connection[out] it
must dispose. The inbound-frame handler reuses the on message (queue) form.
ws_close_handler
Section titled “ws_close_handler”ws_close_handler ::= "on" "close" "(" (param ("," param)*)? ","? ")" "->" type_ref by_clause? given_clause? blockfrom websocket — fires when the connection ends (v0.106); disposes the stored
connection.
See also. WebSocket · Handle a WebSocket connection.
event_handler
Section titled “event_handler”event_handler ::= "on" "event" "(" (param ("," param)*)? ","? ")" "->" type_ref by_clause? given_clause? blockfrom Events(E) — a subscriber’s one handler (Events track, spine #936),
on event(e: E) -> Effect[()]. Same generic shape as every other handler
form (params/by/given/body all uniform); the checker verifies the
parameter’s type against the header’s event type.
See also. Understand events.
Agents
Section titled “Agents”An agent is a keyed, stateful entity: its state lives in store fields that
handlers read by name and write with :=.
agent_decl
Section titled “agent_decl”agent_decl ::= "agent" identifier "{" key_decl store_field* (invariant_decl | transition_decl)* (store_annotation* call_handler)* "}"An agent: a key, store fields, and handlers that read and write them (writes
commit atomically at handler end).
Example.
context counters
type CounterId = opaque String
agent Counter { key id: CounterId
store count: Cell[Int]
on call current() -> Effect[Int] { count }
on call increment() -> Effect[Int] { let next = count + 1 count := next next }}Static semantics. {{#grammar-semantics agent_decl}}
See also. Agents · Build a stateful agent · The agent model.
key_decl
Section titled “key_decl”key_decl ::= "key" identifier ":" type_refThe agent’s identity: a key field whose value names an instance.
store_field
Section titled “store_field”store_field ::= "store" identifier ":" store_kind store_annotation* ("=" expression)?A store field (storage track): store <name>: <Kind>[…] [@annotations] [= <init>]
— an access-pattern slot of a declared storage kind. store is a contextual
keyword (also a valid identifier elsewhere). It is the agent’s sole state surface
(ADR 0108); the legacy state { } block was removed at the parity slice.
Cell,Map,Set,Cache, andLogare functional. ACell[T](v0.82) reads by bare name (implicit deref) and writes with:=; aMap[K, V](v0.83, ADR 0110) is a storage map with effectful entry methods (put/get/update/upsert/remove/contains/size); aSet[T](v0.84, ADR 0110) is a storage set with effectful membership methods (add/remove/contains/size); aCache[K, V](v0.87, ADR 0113) is aMapwith per-entry TTL expiry, requiring@ttl(<duration>)andgiven Clockon its handlers (eviction reads the clock); aLog[T](v0.95, ADR 0121) is an append-only, time-indexed sequence whoseappendstampsClock.now()(given Clock) and whose reads are lazyQuery[T]time-window builders (since/before/between/recent/reversed), with an optional@retain(<duration>). All write ops are awaited with<-and commit atomically at handler end with the invariant gate (ADR 0109). The storage-kind catalogue is closed at these five — there is noQueuestorage kind: a queue is a delivery concern reached through thefrom queueprotocol, not agent state (ADR 0122).
store_kind
Section titled “store_kind”store_kind ::= identifier ("[" type_ref ("," type_ref)* "]")?A storage kind applied to its element type(s): Cell[Int], Map[K, V]. The head
is the kind name; the checker restricts it to the storage-kind catalogue.
store_annotation
Section titled “store_annotation”store_annotation ::= "@" identifier ("(" (annotation_arg ("," annotation_arg)*)? ","? ")")?A storage-field annotation (v0.85, ADR 0111): @<name> or @<name>(<args>),
between the kind and the initialiser — @ttl(5.minutes), @indexed(by: orderId).
The name is matched against the closed registry (@indexed/@ttl/@retain/
@bounded); an unknown name, a wrong-kind use, or an annotation whose slice has
not landed is a checker diagnostic. v0.85 (slice 3a) lands the grammar and
registry; each annotation becomes functional with its kind’s slice.
annotation_arg
Section titled “annotation_arg”annotation_arg ::= (identifier ":")? expressionOne annotation argument: an optional label: then a value expression — by: id
(labelled, as in @indexed) or 5.minutes (positional, as in @ttl). Arguments
are compile-time metadata, restricted to literals (and the @indexed field-name
labels) by the checker (ADR 0111 D4).
invariant_decl
Section titled “invariant_decl”invariant_decl ::= "invariant" identifier ":" expressionAn agent invariant: invariant <name>: <predicate>. A universally-quantified,
pure Bool predicate over the agent’s store fields, runtime-checked at each
commit boundary. Invariants form a phase between the store fields and the
handlers.
See also. Agent invariants.
transition_decl
Section titled “transition_decl”transition_decl ::= "transition" identifier ":" expressionAn agent step invariant: transition <name>: <predicate over old/new>. A pure
Bool predicate over the old/new committed-state pair, runtime-checked at the
commit boundary (from the second commit onward). Transitions sit in the same phase
as invariants, between the store fields and the handlers.
See also. Step invariants.
Expressions
Section titled “Expressions”Bynk is expression-oriented: a block’s value is its final expression. Operators follow the usual precedence (see Operators & built-ins).
expression
Section titled “expression”expression ::= if_expr | match_expr | is_expr | expect_expr | binary_expr | unary_expr | primaryAny expression: control flow, refinement checks, operators, or a primary.
primary
Section titled “primary”primary ::= lambda_expr | paren_expr | method_call | field_access | call | record_construction | record_spread | question_expr | ok_expr | err_expr | some_expr | none_expr | effect_pure_expr | val_expr | wire_expr | trace_expr | list_literal | block | number_literal | float_literal | string_literal | boolean_literal | unit_literal | self_expr | identifierThe atomic and postfix expressions: literals, names, calls, field and method access, constructors, and parenthesised expressions.
if_expr
Section titled “if_expr”if_expr ::= "if" expression block ("else" (if_expr | block))?A conditional expression; both branches must have the same type.
Static semantics. {{#grammar-semantics if_expr}}
match_expr
Section titled “match_expr”match_expr ::= "match" expression "{" match_arm* "}"Pattern-matches a value against variants; must be exhaustive.
Example.
match s { Pending => "awaiting shipment" Shipped(tracking: t) => t Cancelled(reason: r) => r}Static semantics. {{#grammar-semantics match_expr}}
See also. Pattern-match with match.
is_expr
Section titled “is_expr”is_expr ::= expression "is" patternA refinement/variant check that also narrows the value’s type in the true
branch. A variant pattern’s nested payload patterns are structural tests too:
r is Rejected(RefinementViolation(_)) tests the outer tag and the inner one,
the same tests a match arm applies. A payload bound to a plain name (is Ok(x))
or a wildcard (is Ok(_)) adds no nested test — it only narrows.
Static semantics. {{#grammar-semantics is_expr}}
See also. Narrow and bind with is.
binary_expr
Section titled “binary_expr”binary_expr ::= expression "implies" expression | expression "||" expression | expression "&&" expression | expression ("==" | "!=") expression | expression ("<" | "<=" | ">" | ">=") expression | expression ("+" | "-") expression | expression ("*" | "/") expressionThe binary operators, in precedence order from || to *//.
Static semantics. {{#grammar-semantics binary_expr}}
See also. Operators & built-ins.
unary_expr
Section titled “unary_expr”unary_expr ::= ("!" | "-") expressionLogical negation ! and numeric negation -.
method_call
Section titled “method_call”method_call ::= primary "." identifier ("[" type_ref ("," type_ref)* "]")? "(" (expression ("," expression)*)? ","? ")"Calls a method on a value: receiver.method(args).
Static semantics. {{#grammar-semantics method_call}}
field_access
Section titled “field_access”field_access ::= primary "." identifierReads a field of a record or agent state: value.field.
Static semantics. {{#grammar-semantics field_access}}
call ::= identifier ("[" type_ref ("," type_ref)* "]")? "(" (expression ("," expression)*)? ","? ")"Calls a function or constructs a variant: name(args).
Static semantics. {{#grammar-semantics call}}
record_construction
Section titled “record_construction”record_construction ::= identifier "{" (field_init ("," field_init)*)? ","? "}"Builds a record value: Type { field: value, … }.
Static semantics. {{#grammar-semantics record_construction}}
field_init
Section titled “field_init”field_init ::= identifier ":" expression | identifierOne field in a record construction: name: value, or shorthand name.
record_spread
Section titled “record_spread”record_spread ::= identifier "{" "..." expression ("," field_init)* ","? "}" | "{" "..." expression ("," field_init)* ","? "}"Builds a record from an existing one, overriding some fields: { ...base, field: value }.
Static semantics. {{#grammar-semantics record_spread}}
question_expr
Section titled “question_expr”question_expr ::= expression "?"The ? operator: unwraps a Result, propagating the error on failure.
Static semantics. {{#grammar-semantics question_expr}}
See also. Work with Result and optional values.
ok_expr
Section titled “ok_expr”ok_expr ::= "Ok" "(" expression ")"The Ok constructor of Result (or HttpResult).
Static semantics. {{#grammar-semantics ok_expr}}
err_expr
Section titled “err_expr”err_expr ::= "Err" "(" expression ")"The Err constructor of Result.
Static semantics. {{#grammar-semantics err_expr}}
some_expr
Section titled “some_expr”some_expr ::= "Some" "(" expression ")"The Some constructor of Option.
Static semantics. {{#grammar-semantics some_expr}}
none_expr
Section titled “none_expr”none_expr ::= "None"The None constructor of Option.
Static semantics. {{#grammar-semantics none_expr}}
effect_pure_expr
Section titled “effect_pure_expr”effect_pure_expr ::= "Effect" "." "pure" "(" expression ")"Effect.pure(x) — lifts a pure value into an Effect.
val_expr
Section titled “val_expr”val_expr ::= "Val" "[" type_ref "]" val_arg?Val[T] — fabricates a valid inhabitant of type T (drawn from its refinement
domain), optionally pinned to a specific value with Val[T](v). Valid only in
test bodies. Replaces the retired Mock[T] (v0.114).
Static semantics. {{#grammar-semantics val_expr}}
See also. Write tests.
val_arg
Section titled “val_arg”val_arg ::= "(" expression ("," expression)* ","? ")" | "{" (field_init ("," field_init)*)? ","? "}"The pin arguments to a Val[T]: positional values or a record of field pins.
wire_expr
Section titled “wire_expr”wire_expr ::= "Wire" "(" expression ")"Wire(<String>) — a raw, pre-validation argument to a system-tier service
address (testing-the-boundary). The inner String is the wire form the boundary
receives unvalidated — a body’s JSON text or a path segment — so a case can
drive the router with input the type system forbids and observe the rejection.
Valid only as a service-address argument in a system-tier case; the router
validates it, so no refined value is ever minted from a Wire.
lambda_expr
Section titled “lambda_expr”lambda_expr ::= "(" (lambda_param ("," lambda_param)*)? ")" "=>" (expression | block)A lambda (v0.20a): (o) => o.paid, (acc, t) => acc + t, () => 0, or with a
block body (o) => { … }. Always parenthesised; => is the value arrow
(shared with match arms), -> stays the type arrow. Parameter annotations
are optional where an expected function type supplies them — and required
otherwise. A lambda may close over and call a given capability; its
effectfulness is read off its body (an effect operation makes it effectful,
wrapping the result in Effect).
Static semantics. {{#grammar-semantics lambda_expr}}
lambda_param
Section titled “lambda_param”lambda_param ::= identifier (":" type_ref)?One lambda parameter, with an optional type annotation.
Static semantics. {{#grammar-semantics lambda_param}}
list_literal
Section titled “list_literal”list_literal ::= "[" (expression ("," expression)* ","?)? "]"A List literal (v0.20b): [1, 2, 3], with an optional trailing comma. A
leading [ only — type application (name[T](…)) stays a postfix form on
a callee identifier, and its [ must sit on the same line as the callee.
Elements check against the expected element type when one is supplied; an
empty [] needs an expected type to infer its element type from.
Static semantics. {{#grammar-semantics list_literal}}
paren_expr
Section titled “paren_expr”paren_expr ::= "(" expression ")"A parenthesised expression, for grouping.
self_expr
Section titled “self_expr”self_expr ::= "self"self — the receiver inside a method or agent handler.
Static semantics. {{#grammar-semantics self_expr}}
Patterns & matching
Section titled “Patterns & matching”The patterns used in match arms and is checks.
match_arm
Section titled “match_arm”match_arm ::= (pattern | refined_pattern) ("if" expression)? "=>" expression ","?One arm of a match: a pattern, an optional if guard over the pattern’s
bindings, =>, and a result expression. A guarded arm never satisfies
exhaustiveness (its guard may fail at runtime). refined_pattern (#472) is
admitted only here — a match arm’s top-level pattern — not through pattern
generally, so it is never reachable from is or from a nested payload
position; a match arm’s pattern is always followed by a fixed terminator
(if/=>), unlike an expression position, where refinement’s own
&&-joined predicate list would be ambiguous with the surrounding grammar.
Static semantics. {{#grammar-semantics match_arm}}
See also. Pattern-match with match.
pattern
Section titled “pattern”pattern ::= wildcard_pattern | literal_pattern | variant_pattern | or_pattern | paren_patternA pattern: a wildcard, a literal, a binding, a variant pattern, an
or-pattern, or a parenthesized pattern. A lowercase-led identifier is a
binding (it matches anything and binds the value); an uppercase-led one is a
nullary variant — in the concrete grammar both parse as variant_pattern.
variant_pattern
Section titled “variant_pattern”variant_pattern ::= (identifier ".")? identifier ("(" (pattern_binding ("," pattern_binding)*)? ","? ")")?Matches a sum-type variant, optionally binding its payload fields.
Static semantics. {{#grammar-semantics variant_pattern}}
wildcard_pattern
Section titled “wildcard_pattern”wildcard_pattern ::= "_"_ — matches anything, binding nothing.
literal_pattern
Section titled “literal_pattern”literal_pattern ::= "-"? number_literal | string_literal | boolean_literalMatches a primitive scrutinee (Int/String/Bool, or a refinement over one)
by value equality — an integer (optionally negated), a string, or a boolean.
A literal-pattern match needs a wildcard _ arm to be exhaustive, except over
Bool, which is complete once both true and false appear.
or_pattern
Section titled “or_pattern”or_pattern ::= pattern "|" patternp₁ | p₂ — matches if either alternative matches, left-associative
(p₁ | p₂ | p₃ is (p₁ | p₂) | p₃). Every alternative must bind the same
set of names, a name shared across alternatives must have the same type
(including refinement) in each, and every alternative must match the same
value type. | is a pattern-position operator only, distinct from boolean
||.
Static semantics. {{#grammar-semantics or_pattern}}
See also. Pattern-match with match.
paren_pattern
Section titled “paren_pattern”paren_pattern ::= "(" pattern ")"Parentheses around a pattern — transparent grouping, most useful around an
or-pattern for readability (is (Held(...) | Confirmed(...))). Optional:
is already parses one whole pattern, |-chain included, without them.
Never admits a refined_pattern inside (#472) — see below.
refined_pattern
Section titled “refined_pattern”refined_pattern ::= wildcard_pattern "where" refinementp 'where' predicate (#472) — a runtime guard on a pattern, reusing the
closed refinement-predicate catalogue a type X = Base where P declaration
uses (refinement). v1 admits only _ where predicate —
the compiler rejects any other inner form (bynk.parse.refined_pattern_inner).
Admitted only against a literal-kind scrutinee (Int/String); a guard, not
a narrowing — matching a refined arm does not change any static type. Like an
if guard, a refined arm alone is never exhaustive, and it is match-only —
one on the right of is is rejected (bynk.types.is_refined_pattern).
Composes with an or-pattern only as the whole thing’s outer wrapper —
(p₁ | p₂) where predicate refines the alternation as a unit — never as one
alternative among others (match_arm’s pattern field is a single
choice($._pattern, $.refined_pattern), not a repetition, so a refined
pattern can never appear as one |-separated alternative alongside others).
See also. Pattern-match with match.
pattern_binding
Section titled “pattern_binding”pattern_binding ::= named_binding | patternA binding within a variant pattern: a named binding, or a full sub-pattern
matched against the payload field. A lowercase-led identifier binds the field, an
uppercase-led one discriminates a nested nullary variant (Err(PollClosed)), _
ignores it, and a nested variant recurses (Some(Ok(x))).
named_binding
Section titled “named_binding”named_binding ::= identifier ":" patternBinds a payload field by name, matching it against a sub-pattern: field: name
(or field: _ to ignore).
Statements
Section titled “Statements”A block is a sequence of statements ending in an optional value expression.
block ::= "{" statement* expression? "}"A braced sequence of statements with an optional trailing expression, which is the block’s value.
statement
Section titled “statement”statement ::= let_stmt | effect_let_stmt | effect_send_stmt | do_stmt | assign_stmt | expect_exprA statement: a let, an effectful let, a := store write, an async send, or
an assertion.
let_stmt
Section titled “let_stmt”let_stmt ::= "let" binding_name (":" type_ref)? "=" expressionBinds a pure value: let name = expr.
Static semantics. {{#grammar-semantics let_stmt}}
effect_let_stmt
Section titled “effect_let_stmt”effect_let_stmt ::= "let" binding_name (":" type_ref)? "<-" expression call_site_actor?Binds the result of an effect: let name <- effect. In a test case body it may
carry a trailing call_site_actor clause naming the
principal the case acts as when the effect drives a service handler.
Static semantics. {{#grammar-semantics effect_let_stmt}}
call_site_actor
Section titled “call_site_actor”call_site_actor ::= "by" identifier ("(" expression ")")?The test-body by <Actor>(<identity>) clause (v0.182): names the actor a case
acts as when it drives a service handler, and supplies the identity value. Written
by User("bob") for an identity-carrying actor, or by Visitor (no argument) for
a unit-identity actor. Distinct from by_clause, the handler
form, which binds an actor and admits a sum but carries no identity argument.
effect_send_stmt
Section titled “effect_send_stmt”effect_send_stmt ::= "~>" expressionSends an effect asynchronously without awaiting its reply: ~> effect. The
caller does not wait and binds nothing; legal only when the reply is Effect[()]
(see the error gate below). Contrast let _ <- effect, which awaits the reply
and discards it.
Static semantics. {{#grammar-semantics effect_send_stmt}}
do_stmt
Section titled “do_stmt”do_stmt ::= "do" expressionPerforms a unit effect as a statement: do effect (v0.146, ADR 0170). The
binder-free spelling of let _ <- effect when the awaited value is () — the
effect runs and joins the handler, its unit result discarded. Legal only in an
effectful body; the operand must be Effect[()]. To await and discard a
valued reply, keep let _ <- effect, so throwing away a real value stays
visible.
Static semantics. {{#grammar-semantics do_stmt}}
assign_stmt
Section titled “assign_stmt”assign_stmt ::= identifier ":=" expressionname := expr (v0.81, storage track) — a Cell store write. The unconditional
write form; .update(fn) is the read-modify-write form. ADR 0108.
expect_expr
Section titled “expect_expr”expect_expr ::= "expect" (observation_expr | expression)expect — checks a Bool predicate in a case.
Static semantics. {{#grammar-semantics expect_expr}}
observation_expr
Section titled “observation_expr”observation_expr ::= identifier "." identifier ("called" ("once" | number_literal "times")? ("with" expression)? | "never" "called" | "before" identifier "." identifier)An observation over a capability seam, inside a case: a Cap.op subject (named,
not called) and a matcher — called (optionally once / <n> times, optionally
with <predicate>), never called, or before Cap.op. Calls are recorded
automatically at the seam in the test build, so no setup is needed. The matcher
words are contextual keywords.
See also. Observation.
trace_expr
Section titled “trace_expr”trace_expr ::= "trace" "(" identifier "." identifier ")"trace(Cap.op) — the escape hatch: the recorded calls of a capability operation as
a List of per-operation records, in call order, asserted with the ordinary List
surface. A test-only builtin.
See also. Observation.
binding_name
Section titled “binding_name”binding_name ::= identifier | "_"The name bound by a let: an identifier, or _ to discard.
Testing constructs
Section titled “Testing constructs”Cases and stub clauses. See also the top-level
suite_decl.
case ::= "case" string_literal ("as" ("unit" | "integration" | "system"))? "{" stub_clause* statement* expression? "}"A single named case, typically ending in expects. An optional as <tier>
classifier (unit, integration, or system) records the test level, and the
body may open with stub clauses before its statements.
Example.
case "a fresh counter starts at zero" { let n <- Counter(CounterId.unsafe("fresh")).current() expect n == 0}Static semantics. {{#grammar-semantics case}}
See also. Testing · Write tests and stub collaborators.
property_decl
Section titled “property_decl”property_decl ::= "property" string_literal "{" for_all "}"A generative property (v0.114) — the generative sibling of case. Its body is
a single for all binder; the runner produces the subjects.
Example.
property "more discount, never a higher price" { for all p: Price, a: Percent, b: Percent where a <= b { expect discount(p, b) <= discount(p, a) }}Static semantics. {{#grammar-semantics property_decl}}
See also. Testing · Write tests.
for_all
Section titled “for_all”for_all ::= "for" "all" for_all_binding ("," for_all_binding)* ("where" expression)? blockThe for all binder: one or more bindings over
generated inhabitants, an optional where filter (a pure Bool applied before
the body runs), and a predicate body of expects.
Static semantics. {{#grammar-semantics for_all}}
for_all_binding
Section titled “for_all_binding”for_all_binding ::= identifier ":" type_refA single for all binding, x: T — binds x to a generated inhabitant of the
refinement-generable type T.
stub_clause
Section titled “stub_clause”stub_clause ::= "stub" identifier "." identifier "(" (("_" | expression) ("," ("_" | expression))* ","?)? ")" ("returns" "each" "[" (("fails" | expression) ("," ("fails" | expression))* ","?)? "]" | "returns" expression | "fails")A test-scope stub for one capability operation — stub Cap.op(<pattern>, …) <rhs>. Each argument pattern is _ (any) or a value expression; the right-hand
side is returns <expr>, returns each [ <outcome>, … ] (a scripted sequence of
outcomes, each fails or an expression), or fails. Its own keyword since the
keyword-hygiene batch (#548) — no longer punned on the production
provider_decl (provides Cap = Impl …); it appears as a
suite item or as a leading item in a case body. returns / each / fails
are contextual words.
See also. Write tests and stub collaborators.