(* ============================================================================ *) (* Wax Language Grammar — W3C-style EBNF *) (* *) (* Hand-maintained from Src/Parser.c. Keep in sync when the parser changes. *) (* *) (* Notation: *) (* = defines a production ; terminates a production *) (* | alternative ? optional (zero or one) *) (* * zero or more + one or more *) (* () grouping '' terminal keyword/token *) (* UPPER_CASE terminal token category PascalCase non-terminal *) (* (* ... *) comment *) (* ============================================================================ *) (* ======================================================================== *) (* Section 1: Source File Structure *) (* ======================================================================== *) SourceFile = NamespaceDeclaration? TopLevelDeclaration* EOF ; TopLevelDeclaration = MethodDeclaration | PropertyDeclaration | FieldDeclaration | TypeDeclaration | LayoutDeclaration | InvestigationDeclaration | WidgetFnDeclaration | ChannelDeclaration | ImportDeclaration ; (* TypeDeclaration, ChannelDeclaration and ImportDeclaration are TOP-LEVEL ONLY. A type is scoped by its namespace and never by an enclosing type, so Wax has no nested type and no 'Outer.Inner' name; an import binds a name for the whole file, not for one type's body; and a channel's slots are global. Written inside a type body each is parsed and then refused by name — ERR_TypeDeclarationMustBeTopLevel, ERR_ImportMustBeTopLevel, ERR_ChannelMustBeTopLevel — rather than silently treated as a member. The reverse also holds: ConstructorDeclaration and EnumMember are MEMBERS ONLY, since each names something about the type that declares it, and a top-level one is refused with ERR_ConstructorMustBeAMember / ERR_EnumCaseNotAllowedHere. An ImportDeclaration conventionally precedes every other declaration; one written after them is accepted with ERR_ImportDeclarationsShouldAppearBeforeDeclarations. *) NamespaceDeclaration = 'namespace' QualifiedPath ';' ; ImportDeclaration = 'import' ImportItem ( ',' ImportItem )* ( 'from' ImportPath )? ';' ; ImportPath = ScopedPackagePath | QualifiedPath ; ScopedPackagePath = '@' PackageIdentitySegment '/' PackageIdentitySegment ( '::' SimpleName ( '::' SimpleName )* )? ; (* The other '@'. Reached only from here and from Namespace, never from statement position, where '@' heads a DecoratedContribution instead -- the two are separated by position and share no lookahead. *) PackageIdentitySegment = PACKAGE_IDENTITY_WORD ( ( '-' | '.' ) PACKAGE_IDENTITY_WORD )* ; PACKAGE_IDENTITY_WORD = ASCII_LETTER ( ASCII_LETTER | ASCII_DIGIT )* ; ImportItem = VisibilityModifier? IDENTIFIER ( 'as' IDENTIFIER )? ; QualifiedPath = '::'? SimpleName ( '::' SimpleName )* ; (* ======================================================================== *) (* Section 2: Type Declarations *) (* ======================================================================== *) TypeDeclaration = Modifiers TypeKeyword IDENTIFIER TypeParameterList? CompactFieldHeader? ( 'is' ConstraintQualifier )? ( 'extends' Type )? ( 'implements' TypeList )? WhereClause? ( '{' MemberDeclaration* '}' | ';' ) ; (* Semantic restriction: a user-declared type name begins with uppercase. ';' terminates a declaration only when a CompactFieldHeader is present. *) CompactFieldHeader = ParenParameterList ; (* 'struct Point(float x, float y);' declares one public, writable instance field per entry, in header order, plus the type's single unnamed constructor assigning each entry to its field. 'struct' and 'class' only; every other TypeKeyword is refused by name. A parameter default is an ordinary constructor default, not a field initializer. 'ref', 'out', and 'scoped' are refused: an entry is stored, not borrowed. 'is' is refused alongside a header, since a constraint declaration carries neither fields nor a constructor; 'extends' and 'implements' are not. The generated constructor takes the implicit ': base()', so a base whose constructor requires arguments is the ordinary missing-base-call error. A body may still follow the header, and its members meet the generated ones under the ordinary duplicate rules. *) TypeKeyword = 'class' | 'struct' | 'interface' | 'error' | 'widget' | 'enum' | 'flags' | 'constraint' ; LayoutDeclaration = 'layout' IDENTIFIER ParenParameterList 'for' LayoutShape ( 'facet' Type )? '{' MemberDeclaration* '}' ; (* A TOP-LEVEL declaration only, and deliberately NOT a TypeDeclaration: a layout is not a source type. It has no modifiers, no type parameters, no 'is'/'extends'/'implements'/'where', and no bodyless form. Its name lives in the compiler-global layout registry rather than the type namespace, so it never resolves in a type position and a nested one is refused by name -- ERR_LayoutDeclarationMustBeTopLevel. 'layout' is CONTEXTUAL. It is promoted to a keyword only in front of ':' (the slot member), in front of 'IDENTIFIER (' (this production), and in front of a field's type or another modifier (the 'layout' field modifier, which is therefore order-free like every other one). A '{' directly after it is claimed too, so the shapeless form is refused by name rather than read as a field type. Everywhere else -- variable, field, parameter, member, argument -- it stays an ordinary identifier. 'facet' is contextual in the same way: it is promoted in this header clause, and it is recognized as the accessor keyword member in a layout entry body (see LayoutFacetRead). Neither layout form takes Modifiers. A leading one is refused once by name with ERR_ModifierNotValidHere rather than cascading through the field fallback. The 'for' clause takes a LayoutShape, the same spelling the layout slot declares -- shape word, accepted child type, provisioning. A misspelled shape word does not unwind the header; arity words are lowercase, and an uppercase one names the lowercase spelling that replaced it in the same diagnostic. A layout declares exactly ONE facet -- declaring it is what allocates its storage, and two facets that only ever co-occur are one struct with nullable fields. A comma-separated tail still parses so the refusal can point at the second entry (ERR_LayoutDeclaresOneFacet). The 'facet' Type must resolve to a struct (ERR_LayoutFacetMustBeAStruct), and the body must declare a 'Perform' entry (ERR_LayoutMissingPerformEntry). Header parameters are the layout's complete applied input list; every field the body declares is retained layout-local state. *) LayoutFacetRead = Expression '.' 'facet' '<' Type '>' '(' Expression? ')' ; (* The ONE facet accessor, and a keyword member rather than a member of the context type: it is recognized positionally against the enclosing layout, so it exists only inside a layout entry body and only against that entry's own scoped context parameter. Indexed on the indexed arities ('children.facet(i)'), unindexed on 'leaf' and 'singlechild' ('child.facet()') -- ERR_LayoutRecipeArgumentCountMismatch. Type must be the enclosing layout's declared facet; anything else, including a read in a layout that declares no facet, is ERR_LayoutFacetTypeMismatch, which names the declared facet. The read is NULLABLE: it yields 'Type?'. Presence is never promised -- a facet nothing wrote, one dropped at its meet, and one on a child not built yet all read null -- and the total form is the coalesce against the struct's own declared ground state ('children.facet(i) ?? new Flex()'). A read whose receiver is a local SHADOWING the context parameter's name addresses that local rather than the context, and is refused by name with ERR_FacetReadThroughAShadowedContext. 'facet' is contextual and stays an ordinary identifier for locals, parameters and function names. What makes the accessor unambiguous is a narrowing rule rather than reservation: declaring a MEMBER named 'facet' on any type is ERR_FacetMemberNameIsReserved. *) ManualFacetAccess = Expression '.' 'facet' '<' Type '>' '(' Expression ( ',' Expression )? ')' ; (* The same keyword member in the manual / retained world: a node-typed receiver, plain calls, imperative semantics. One argument READS the edge between the receiver and that child and yields 'Type?'; two WRITE it, last-write-wins, with 'null' as the clear. The 1-arg/2-arg read/write pairing is a compiler-known special case confined to this one member -- user code cannot declare arity overloads -- and there is no 'SetFacet' sibling, no lvalue form and no generated per-facet method. Any other argument count is ERR_ManualFacetCallShape, and so is a missing type argument. An edge is a (parent, child) pair of render nodes, so the receiver (ERR_ManualFacetReceiverNotARenderNode) and the child argument (ERR_ManualFacetChildNotARenderNode) are both node types -- operationally, a class whose 'layout : shape { ... }' slot chooses its layout, own or inherited. Type must be in the receiver's DERIVED FACET SET: the union of the facets declared by the layouts its selector can return. Anything else is ERR_ManualFacetNotInDerivedSet, which names the set. The check is DEFERRED when the node type or the facet type is a type parameter, and it is SKIPPED whenever the set could not be computed in full -- a selector naming a layout that does not resolve, or an ambiguous flat name -- since the file that broke the selector is the one reporting it. A manual WRITE against a declaratively- or machinery-owned edge is refused (F12). Edge ownership kinds do not exist yet, in the compiler or the runtime, so that rule is unimplemented; nothing passes silently in the meantime, because every backend refuses 'wax.layout.manual_facet_write' by name. *) WidgetFnDeclaration = Modifiers? 'widget' 'fn' IDENTIFIER TypeParameterList? ParenParameterList ContributionClause WhereClause? ( Block | ';' ) ; (* A widget callable. Everything a method declaration has -- modifiers, generics, parameters, a where clause and a body -- with one difference: its ':' position is a ContributionClause rather than a ReturnType, and the two are never both present. The clause is REQUIRED: a widget is what it contributes, and every rule that reads a clause is silently off without one (ERR_WidgetNeedsAContributionClause). 'widget' is a reserved keyword with four uses, separated by what follows it: 'widget fn IDENTIFIER' is this production, 'widget fn <' is a widget lambda type (see FunctionType), 'widget IDENTIFIER {' is a TypeDeclaration, and anything else in a type position is the reference root (see WidgetRootType). Parsed in member position as well as at top level; whether a widget callable may be a member is a semantic question and is answered where the clause is checked. The body is WIDGET-BODY STATEMENT POSITION, which is the only place a FacetStatement is legal. *) InvestigationDeclaration = VisibilityModifier? 'investigation' IDENTIFIER '(' ( StringLiteral | RawStringLiteral ) ')' '{' InvestigationMember* '}' ; (* A TOP-LEVEL debugger query-overlay declaration. Omitted visibility means public; any visibility other than public is refused. The description is a nonempty, non-interpolated string literal. An investigation is a structural program rather than a value or type: it cannot be constructed or invoked by ordinary Wax code outside its own body. *) InvestigationMember = InvestigationMethod | InvestigationCase ; InvestigationMethod = VisibilityModifier? 'fn' IDENTIFIER ParenParameterList ReturnType? Block ; (* Omitted visibility means public. Public members are discoverable runner entries; private members are declaration-local. Internal warns and behaves as private; protected is refused. A member is non-generic and non-throwing and takes either no argument or one plain by-value typed struct argument. *) InvestigationCase = 'case' ( StringLiteral | RawStringLiteral ) '=>' IDENTIFIER ArgumentList ';' ; (* The identifier must name a member of this investigation. Its arguments are closed source expressions checked with ordinary invocation rules. Case names are nonempty and unique within the declaration. *) ContributionClause = ':' Type FacetClause* ; (* What calling the widget contributes, plus the facets that contribution carries. The Type is a lowercase shape ('singlechild', 'singlechild?', 'multichild'), a typed shape ('singlechild', 'multichild'), or a bare widget-declared type, which is sugar for 'singlechild'. Shapes are lowercase contextual words; uppercase in this position is read as a node type. The legal '?' placements are 'singlechild?', 'singlechild?' and 'NodeType?' -- all "zero or one contribution". 'multichild?' does not exist, since multichild is already zero or more (ERR_ShapeIsNotOptional), and the marker belongs to the clause rather than to the node type, so 'singlechild' is not one of them either -- it names a nullable node type and is read as such. An uppercase contribution type must be a render node -- a widget whose 'layout : shape { ... }' slot chooses its layout, which is what gives a node type the derived facet set a clause is read against. Otherwise ERR_ContributionTypeNotRenderNode. A typed shape obliges EVERY root contribution to the declared node type -- or a subclass of it, and "some render node" is not "this render node", so a contribution whose own clause names no node type does not satisfy one that does (ERR_TypedContributionShapeViolated); mixed content declares the bare shape and gives up the yielded handle's element type. A clause must also COVER what its root contributions carry, by naming each facet or by forwarding the union its body slots declare (ERR_ClauseDoesNotCoverCarry) -- droppable carries included, since a 'facet?' still rides out even where it would drop -- and a root 'facet::' write of a facet the clause does not name is ERR_RootFacetWriteNotInClause. Both are checked locally at the callable, so the error lands where the fix is. Where a carried facet MEETS a destination -- the call site for a direct call -- is checked once widget destinations exist; a slot binding meets through the assignability rule (see FunctionType). *) FacetClause = 'facet' '?'? Type | '...' 'facet' ; (* One entry of a contribution clause. 'facet T' carries T strictly -- every destination must accept it; 'facet? T' is the droppable flavour -- where the destination does not accept T the write drops and the edge reads absent. A clause ALLOWS a body to write the facet on its own root contributions; it never requires one, and there is no every-exit checking. '...facet' is forwarding: the language's prefix spread token splicing the bound body's carried set into this clause. It names no type of its own and is legal only on a callable taking a widget-fn body parameter (otherwise ERR_ForwardFacetWithoutBodySlot). Forwarding also SUPPRESSES the meet on that callable's own body slots: what the bound body carries re-surfaces on the wrapper's outgoing edge and meets at the wrapper's call site instead, which is what makes the sugared and hand-written decorator forms identical. The forward-and-demote form '...facet?' is reserved, not spelled: a wrapper may not weaken its target's declared strictness (ERR_ForwardFacetCannotDemote). A carried type is a facet struct, held to the same admission rule as a 'facet::' value (ERR_FacetValueTypeNotAdmitted), and one facet type has one writer per edge, so a clause carries each facet type once (ERR_DuplicateFacetWriter). The carried entries are a SET: two clauses accepting the same facets are one slot type, whatever order they were spelled in. 'facet' is contextual here exactly as it is in a layout header. *) DecoratedContribution = '@' SimpleName ArgumentList? Statement ; (* De-nesting sugar over an ordinary wrapper call, and nothing more: '@Name(args) Target();' is EXACTLY 'Name(args) { Target(); }', and a bare '@Name' is 'Name()'. It desugars in the parser, so every rule downstream -- the contribution clause, the facet checks, channel binding -- sees only the hand-written form; there is no decorator-specific rule and no declaration form. Any callable taking a widget fn body parameter qualifies, a widget TYPE included; one that cannot take a body is ERR_TrailingClosureRequiresFnParameter at the desugared site, naming the sugar. The target is itself a Statement, so a stack nests: '@A @B Thing();' is 'A { B { Thing(); } }' -- first listed OUTERMOST. That is also why a 'facet::' prefix on a decorated run binds the outermost edge: what the prefix annotates IS the outermost call. Canonical formatting is 'facet::' lines, then '@' lines, then the indented target. '@' is disambiguated by POSITION, not by a second sigil: it heads a decorator only in contribution-statement position, while package identity ('@scope/name') is reached only from an Import or a Namespace. Outside a widget fn body the prefix is ERR_DecoratorOutsideWidgetBody; with nothing after it to wrap -- the half-typed state an editor sees constantly -- it is ERR_DecoratorPrefixNotAContribution, and the token that could not start a statement is left where it is so the block still closes. *) FacetStatement = FacetApplication+ Statement ; (* The facet application statement family. Canonical formatting is multi-line: 'facet::' lines first, then the target contribution indented beneath; single-line stays legal for the trivial case. Multiple facets on one target REPEAT THE PREFIX on their own lines -- there is no comma list and no block form -- so the whole run collects onto one statement. The target must be a contribution CALL. A 'state foreach', an 'if', a 'switch' or a block is ERR_FacetPrefixNotAContribution, whose fix is to annotate the contribution inside the loop, per item. One facet type has one writer per edge (ERR_DuplicateFacetWriter), across the caller/callee boundary: a prefix meeting a facet the callee's clause carries is the same duplicate, because a carry rides out to exactly the edge the prefix names. The sanctioned base/override spelling is a single 'facet::if'. For the same reason 'facet::null' contradicts any carried facet, strict or droppable (ERR_FacetNullCombinedWithFacet). A caller-side prefix on a call whose declared shape is 'multichild' names no single edge and is ERR_FacetOnMultiChildContribution, which names both fixes: wrap the group in a container, or let the aggregate carry the facet through its own clause. A statement must either target a contribution into a destination created in the same body, or ride a ROOT contribution -- a direct statement of the widget body -- covered by that callable's clause. Anything else is ERR_FacetDestinationNotCreatedHere. Control flow in the widget's own body does not change whose destination a contribution lands in, so a 'facet::' under an 'if' or a 'state foreach' is a root contribution like any other -- which is what makes the canonical per-item spelling legal. A body bound to another callable's widget fn slot is the case that is not: its contributions land in a destination that callable created, so nothing there may name its edge (ERR_FacetDestinationNotCreatedHere). A PLAIN LAMBDA contributes nothing at all, so a 'facet::' there names no edge either; it gets ERR_FacetApplicationOutsideWidgetBody with the boundary named. *) FacetApplication = 'facet' '::' FacetHead | 'facet' '::' 'if' '(' Expression ')' FacetHead ( 'else' FacetHead )? | 'facet' '::' '(' Expression ')' | 'facet' '::' 'null' ; (* '::' is a live postfix operator in expressions, so 'facet' '::' is promoted as a statement head in widget-body statement position ONLY. Everywhere else 'facet' stays an ordinary identifier and the sequence gets a targeted ERR_FacetApplicationOutsideWidgetBody -- including in expression position, where there is nothing to fall back on: a contribution has no value by the widget ABI. 'facet::if' is the canonical conditional-presence idiom: value when true, ABSENT when false, identity preserved either way. The false arm does not evaluate. Arms are facet-value heads only -- no nesting and no 'else if' chain (ERR_FacetIfArmNotAHead) -- and both arms name one facet type (ERR_FacetIfArmTypeMismatch). The parenthesized computed form admits any expression of an admitted type. An inline ternary inside it is legal but non-canonical; a presence-deciding helper returning 'T?' is the canonical spelling. 'facet::null' asserts that this contribution's edge deliberately carries no facets. It is not a clear -- clearing exists only in the manual world -- and combining it with any other application on the same target is ERR_FacetNullCombinedWithFacet. Every form's value must be an admitted facet struct 'T' or 'T?' of one; anything else is ERR_FacetValueTypeNotAdmitted. A null value writes nothing this pass. *) FacetHead = IDENTIFIER ( '.' IDENTIFIER )* ArgumentList ; (* A qualified name plus EXACTLY ONE argument list. Resolution decides what the name is: no overloading means every name has one binding, so a facet type name is an elided-'new' construction (the third new-elision position, and a type wins over a same-named callable) and anything else is an ordinary call -- factory ('facet::Flex.Weight(4.0f)'), free function ('facet::WeightFor(song)') or method -- whose static return type is the facet value. The empty argument list ('facet::Flex()') IS the struct's declared ground state; a bare name with no argument list is ERR_FacetHeadMissingArgumentList. A head that is anything else -- a chain postfixed after that one argument list, an index, a '::' path -- is ERR_FacetHeadPostfixChain, and a value that needs more than a head goes in the computed form: 'facet::(Flex.Weight(4.0f).Scaled(2.0f))'. *) LayoutShape = IDENTIFIER TypeArgumentList? '?'? | 'lazy' '<' '(' InformalParameter ( ',' InformalParameter )* ')' ':' Type '>' ; (* One shape spelling, shared by a layout header's 'for' clause and a widget's layout slot. The shape word is parsed as a plain IDENTIFIER and checked semantically against the closed family 'leaf' | 'singlechild' | 'multichild' | 'fixedchild2' .. 'fixedchild8' -- ERR_UnknownLayoutArity. Fixed arity is that literal family and nothing else: eager angle brackets take the accepted child type, defaulting to 'widget' (and spelling that default out, 'multichild', is the same shape), never a count, so 'fixedchild<3>' is ERR_LayoutShapeTakesTypesNotCounts naming the family and 'multichild' as the runtime-count fix. Lazy is a separate shape head with an ordinary function signature in angles. Its full parameter list declares the injected builder; the first parameter is the key, and the mandatory return type is the widget type each key builds ('widget' accepts any widget). The derived channel inserts the contribution shape: 'widget fn<(params) : singlechild>'. Floating-point first parameters are ERR_LazyLayoutKeyCannotBeFloat because floating point is not stable identity. Parameter names document the contract but do not participate in shape agreement. An arity word composed with lazy is ERR_LayoutShapeCannotBeLazy naming the head spelling; the old suffix form is not an alias. The '?' is the zero-or-one marker on eager shapes. Which shapes admit each part is semantic. The marker composes onto 'singlechild' alone -- 'multichild' is already zero-or-more, and no other shape has an optional child -- so anything else is ERR_ShapeIsNotOptional; it also states that an OWNER's slot may be empty, so it belongs on a slot and not on a layout header. A 'leaf' accepts no child type, and no eager shape accepts more than one (ERR_LayoutShapeTypeArgumentNotValid). *) LayoutSelectorDeclaration = 'layout' ':' LayoutShape Block ; (* A MEMBER only -- it declares the layout slot of the widget that declares it, so a top-level one is refused with ERR_LayoutSelectorMustBeAMember. Unnamed, non-addressable and implicitly sealed, so it takes no modifiers. A node has one layout slot, so a type declares at most one selector; every block after the first is refused with ERR_DuplicateLayoutSelector. The shape is DECLARED, never inferred from the recipes the block returns, because the block is a choice among recipes and the shape is what every one of them must agree with -- a recipe whose own shape disagrees is ERR_LayoutRecipeContractMismatch naming both. The bare 'layout { ... }' form is therefore ERR_LayoutSlotNeedsAShape, naming the 'layout : shape { ... }' spelling that fixes it; the block is parsed either way so its own mistakes are reported in the same compile. A slot takes no facet clause: a facet rides the LAYOUT HEADER ('layout Name(...) for shape facet F'), and the slot derives its set from the layouts its block can return. 'layout : shape facet F { ... }' is ERR_LayoutSlotTakesNoFacetClause naming that spelling; the clause is eaten whole (bounded by '{', '}', ';', EOF) so it draws one complaint and the block behind it still parses. A slot is what makes a declaration a node in the widget tree, so only a 'widget' declares one (ERR_LayoutSlotOutsideWidget), and declaring one SEALS that widget: extending it is ERR_LayoutSlotSealsWidget. 'layout' FIELDS do not seal, which is what lets a behaviour-only base declare them for a derived widget's slot to read. A widget whose lineage declares no slot is implicitly abstract: constructing one is ERR_WidgetHasNoLayoutSlot, since there is no default layout to fall back on. A layout recipe ('return Linear(axis, gap);') is legal only as the direct expression of a return inside this block; naming a layout anywhere else is ERR_LayoutRecipeOutsideSelectorReturn rather than an unresolved identifier. *) ChannelDeclaration = (VisibilityModifier | ChannelDirection)* 'channel' '?'? IDENTIFIER '{' ChannelField+ '}' ; (* The leading modifiers are order-free, as everywhere else in the language: they reach the declaration as one modifier list, so 'public host channel Foo' and 'host public channel Foo' parse identically. Exactly one direction, and at most one visibility. A TOP-LEVEL declaration only: a channel's slots are global, so nesting one inside any other type is an error rather than a member. 'channel' is a reserved word, like every other declaration keyword. A singleton: no type parameters, no 'is'/'extends'/'implements'/'where', no user-declared methods, no constructors, no nested types. The compiler adds the built-in GetChanges() query. The parser still consumes each of those tail forms so it can refuse it by name. '?' = OPTIONAL: the register block is present or absent as a unit, per frame, ground state absent, and it is read through the nullable payload capture ('if (Foo) |f| { ... }') rather than field by field. The '?' rides the KEYWORD — 'channel? Foo', the same position 'host? fn' uses, and the same position an ordinary nullable declaration writes as 'T? name'. 'channel Foo?' is refused by name. An optional channel carries registers only — no event field and no nullable field, since either would be a second presence fact — and those registers must be ref-free, so 'string' and 'ReadOnlySpan' are legal on a required channel and refused here. At least one field: a channel with none binds nothing and can never be read, and the optional form would still emit a slot and a staging obligation carrying no data. Semantic restrictions: the name is upperCamel — the leading uppercase is the general type-name rule, and '_' is refused on top of it because it is the separator the '_' boundary name is built from; a channel is a singleton and is never a value, so it cannot be instantiated, stored, passed, returned, or named as a generic argument, cast target, or 'is'/'as' operand. *) ChannelDirection = 'host' | 'api' ; (* Which side of the boundary FILLS the block. Required, not defaulted: the boundary names the owning side everywhere else it speaks ('host fn' is implemented by the host, 'api fn' by the app), so a bare 'channel' would be the one place a direction was implied. ERR_ChannelMustDeclareDirection. Exactly one -- 'host api channel' states two owners for one block and is refused rather than resolved by order (ERR_ChannelDeclaresTwoDirections), as is a repeat of the same one. 'host?' is refused by name (ERR_HostOptionalNotValidOnChannel): it says the host may not supply the FUNCTION, while a channel spells an absent BLOCK on its own keyword as 'host channel? Foo'. 'host channel': the host stages, the app reads. Every field is read-only; assignment is ERR_CannotAssignToChannel. 'api channel': the app writes the registers and a host reads them. Fields are writable at any depth and readable back, so a register can accumulate across a frame. A 'readonly api fn' cannot write one: the register is state a host observes across calls, which is what the readonly boundary asserts does not happen. An 'event' field is admitted in both directions: inbound it is the host-filled batch the app reads as a span, outbound it is the app-filled 'List' the host copies out, handed a fresh empty list by every FrameBegin. A 'List' register is its persistent sibling. Three shapes an outbound block does not carry, each because its inbound meaning is a fact about HOST staging with no app-side counterpart: optionality (ERR_OutboundChannelCannotBeOptional -- nothing marks a field-by-field block present or clears it), a nullable register (ERR_InvalidChannelPayloadType -- the copy-out ABI has no presence bit), and 'secret' (ERR_OutboundChannelCannotCarrySecret -- host-owned content a recording redacts, so publishing an app-authored one is a decision rather than a default). How a host reads a published register is not a grammar fact and is specified in FrameStream.md. *) ChannelField = VisibilityModifier? 'event'? Type IDENTIFIER ';' ; (* Implicitly static; an initializer is an error. A 'host channel' field is read-only in Wax and gets its required initial value from the host at bind. Its payload vocabulary is the call-boundary vocabulary minus 'opaque', mutable reference types, and arrays nested inside aggregates; immutable strings and frozen json may occur in recursively admitted app structs. 'secret' is admitted only as the exact direct type of a required non-event host field. Per-field optionality is the payload's ('T? x;'), legal only on a NON-optional host channel. An 'api channel' field is writable by Wax and readable by the host; it admits fixed register values, immutable strings (including nested strings), a 'List' collection, an 'event T' batch, and a 'json' document the frame's close stringifies, but no nullable, bare array, or json nested inside an aggregate. Its zero/empty storage is observable before the first assignment, so enum leaves must admit raw zero, string leaves are initialized to real empty strings, and aggregate field initializers are refused rather than skipped. 'event' is contextual in this position only and declares an ordered per-frame batch whose read type is 'ReadOnlySpan'. An array-valued register MUST be declared with the view type it reads as, 'ReadOnlySpan'; the bare 'T[]' spelling is rejected on a host channel, since it would hand the app a mutable reference into the frame's storage. A channel field is the only position where a scoped view type may be declared as storage, because the slot stores the payload array and the read mints a fresh view. Assigning a channel object, or binding it 'ref'/'out', is an error. Field names are lowerCamel. The modifier list really is visibility only: 'static', 'readonly' and 'native' restate what a channel field already is, 'const' contradicts it, and each is refused by name. *) TypeList = Type ( ',' Type )* ; WhereClause = 'where' WhereClauseEntry ( ',' WhereClauseEntry )* ; (* Disambiguation: 'where' is a contextual keyword — parsed when an identifier token with text "where" appears after a parameter list or type declaration header. *) WhereClauseEntry = Type 'is' ( ConstraintQualifier | CallableConstraint | ConstraintList ) ; CallableConstraint = 'callable' '<' ( '(' InformalParameter ( ',' InformalParameter )* ')' )? ReturnType? '>' ; (* callable is contextual after 'where' Type 'is' and uses FunctionType signature syntax without the leading 'fn'. *) ConstraintQualifier = 'struct' | 'class' | 'enum' | 'blittable' | 'flags' | 'numeric' | 'integral' | 'integer' | 'signed' | 'unsigned' | 'real' ; (* flags, numeric, integral, integer, signed, unsigned, real are contextual keywords *) ConstraintList = Type ( '&' Type )* ; (* ======================================================================== *) (* Section 3: Member Declarations *) (* ======================================================================== *) (* Disambiguation: When the current token after modifiers is an identifier or predefined type, the parser uses lookahead to distinguish field declarations (type IDENTIFIER '=' | ';') from expression statements. *) MemberDeclaration = MethodDeclaration | PropertyDeclaration | ConstructorDeclaration | FieldDeclaration | LayoutSelectorDeclaration | EnumMember ; (* TypeDeclaration, ImportDeclaration and ChannelDeclaration are deliberately absent: all three appear only in TopLevelDeclaration. A member is a thing an instance carries or a name the type owns; none of those three is either. ConstructorDeclaration and EnumMember appear only here, for the mirror reason. *) MethodDeclaration = Modifiers 'fn' IDENTIFIER ( '.' IDENTIFIER )? TypeParameterList? ParenParameterList ReturnType? WhereClause? ( Block | ';' ) ; (* Semantic restrictions: 'host? fn' and 'query fn' are top-level only; 'api fn' and 'host fn' are valid top-level or inside a static class. 'query fn' is valid only in a debugger query-overlay compilation and cannot be combined with another function modifier. 'readonly' pairs only with 'api fn', top-level or inside a static class. *) PropertyDeclaration = Modifiers ( 'get' | 'set' ) ( IDENTIFIER | 'this' ) ( '.' ( IDENTIFIER | 'this' ) )? TypeParameterList? ParenParameterList ReturnType? WhereClause? ( Block | ';' ) ; ConstructorDeclaration = Modifiers 'constructor' ( IDENTIFIER ( '.' IDENTIFIER )? )? ParenParameterList ConstructorBaseCall? ( Block | ';' ) ; ConstructorBaseCall = ':' ( 'base' | 'this' ) ( '.' ( IDENTIFIER | 'default' ) )? ArgumentList ; FieldDeclaration = Modifiers ( 'var' Type? | Type ) IDENTIFIER ( '=' Expression )? ';' ; (* Semantic restriction: 'host' / 'host?' on a field is an error. A frame-stream channel is a ChannelDeclaration. The contextual 'layout' modifier ('public layout Axis axis;') is part of Modifiers on a member field only; it marks an applied render-node field the layout selector may read, and like every other modifier its position in the list does not matter. *) EnumMember = Modifiers 'default'? 'case' IDENTIFIER ( '=' Expression )? ';' ; KeyEqualsValueGroup = '{' ( IdentifierEqualsValue ( ',' IdentifierEqualsValue )* ','? )? '}' ; IdentifierEqualsValue = IDENTIFIER '=' Expression ; (* ======================================================================== *) (* Section 4: Generics and Type Parameters *) (* ======================================================================== *) TypeParameterList = '<' TypeParameter ( ',' TypeParameter )* '>' ; TypeParameter = Type ; TypeArgumentList = '<' TypeArgument ( ',' TypeArgument )* '>' ; TypeArgument = Type ; (* Disambiguation: '<' after an identifier is ambiguous between a generic type argument list and a less-than comparison. The parser uses ScanTypeArgumentList with backtracking to decide. *) (* ======================================================================== *) (* Section 5: Types *) (* ======================================================================== *) Type = 'lateinit'? UnderlyingType '?'? ArraySuffix? ; (* 'lateinit' is a prefix modifier on a checked, monotonically-initialized array. It is valid ONLY when an ArraySuffix is present (arrays only; 'lateinit Foo x;' is a semantic error), and it binds the OUTERMOST array level. Construction is single dimension only: 'new lateinit T[n]' cannot be jagged. Reads are checked (panic on an unwritten slot) and dissolve to a dense 'string[]' via AsArray(). See PublicDocs/ArrayNullSafety.md. NOTE: ArraySuffix accepts an Expression, but no sized inline array type exists. The size is dropped in field/parameter/return position ('T[4]' resolves to 'T[]') and is a parse error in a local declaration. See Docs/FixedArrayProposal.md. *) UnderlyingType = PredefinedType | FunctionType | WidgetRootType | 'this' | 'base' | SecretType | TypeName ; WidgetRootType = 'widget' ; (* Bare 'widget' is the REFERENCE ROOT of the widget tree -- "any widget-declared type" -- and is legal wherever a type is: 'widget child', 'widget[]', 'List', parameters, locals, fields, casts. Every widget declaration implicitly extends it, so every widget is assignable to it and narrows off it through the ordinary 'as?' / 'is' machinery. It declares no layout slot and no members of its own: constructing it and extending it are both ERR_WidgetRootIsNotAConcreteWidget, because widget-ness comes with the keyword rather than from a base type. One token of lookahead separates the four uses of the keyword. 'widget' 'fn' is a FunctionType (or, with a following IDENTIFIER, a WidgetFnDeclaration), so the root reading is taken only when 'fn' does NOT follow. In STATEMENT and MEMBER position 'widget' IDENTIFIER still heads a TypeDeclaration, so the two are separated by what comes after the name: a declaration continues with its generic parameters, compact field header, constraint qualifier, base, interfaces or body ('<', '(', 'is', 'extends', 'implements', '{'), and anything else is a declarator. A '?' or '[' suffix is a type either way. That reading gives a malformed 'widget child' the missing-semicolon diagnostic rather than a missing-body one; the spelling it trades away is the bodyless 'widget Name;' INSIDE a type or a function body, where the field and the local are the useful readings. At top level, where there is no local or field to confuse it with, the keyword always heads a declaration. In a ContributionClause or a LayoutShape's type argument it means "any widget", which is what the bare shape already means -- 'multichild' and bare 'multichild' are the same shape, and ': widget' is bare ': singlechild'. *) SecretType = 'secret' ; (* CONTEXTUAL, unlike every PredefinedType spelling: an IDENTIFIER spelled `secret` is a type only where the grammar has already committed to a type. It stays an ordinary identifier as a variable, field, parameter, or member name. Rejected as a declared type name and as a generic parameter name. *) TypeName = IDENTIFIER TypeArgumentList? ; SimpleName = IDENTIFIER TypeArgumentList? ; PredefinedType = 'int8' | 'int16' | 'int32' | 'int64' | 'uint8' | 'uint16' | 'uint32' | 'uint64' | 'bool' | 'byte' | 'sbyte' | 'int' | 'uint' | 'short' | 'ushort' | 'long' | 'ulong' | 'float' | 'float32' | 'double' | 'float64' | 'string' | 'char' | 'object' | 'void' | 'json' | 'opaque' | 'float2' | 'float3' | 'float4' | 'float3x2' | 'float3x3' | 'float4x4' | 'color8' | 'color16' | 'color32' | 'quaternion' | 'timespan' | 'angle' | 'timepoint' | 'int8x16' | 'uint8x16' | 'int16x8' | 'uint16x8' | 'int32x4' | 'uint32x4' | 'int64x2' | 'uint64x2' | 'float32x4' | 'float64x2' ; (* Lowercase PredefinedType spellings are reserved and import-free. Uppercase nominal names match TypeName and use ordinary declaration/import lookup. User-declared type names begin uppercase; StringBuilder requires import. *) FunctionType = 'widget' 'fn' '<' ( '(' InformalParameter ( ',' InformalParameter )* ')' )? ContributionClause '>' | 'state'? 'fn' '<' ( '(' InformalParameter ( ',' InformalParameter )* ')' )? ReturnType? '>' ; (* A widget fn type is a BODY SLOT: 'widget fn<() : singlechild facet Flex> body'. Invoking one contributes rather than returns, so its ':' position is a ContributionClause and never a ReturnType -- the same clause, read by the same model, as a widget callable's. The clause is part of the type's IDENTITY: two slots differing only in what they accept are different types, because they accept different things. What may bind to a slot is the assignability rule (ERR_ClauseNotAssignable): shape covariance composed with 'the source's STRICT carried set is a subset of what the slot accepts', with 'facet?' entries free to exceed and drop. A widget callable and a plain fn never fill each other's slots. The clause is required here for the same reason it is on a declaration: without one the type IS the plain 'fn<...>' type (ERR_WidgetNeedsAContributionClause). *) InformalParameter = 'scoped'? ( 'ref' | 'out' )? Type IDENTIFIER? ; ArraySuffix = '[' Expression? ']' '?'? ArraySuffix? ; ReturnType = ':' 'ref'? Type ThrowsClause? | ThrowsClause ; ThrowsClause = 'throws' ThrowsTypeList? ; ThrowsTypeList = ThrowsType ('|' ThrowsType)* ; (* 'error' names the base error type -- the same thing bare 'throws' means -- and must be the only member when it appears. *) ThrowsType = 'error' | Type ; (* ======================================================================== *) (* Section 6: Parameters *) (* ======================================================================== *) ParenParameterList = '(' ( Parameter ( ',' Parameter )* )? ')' ; Parameter = 'scoped'? ( 'ref' | 'out' )? Type IDENTIFIER ( '=' Expression )? ; (* ======================================================================== *) (* Section 7: Statements *) (* ======================================================================== *) Statement = Block | IfStatement | GuardStatement | WhileStatement | DoWhileStatement | ForStatement | ForEachStatement | ParallelStatement | SwitchStatement | TryStatement | ReturnStatement | ThrowStatement | YieldStatement | BreakStatement | ContinueStatement | UnreachableStatement | PanicStatement | LocalVariableDeclaration | StateVariableDeclaration | StateLifecycleHook | StaticIfStatement | FacetStatement | DecoratedContribution | ExpressionStatement | EmptyStatement ; (* FacetStatement and DecoratedContribution are recognized only in widget-body statement position; see their own productions for what happens to the spelling everywhere else. *) Block = '{' Statement* '}' ; IfStatement = 'if' '(' TryableExpression ( ',' TryableExpression )* ')' PayloadCapture? EmbeddedStatement ElseClause? ; ElseClause = 'else' PayloadCapture? EmbeddedStatement ; (* guard captures-or-bails: the payload binds DOWNWARD into the enclosing scope (live after the statement) and the mandatory 'else' must diverge (return / break / continue / throw / panic / unreachable / yield, or a block/if composed of those). There is no if-style body — the success continuation is the enclosing scope. *) GuardStatement = 'guard' '(' TryableExpression ( ',' TryableExpression )* ')' PayloadCapture? 'else' EmbeddedStatement ; StaticIfStatement = 'static' 'if' '(' StaticIfPredicate ')' EmbeddedStatement StaticIfElseClause? ; StaticIfElseClause = 'else' ( StaticIfElseIf | EmbeddedStatement ) ; StaticIfElseIf = 'if' '(' StaticIfPredicate ')' EmbeddedStatement StaticIfElseClause? ; StaticIfPredicate = StaticIfOr ; StaticIfOr = StaticIfAnd ( '||' StaticIfAnd )* ; StaticIfAnd = StaticIfNot ( '&&' StaticIfNot )* ; StaticIfNot = '!' StaticIfNot | '(' StaticIfPredicate ')' | IDENTIFIER 'is' ( Type | ConstraintQualifier ) ; EmbeddedStatement = Statement ; WhileStatement = 'while' '(' Expression ')' PayloadCapture? EmbeddedStatement ; DoWhileStatement = 'do' PayloadCapture? EmbeddedStatement 'while' '(' Expression ')' ';' ; ForStatement = 'for' '(' ForInit? ';' Expression? ';' Expression? ')' EmbeddedStatement ; ForInit = LocalVariableDeclaration | Expression ; ForEachStatement = 'state'? 'foreach' '(' Expression ( ',' IDENTIFIER ':' Expression )? ')' PayloadCapture? ( 'key' Expression )? EmbeddedStatement ; (* First Expression is the iterable. Optional second in-paren clause is a key selector: foreach (collection, key: (x) => x.id) |item| { ... }. The post-payload `key Expression` clause (state foreach only; 'key' is a contextual keyword) sets the per-iteration state identity. The key Expression is parsed so a following `{` opens the loop body, not a trailing closure or object/collection initializer on the expression — e.g. `key new T(a, b) { body }` binds `{ body }` as the loop body, not as T's initializer. *) ParallelStatement = 'parallel' '(' Expression ',' Expression ')' ':' '(' ParallelBinding ( ',' ParallelBinding )* ','? ')' ParallelIndexCapture? Block ; ParallelBinding = ParallelAlignedBinding | ParallelCapabilityBinding ; ParallelAlignedBinding = ParallelAlignedMode Type IDENTIFIER 'from' ParallelResourcePath ; ParallelAlignedMode = 'readonly' | 'ref' | 'out' ; ParallelCapabilityBinding = Type IDENTIFIER 'from' ParallelResourcePath ; ParallelResourcePath = IDENTIFIER ( '.' IDENTIFIER )* ; ParallelIndexCapture = '|' IDENTIFIER '|' ; SwitchStatement = 'switch' '(' Expression ')' '{' SwitchSection* '}' ; SwitchSection = ( 'case' Expression ( ',' Expression )* | 'default' ) ':' WhenClause? Block ; WhenClause = 'when' '(' Expression ')' PayloadCapture? ; TryStatement = 'try' Expression CatchClause* ';'? ; CatchClause = 'catch' Type? PayloadCapture? Block ; ReturnStatement = 'return' 'ref'? Expression? ';' ; ThrowStatement = 'throw' Expression ';' ; YieldStatement = 'yield' 'ref'? Expression? ';' ; BreakStatement = 'break' ';' ; ContinueStatement = 'continue' ';' ; UnreachableStatement = 'unreachable' ArgumentList? ';' ; PanicStatement = 'panic' ArgumentList ';' ; LocalVariableDeclaration = Modifiers ( 'var' | Type ) IDENTIFIER ( '=' TryableExpression )? ';' ; StateVariableDeclaration = 'state' Type IDENTIFIER '=' Expression ';' ; StateLifecycleHook = 'state' '.' ( 'create' | 'destroy' ) '=>'? Block ; (* `state.enable` / `state.disable` were removed. Under per-frame teardown semantics, every "becomes active" edge is a fresh create and every "becomes inactive" edge is a destroy; the four-hook lifecycle degenerates to two events. The parser still accepts any identifier here (production is intentionally loose) and Sema enforces the create/destroy restriction with a clear diagnostic. *) ExpressionStatement = Expression ';' ; EmptyStatement = ';' ; (* ======================================================================== *) (* Section 8: Expressions — Precedence Hierarchy *) (* *) (* Loosest (top) to tightest (bottom). Each level uses left-associative *) (* {op NextLevel} unless noted otherwise. *) (* ======================================================================== *) Expression = AssignmentExpression ; (* Assignment — right-associative *) AssignmentExpression = ConditionalExpression ( AssignmentOperator AssignmentRhs )? ; AssignmentRhs = 'ref' TryableExpression | TryInlineExpression | ConditionalExpression ; TryInlineExpression = 'try' ConditionalExpression CatchExpressionClause* ; (* Zero catch clauses is syntactically valid; semantic analysis handles this case *) (* Conditional / ternary. Either arm may instead be a diverging bail term ('throw' / 'panic' / 'unreachable') that yields no value — the result then takes the other arm's type and that branch bails. Both arms may not diverge. *) ConditionalExpression = CoalescingExpression ( '?' PayloadCapture? TernaryArm ':' TernaryArm )? ; TernaryArm = TryableExpression | CoalesceBailTerm ; (* Null-coalescing — right-associative. The RHS may instead be a diverging bail term ('throw' / 'panic' / 'unreachable') that yields no value: the coalesce result then takes the LHS non-null type and the null branch bails. *) CoalescingExpression = LogicalOrExpression ( '??' ( LogicalOrExpression | CoalesceBailTerm ) )* ; CoalesceBailTerm = 'throw' Expression | 'panic' ArgumentList | 'unreachable' ArgumentList? ; (* Logical OR *) LogicalOrExpression = LogicalAndExpression ( '||' LogicalAndExpression )* ; (* Logical AND *) LogicalAndExpression = BitwiseOrExpression ( '&&' BitwiseOrExpression )* ; (* Bitwise OR *) BitwiseOrExpression = BitwiseXorExpression ( '|' BitwiseXorExpression )* ; (* Bitwise XOR *) BitwiseXorExpression = BitwiseAndExpression ( '^' BitwiseAndExpression )* ; (* Bitwise AND *) BitwiseAndExpression = EqualityExpression ( '&' EqualityExpression )* ; (* Equality *) EqualityExpression = RelationalExpression ( ( '==' | '!=' ) RelationalExpression )* ; (* Relational — includes 'is' type check *) RelationalExpression = ShiftExpression ( RelationalOp ShiftExpression )* | ShiftExpression 'is' IsType ; IsType = 'lateinit'? UnderlyingType ( '?' IsArraySuffix | IsArraySuffix? ) ; IsArraySuffix = '[' Expression? ']' ( '?' IsArraySuffix | IsArraySuffix? ) ; (* A type test cannot target a nullable outer type: null is not an instance of any type. Accordingly, a '?' after the complete 'is' type begins the ConditionalExpression. Nullable element and inner-array types remain legal because their '?' is followed by another array suffix: 'T?[]' and 'T[]?[]'. *) RelationalOp = '<' | '<=' | '>' | '>=' ; (* Shift *) ShiftExpression = AdditiveExpression ( ( '<<' | '>>' ) AdditiveExpression )* ; (* Additive *) AdditiveExpression = MultiplicativeExpression ( ( '+' | '-' ) MultiplicativeExpression )* ; (* Multiplicative *) MultiplicativeExpression = SwitchPrecedenceExpression ( ( '*' | '/' | '%' ) SwitchPrecedenceExpression )* ; (* Switch expression — infix operator *) SwitchPrecedenceExpression = UnaryExpression ( 'switch' '{' SwitchExpressionArm* '}' )? ; (* Unary — prefix operators *) UnaryExpression = PrefixOperator UnaryExpression | CastExpression ; (* Cast — 'as' binds tighter than unary, like Rust *) CastExpression = PostfixExpression ( 'as' ( '!' | '?' ) Type )? ; PrefixOperator = '+' | '-' | '!' | '~' | '++' | '--' ; (* Note: '-' followed immediately by a numeric literal folds into a single negative NumericLiteral rather than a unary minus expression. *) (* ======================================================================== *) (* Section 9: Postfix and Primary Expressions *) (* ======================================================================== *) PostfixExpression = PrimaryExpression PostfixOp* ; PostfixOp = ArgumentList PayloadCapture? YieldBody? (* invocation *) | BracketedArgumentList (* element access *) | ( '++' | '--' | '!' ) (* postfix unary *) | '.' SimpleName (* member access *) | '.' VisibilityKeyword (* visibility access *) | '::' IDENTIFIER (* colon-colon access *) | '?' ConsequenceChain (* null-conditional *) | '->' SimpleName PipelineArg ; (* iterator chain *) PipelineArg = ArgumentList (* args mode: Take(5), Sum(), IndexOf(value) *) | '(' ArgumentExpression ( ',' ArgumentExpression )* ',' PayloadCapture '=>' Expression ')' (* args + payload body: ReduceOrElse(0, |acc, x| => next) *) | '(' ArgumentExpression ( ',' ArgumentExpression )* ',' PayloadCapture Block ')' (* args + payload block body *) | '(' PayloadCapture '=>' Expression ')' (* payload expression body: Filter(|x| => p), FindIndex(|x| => p) *) | '(' PayloadCapture Block ')' (* payload block body *) | '(' Block ')' ; (* no-payload block body *) YieldBody = Block ; ConsequenceChain = '.' ConsequenceOp ConsequenceTail* ; ConsequenceOp = SimpleName | VisibilityKeyword | BracketedArgumentList | ArgumentList ; ConsequenceTail = ArgumentList | BracketedArgumentList | '.' SimpleName | '.' VisibilityKeyword | '::' IDENTIFIER | '?' ConsequenceChain ; PrimaryExpression = Literal | IDENTIFIER TypeArgumentList? (* simple name *) | ParenExpression | NewExpression | JsonObject | CollectionExpression | ExplicitLambda | AnonymousLambda | 'this' | 'base' | DefaultExpression | SizeOfExpression | RegexExpression | QueryScanExpression | QueryAtExpression | '_' (* discard *) | 'throw' Expression | PredefinedType ; (* valid only before '.' *) (* Disambiguation: '(' is ambiguous between a parenthesized expression and an anonymous lambda. The parser uses IsPossibleAnonymousLambda with lookahead to decide. *) (* 'query.scan' and 'query.at' are compiler-recognized expressions, not library calls. 'query' is a declaration modifier everywhere except directly before a '.', where it opens these two forms. 'scan' and 'at' are ordinary identifiers, not keywords: any other selector after 'query' '.' is an error naming the two accepted ones. The brace body is an ordered lane list in the same '.name = Expression' form an ObjectInitializer uses, so an empty block and a trailing comma both parse; Sema refuses an empty block and duplicate lane names. *) QueryScanExpression = 'query' '.' 'scan' '(' Expression ')' QueryLaneBlock ; QueryAtExpression = 'query' '.' 'at' '(' Expression ')' QueryLaneBlock ; QueryLaneBlock = '{' ( DotIdentifierEqualsValue ( ',' DotIdentifierEqualsValue )* ','? )? '}' ; (* ======================================================================== *) (* Section 10: Literals *) (* ======================================================================== *) Literal = NumericLiteral | StringLiteral | RawStringLiteral | BacktickLiteral | CharacterLiteral | BoolLiteral | NullLiteral ; NumericLiteral = INT32_LITERAL | UINT32_LITERAL | INT64_LITERAL | UINT64_LITERAL | FLOAT_LITERAL | DOUBLE_LITERAL ; BoolLiteral = 'true' | 'false' ; NullLiteral = 'null' ; StringLiteral = STRING_START StringPart* STRING_END | STRING_EMPTY ; RawStringLiteral = RAW_STRING_START StringPart* RAW_STRING_END ; BacktickLiteral = BACKTICK_START StringPart* BACKTICK_END | BACKTICK_EMPTY ; StringPart = STRING_PART | INTERPOLATED_IDENTIFIER | INTERPOLATED_EXPR_START Expression INTERPOLATED_EXPR_END ; CharacterLiteral = CHAR_START CHAR_CONTENT CHAR_END ; (* ======================================================================== *) (* Section 11: Object, Array, and Collection Creation *) (* ======================================================================== *) NewExpression = AnonymousNewExpression | TypedObjectCreation | TypedArrayCreation ; AnonymousNewExpression = 'new' KeyEqualsValueGroup ; TypedObjectCreation = 'new' Type ( '.' IDENTIFIER )? ArgumentList? ObjectInitializer? ; TypedArrayCreation = 'new' 'lateinit'? Type? BracketedArgumentList ( ArrayGeneratorBody | ArrayInitializer )? ; (* Disambiguation: after parsing optional Type, '[' selects array creation while '(' selects TypedObjectCreation. Type may be omitted: new [10] { 1, 2 } 'lateinit' allocates a checked holey buffer 'new lateinit T[n]': n slots left unassigned, reads sentinel-guarded, promotion (AsArray/foreach) verified. It requires a size and forbids an ArrayInitializer / ArrayGeneratorBody / jagged shape (all of which fill every slot, yielding a dense array — drop 'lateinit'). *) ObjectInitializer = '{' ( DotIdentifierEqualsValue ( ',' DotIdentifierEqualsValue )* ','? )? '}' ; DotIdentifierEqualsValue = '.' IDENTIFIER '=' Expression ; ArrayInitializer = '{' ( Expression ( ',' Expression )* ','? )? '}' ; ArrayGeneratorBody = PayloadCapture? Block ; (* Generator construction: the Block yields one element per index, e.g. new Foo[n] |index, length| { yield mk(index); }. The PayloadCapture (|index, length|) is optional; when omitted the Block must lead with 'yield' so it is unambiguous against an ArrayInitializer. Every path through the Block must terminate in a 'yield', like a return. *) CollectionExpression = ListExpression | CollectionBlockExpression ; ListExpression = '[' ( ListElement ( ',' ListElement )* ','? )? ']' ; ListElement = Expression | '...' Expression ; (* spread into array *) CollectionBlockExpression = '[' Expression? ']' CollectionInitializerBody ; CollectionInitializerBody = '{' CollectionInitializerEntry* '}' ; CollectionInitializerEntry = Expression ',' | '...' Expression ',' ; (* The brace body is an initializer list, not a statement block. Declarations, nested blocks, and control-flow statements are not permitted. A following brace belongs to state-foreach when the bracketed expression is its key. *) (* ======================================================================== *) (* Section 12: Lambda Expressions *) (* ======================================================================== *) ExplicitLambda = Modifiers 'fn' ParenParameterList ReturnType? '=>' ( Block | Expression ) ; AnonymousLambda = Modifiers '(' ( AnonymousLambdaParameter ( ',' AnonymousLambdaParameter )* )? ')' '=>' ( Block | Expression ) ; AnonymousLambdaParameter = ( 'ref' | 'out' )? IDENTIFIER ; ParenExpression = '(' TryableExpression ')' ; (* ======================================================================== *) (* Section 13: Switch Expressions *) (* ======================================================================== *) SwitchExpressionArm = 'case' ( WhenClause | Expression WhenClause? ) '=>' TryableExpression ';' ; (* 'case' followed by 'when' omits the match expression — acts as a guard-only arm *) (* ======================================================================== *) (* Section 14: JSON Literals *) (* ======================================================================== *) JsonObject = '{' ( JsonKeyValue ( ',' JsonKeyValue )* ','? )? '}' ; JsonKeyValue = JsonKey ':' Expression | '...' Expression ; (* spread into object *) JsonKey = IDENTIFIER | StringLiteral | '[' Expression ']' ; (* dynamic key *) (* Keywords like null, true, false are accepted as keys with error recovery *) (* ======================================================================== *) (* Section 15: Arguments *) (* ======================================================================== *) ArgumentList = '(' ( Argument ( ',' Argument )* )? ')' ; BracketedArgumentList = '[' ( Argument ( ',' Argument )* )? ']' ; Argument = RefArgument | OutArgument | NamedArgument | WidgetHandler | NamedLambdaArgument | TryableExpression ; RefArgument = 'ref' TryableExpression ; OutArgument = 'out' ( OutVariable | TryableExpression ) ; OutVariable = ( 'var' | Type ) IDENTIFIER ; NamedArgument = '.' IDENTIFIER '=' TryableExpression ; WidgetHandler = IDENTIFIER ':' IDENTIFIER ( '.' IDENTIFIER )? PayloadCapture? '=>' TryableExpression ; NamedLambdaArgument = IDENTIFIER PayloadCapture? '=>' TryableExpression ; DefaultExpression = 'default' ( '(' Type ')' )? ; SizeOfExpression = 'sizeof' '(' Type ')' ; RegexExpression = 'regex' '(' ( REGEX_LITERAL | Expression ) ( ',' Expression )? ')' ; (* REGEX_LITERAL is a /pattern/ token — the tokenizer scans raw content between / delimiters when inside regex(). No double-escaping needed. Literal patterns are validated at compile time via PCRE2. Dynamic string patterns require try/catch for RegexError. *) (* ======================================================================== *) (* Section 16: Payload Capture and Try Expressions *) (* ======================================================================== *) PayloadCapture = '|' ( PayloadElement ( ',' PayloadElement )* )? '|' | '||' ; (* empty capture shorthand *) PayloadElement = ( 'ref' | 'out' )? IDENTIFIER ; TryableExpression = 'try' Expression CatchExpressionClause* | Expression ; CatchExpressionClause = 'catch' Type? PayloadCapture? ( '=>' TryableExpression | Block ) ; (* ======================================================================== *) (* Section 17: Modifiers and Operators *) (* ======================================================================== *) Modifiers = Modifier* ; Modifier = VisibilityModifier | 'static' | 'abstract' | 'virtual' | 'override' | 'readonly' | 'const' | 'sealed' | 'ref' | 'native' | 'blittable' | 'intrinsic' | 'host' | 'host?' | 'api' | 'query' | 'recording' | 'scoped' | 'layout' ; (* 'layout' is a FIELD modifier only, and only on a member field. It is contextual: it is read as a modifier only in front of the field's type or in front of another modifier -- so it is order-free like every other one, and a member or local spelled `layout` stays an identifier. See LayoutSelectorDeclaration for the block form and LayoutDeclaration for the declaration form. 'host' and 'host?' are FUNCTION modifiers only — see MethodDeclaration. On a field they are an error: a frame-stream channel is a ChannelDeclaration, never a field modifier. 'scoped' on a struct declares that its values may live in a stack region, which restricts where they may be stored; on a local it promises the value dies with the declaring scope. See Parameter for its third, per-argument meaning. *) (* 'sealed' on a class forbids extending it; on a member it forbids overriding that member again, and is only valid alongside 'override'. *) VisibilityModifier = 'public' | 'private' | 'protected' | 'internal' ; VisibilityKeyword = 'public' | 'private' | 'protected' | 'internal' ; AssignmentOperator = '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '??=' | '|=' | '&=' | '^=' | '<<=' | '>>=' ; (* ======================================================================== *) (* Section 18: Lexical Terminals *) (* ======================================================================== *) (* These are token categories produced by the tokenizer, not grammar rules. *) IDENTIFIER = (* any valid identifier not matching a reserved keyword *) ; ASCII_LETTER = (* one ASCII letter from A through Z or a through z *) ; ASCII_DIGIT = (* one ASCII digit from 0 through 9 *) ; INT32_LITERAL = (* decimal or hex integer literal, 32-bit *) ; UINT32_LITERAL = (* unsigned 32-bit integer literal *) ; INT64_LITERAL = (* 64-bit integer literal *) ; UINT64_LITERAL = (* unsigned 64-bit integer literal *) ; FLOAT_LITERAL = (* floating-point literal with 'f' suffix *) ; DOUBLE_LITERAL = (* floating-point literal without suffix *) ; STRING_START = (* opening " of an interpolated string *) ; STRING_END = (* closing " of an interpolated string *) ; STRING_PART = (* literal text segment within a string *) ; STRING_EMPTY = (* empty string "" as single token *) ; RAW_STRING_START = (* opening of a raw string *) ; RAW_STRING_END = (* closing of a raw string *) ; BACKTICK_START = (* opening backtick of a template string *) ; BACKTICK_END = (* closing backtick of a template string *) ; BACKTICK_EMPTY = (* empty backtick string `` *) ; INTERPOLATED_IDENTIFIER = (* $identifier within a string *) ; INTERPOLATED_EXPR_START = (* ${ within a string *) ; INTERPOLATED_EXPR_END = (* } closing an interpolated expression *) ; CHAR_START = (* opening ' of a character literal *) ; CHAR_CONTENT = (* character content *) ; CHAR_END = (* closing ' of a character literal *) ; EOF = (* end of file *) ;