Grammar (EBNF)
This document provides the complete formal grammar for the Projection Declaration Language using Extended Backus-Naur Form (EBNF) notation.
EBNF Notation
Section titled “EBNF Notation”EBNF is a notation for formally describing syntax. Here are the key elements used:
| Notation | Meaning | Example |
|---|---|---|
= | Definition | Rule = "value" ; |
; | End of rule | Rule = "value" ; |
{ } | Zero or more | { "item" } matches "", “item”, “itemitem”, etc. |
[ ] | Optional (zero or one) | [ "optional" ] matches "" or “optional” |
( ) | Grouping | ( "a" | "b" ) matches “a” or “b” |
| | Alternative (or) | "a" | "b" matches either “a” or “b” |
" " | Terminal (literal) | "keyword" matches the text “keyword” |
, | Sequence | "a", "b" matches “a” followed by “b” |
Special Symbols:
NL- NewlineINDENT- Increased indentation levelDEDENT- Decreased indentation levelIdent- Identifier (letter followed by letters, digits, or underscores)TypeRef- Type reference (identifier with optional dot notation)
Complete Grammar
Section titled “Complete Grammar”Document = { Projection } ;
Projection = "projection", Ident, [ "=>", TypeRef ], NL, [ INDENT, { ProjDirective | Block }, DEDENT ] ;
ProjDirective = "no", "automap", NL | KeyDecl | CompositeKeyDecl ;
Block = EveryBlock | FromAllBlock | FromEventBlock | JoinBlock | ChildrenBlock | NestedBlock | RemoveWithBlock | RemoveWithJoinBlock ;
EveryBlock = "every", NL, INDENT, [ "no", "automap", NL ], { MappingLine }, [ "exclude", "children", NL ], DEDENT ;
FromAllBlock = "all", NL, [ INDENT, [ "automap" | "no", "automap", NL ], { MappingLine }, DEDENT ] ;
FromEventBlock = "from", EventSpec, { ",", EventSpec }, NL, [ INDENT, [ ParentDecl ], { MappingLine | KeyDecl | CompositeKeyDecl }, DEDENT ] ;
EventSpec = TypeRef, [ "key", Expr ] ;
KeyInline = "key", Expr ;
JoinBlock = "join", Ident, "on", Ident, NL, INDENT, { WithEventBlock }, DEDENT ;
WithEventBlock = "with", TypeRef, NL, [ INDENT, { MappingLine }, DEDENT ] ;
ChildrenBlock = "children", Ident, "identified", "by", Expr, NL, INDENT, [ "no", "automap", NL ], { ChildBlock }, DEDENT ;
ChildBlock = ChildEveryBlock | FromEventBlock | JoinBlock | RemoveWithBlock | RemoveWithJoinBlock | ChildrenBlock | NestedBlock | ClearWithBlock ;
NestedBlock = "nested", Ident, NL, INDENT, [ "automap" | "no", "automap", NL ], { ProjDirective | Block | NestedBlock | ClearWithBlock }, DEDENT ;
ClearWithBlock = "clear", "with", TypeRef, NL ;
ChildEveryBlock = "every", NL, INDENT, [ "no", "automap", NL ], { MappingLine }, DEDENT ;
RemoveWithBlock = "remove", "with", TypeRef, [ KeyInline ], NL, [ INDENT, [ ParentDecl ], DEDENT ] ;
RemoveWithJoinBlock = "remove", "via", "join", "on", TypeRef, [ KeyInline ], NL ;
KeyDecl = "key", Expr, NL ;
CompositeKeyDecl = "key", TypeRef, "{", NL, INDENT, KeyPart, { NL, KeyPart }, NL?, DEDENT, "}", NL ;
KeyPart = Ident, "=", Expr ;
ParentDecl = "parent", Expr, NL ;
MappingLine = Assignment | IncLine | DecLine | CountLine | AddLine | SubLine ;
Assignment = Ident, "=", Expr, NL ;
IncLine = "increment", Ident, NL ;DecLine = "decrement", Ident, NL ;CountLine = "count", Ident, NL ;
AddLine = "add", Ident, "by", Expr, NL ;SubLine = "subtract", Ident, "by", Expr, NL ;
Expr = Template | Literal | DollarExpr | Path ;
DollarExpr = "$eventSourceId" | "$eventContext", ".", Ident ;
Path = Ident, { ".", Ident } ;
Template = "`", { TemplateChar | "${", Expr, "}" }, "`" ;TemplateChar = (* any character except ` *) ;
Literal = BoolLiteral | StringLiteral | NumberLiteral | NullLiteral ;
BoolLiteral = "true" | "false" ;StringLiteral = '"', { StringChar }, '"' ;NumberLiteral = [ "-" ], Digit, { Digit }, [ ".", Digit, { Digit } ] ;NullLiteral = "null" ;
TypeRef = Ident, { ".", Ident } ;Ident = Letter, { Letter | Digit | "_" } ;
Letter = "A" | "B" | ... | "Z" | "a" | "b" | ... | "z" ;Digit = "0" | "1" | "2" | ... | "9" ;StringChar = (* any character except " and newline *) ;```pdl
## Grammar Breakdown
### Document Structure
A document contains one or more projections:
```ebnfDocument = { Projection } ;Projection
Section titled “Projection”A projection defines the read model and contains directives and blocks:
Projection = "projection", Ident, "=>", TypeRef, NL, INDENT, { ProjDirective | Block }, DEDENT ;Example:
projection User => UserReadModel from UserCreated Name = nameNote: AutoMap is enabled by default. Use no automap to disable it.
Directives
Section titled “Directives”Projection-level directives:
ProjDirective = "no", "automap", NL | KeyDecl | CompositeKeyDecl ;Blocks
Section titled “Blocks”Main building blocks of a projection:
Block = EveryBlock | FromAllBlock | FromEventBlock | JoinBlock | ChildrenBlock | RemoveWithBlock | RemoveWithJoinBlock ;Every Block
Section titled “Every Block”Apply mappings to all events:
EveryBlock = "every", NL, INDENT, [ "no", "automap", NL ], { MappingLine }, [ "exclude", "children", NL ], DEDENT ;From Event Block
Section titled “From Event Block”Handle specific events:
FromEventBlock = "from", EventSpec, { ",", EventSpec }, NL, [ INDENT, [ ParentDecl ], { MappingLine | KeyDecl | CompositeKeyDecl }, DEDENT ] ;
EventSpec = TypeRef, [ "key", Expr ] ;```pdl
**Note:** AutoMap for from blocks is controlled at the projection or children level, not within individual from blocks.
### Join Block
Enrich with joined events:
```ebnfJoinBlock = "join", Ident, "on", Ident, NL, INDENT, { WithEventBlock }, DEDENT ;
WithEventBlock = "with", TypeRef, NL, [ INDENT, { MappingLine }, DEDENT ] ;Note: AutoMap for join blocks is controlled at the projection or children level, not within individual with blocks.
Children Block
Section titled “Children Block”Define nested collections:
ChildrenBlock = "children", Ident, "identified", "by", Expr, NL, INDENT, [ "no", "automap", NL ], { ChildBlock }, DEDENT ;
ChildBlock = ChildEveryBlock | FromEventBlock | JoinBlock | RemoveWithBlock | RemoveWithJoinBlock | ChildrenBlock | NestedBlock | ClearWithBlock ;
ChildEveryBlock = "every", NL, INDENT, [ "no", "automap", NL ], { MappingLine }, DEDENT ;Note: ChildEveryBlock applies mappings to all events within the children collection. Unlike the top-level EveryBlock, it does not support the exclude children directive as it operates within a children context.
Nested Block
Section titled “Nested Block”Define a single nullable child object on the parent — unlike children, which manages a collection. A nested block must contain at least one from directive.
NestedBlock = "nested", Ident, NL, INDENT, [ "automap" | "no", "automap", NL ], { ProjDirective | Block | NestedBlock | ClearWithBlock }, DEDENT ;
ClearWithBlock = "clear", "with", TypeRef, NL ;A nested block may appear at the projection level, inside a children block, or inside another nested block. The clear with directive nulls the nested object when the given event occurs.
Removal Blocks
Section titled “Removal Blocks”Remove instances based on events:
RemoveWithBlock = "remove", "with", TypeRef, [ KeyInline ], NL, [ INDENT, [ ParentDecl ], DEDENT ] ;
RemoveWithJoinBlock = "remove", "via", "join", "on", TypeRef, [ KeyInline ], NL ;Key Declarations
Section titled “Key Declarations”Define instance keys:
KeyDecl = "key", Expr, NL ;
CompositeKeyDecl = "key", TypeRef, "{", NL, INDENT, KeyPart, { NL, KeyPart }, NL?, DEDENT, "}", NL ;
KeyPart = Ident, "=", Expr ;Mapping Lines
Section titled “Mapping Lines”Operations that modify the read model:
MappingLine = Assignment | IncLine | DecLine | CountLine | AddLine | SubLine ;
Assignment = Ident, "=", Expr, NL ;IncLine = "increment", Ident, NL ;DecLine = "decrement", Ident, NL ;CountLine = "count", Ident, NL ;AddLine = "add", Ident, "by", Expr, NL ;SubLine = "subtract", Ident, "by", Expr, NL ;Expressions
Section titled “Expressions”Values and references:
Expr = Template | Literal | DollarExpr | Path ;
DollarExpr = "$eventSourceId" | "$eventContext", ".", Ident ;
Path = Ident, { ".", Ident } ;
Template = "`", { TemplateChar | "${", Expr, "}" }, "`" ;
Literal = BoolLiteral | StringLiteral | NumberLiteral | NullLiteral ;Indentation Rules
Section titled “Indentation Rules”The grammar uses indentation to define structure:
- INDENT: Increase indentation by one level (typically 2 spaces)
- DEDENT: Decrease indentation by one level
- Consistent Spacing: All indentation must use spaces (no tabs)
- Block Structure: Each block’s content must be indented from its declaration
Example:
projection User => UserReadModel # Level 0 from UserCreated # Level 1 (INDENT) Name = name # Level 2 (INDENT) Email = email # Level 2 # (DEDENT, DEDENT)Validation Rules
Section titled “Validation Rules”Beyond the grammar, these semantic rules apply:
- Event types must exist or be valid identifiers
- Properties referenced must exist on events and read models
- Type compatibility between expressions and target properties
- Numeric operations only on numeric properties
childrenblocks must declareidentified byremove via joinrequires an available join key- Composite keys must contain at least one field
- Parent keys required in children’s from and remove blocks
nestedblocks must contain at least onefromdirective
Formatting Conventions
Section titled “Formatting Conventions”While not enforced by the grammar, these conventions improve readability:
- Indentation: 2 spaces per level
- Blank Lines: Between top-level blocks
- Alignment: Align
=signs when helpful - Ordering: Logical grouping of related mappings
Example Using Grammar
Section titled “Example Using Grammar”This example demonstrates how various grammar rules combine:
projection Order => OrderReadModel every LastUpdated = $eventContext.occurred exclude children
from OrderPlaced key orderId OrderNumber = orderNumber CustomerId = customerId Total = total Status = "Pending"
from OrderShipped Status = "Shipped" ShippedAt = $eventContext.occurred
join Customer on CustomerId events CustomerCreated, CustomerUpdated CustomerName = name
children items identified by lineNumber every UpdatedAt = $eventContext.occurred
from LineItemAdded key lineNumber parent orderId ProductId = productId Quantity = quantity UnitPrice = price
remove with LineItemRemoved key lineNumber parent orderId
remove with OrderCancelledThis projection uses:
- Projection declaration
- Every block with exclude children at the projection level
- Multiple from blocks with keys
- Join block with multiple events
- Children block with:
- Child every block for common mappings across all child events
- Nested from and remove blocks
- Projection-level removal
See Also
Section titled “See Also”- Expressions - Understanding expression syntax
- All other topic pages for specific features described in the grammar