Skip to content

Grammar

The full EBNF grammar of the Screenplay DSL. INDENT/DEDENT are synthesized by the lexer from changes in indentation (offside rule), as in Python. The PDL and CDL bodies are embedded sub-grammars — see Sub-language Pluggability.

(* ============================================================ *)
(* Screenplay DSL — Full EBNF *)
(* ============================================================ *)
Document = { Import }, { ConceptDecl }, { PolicyDecl }, { Module } ;
(* -------------------------------------------------------------- *)
(* Imports *)
(* -------------------------------------------------------------- *)
Import = "import", QualifiedName, NL ;
QualifiedName = Ident, { ".", Ident } ;
(* -------------------------------------------------------------- *)
(* Concepts *)
(* -------------------------------------------------------------- *)
ConceptDecl = "concept", Ident, ":", PrimitiveType, { Attribute }, NL
| "concept", Ident, ":", "Enum", NL,
INDENT, { Ident, NL }, DEDENT ;
PrimitiveType = "Uuid" | "String" | "Int" | "Decimal" | "Bool"
| "Date" | "DateTime" ;
Attribute = "@pii" | "@sensitive" ;
(* -------------------------------------------------------------- *)
(* Policies *)
(* -------------------------------------------------------------- *)
PolicyDecl = "policy", Ident, NL,
INDENT, PolicyBody, DEDENT ;
PolicyBody = PolicyExpr
| InlineBlock ;
PolicyExpr = "require", PolicyCondition, { ( "or" | "and" ), PolicyCondition } ;
PolicyCondition = "authenticated"
| "role", StringLiteral
| "claim", StringLiteral, "matches", ( "subject" | StringLiteral )
| "(", PolicyCondition, { ( "or" | "and" ), PolicyCondition }, ")" ;
(* -------------------------------------------------------------- *)
(* Module *)
(* -------------------------------------------------------------- *)
Module = "module", Ident, NL,
INDENT,
{ LayoutDecl },
{ Feature },
DEDENT ;
(* -------------------------------------------------------------- *)
(* Layouts *)
(* -------------------------------------------------------------- *)
LayoutDecl = "layout", Ident, NL,
INDENT,
"template", NL,
INDENT, { Ident, NL }, DEDENT,
DEDENT ;
(* -------------------------------------------------------------- *)
(* Features *)
(* -------------------------------------------------------------- *)
Feature = "feature", Ident, NL,
INDENT,
{ Feature },
{ SliceDecl },
DEDENT ;
(* -------------------------------------------------------------- *)
(* Slices *)
(* -------------------------------------------------------------- *)
SliceDecl = "slice", SliceType, Ident, NL,
INDENT, { SliceBody }, DEDENT ;
SliceType = "StateChange" | "StateView" | "Automation" | "Translate" ;
SliceBody = EventDecl
| CommandDecl
| QueryDecl
| ProjectionDecl
| CaptureDecl
| ReactorDecl
| ScreenDecl
| ConstraintDecl ;
(* -------------------------------------------------------------- *)
(* Events *)
(* -------------------------------------------------------------- *)
EventDecl = "event", Ident, NL,
INDENT, { PropertyLine }, DEDENT ;
PropertyLine = Ident, TypeRef, NL ;
TypeRef = Ident, [ "[]" ], [ "?" ] ;
(* -------------------------------------------------------------- *)
(* Commands *)
(* -------------------------------------------------------------- *)
CommandDecl = "command", Ident, NL,
INDENT,
{ PropertyLine },
[ AuthorizeDecl ],
{ ValidateDecl },
( { ProducesDecl } | HandlerDecl ),
DEDENT ;
AuthorizeDecl = "authorize", PolicyRef, { ( NL, PolicyRef ) | ( "or", PolicyRef ) }, NL ;
PolicyRef = Ident ;
ValidateDecl = "validate", NL,
INDENT, { ValidationRule }, DEDENT
| "validate", "csharp", NL, InlineBlock ;
ValidationRule = Ident, RuleOp, [ "message", StringLiteral ], NL ;
RuleOp = "not empty"
| "max", Number
| "min", Number
| ">", Value
| ">=", Value
| "<", Value
| "<=", Value
| "==", Value
| "length", "==", Number
| "matches", ( "email" | StringLiteral )
| "all", ">", Value
| "all", ">=", Value ;
Value = Number | StringLiteral | "today" | "true" | "false" ;
(* -------------------------------------------------------------- *)
(* Produces *)
(* -------------------------------------------------------------- *)
ProducesDecl = "produces", Ident, NL,
[ INDENT, { PropertyMapping }, DEDENT ]
| "produces", "when", Condition, NL,
INDENT, Ident, NL,
[ INDENT, { PropertyMapping }, DEDENT ],
DEDENT ;
Condition = ConditionExpr, { ( "and" | "or" ), ConditionExpr } ;
ConditionExpr = Ident, CompOp, Value
| Ident, CompOp, Ident
| "(" Condition ")" ;
CompOp = "==" | "!=" | ">" | ">=" | "<" | "<=" ;
PropertyMapping = Ident, "=", MappingSource, NL ;
MappingSource = Ident (* command property *)
| "$context.occurred"
| "$context.identity.id"
| "$env.", Ident
| StringLiteral
| Number
| "true" | "false"
| Expression ;
Expression = (* arithmetic / method-call expression — freeform *) ;
(* -------------------------------------------------------------- *)
(* Handler *)
(* -------------------------------------------------------------- *)
HandlerDecl = "handler", NL,
INDENT, ( FileDirective | InlineBlock ), DEDENT ;
(* -------------------------------------------------------------- *)
(* Queries *)
(* -------------------------------------------------------------- *)
QueryDecl = "query", Ident, "=>", TypeRef, NL,
[ INDENT,
[ ByClause ],
{ FilterClause },
[ AuthorizeDecl ],
DEDENT ] ;
ByClause = "by", Ident, TypeRef, NL ;
FilterClause = "filter", Ident, TypeRef, NL ;
(* -------------------------------------------------------------- *)
(* Projections — PDL sub-language *)
(* -------------------------------------------------------------- *)
ProjectionDecl = "projection", Ident, "=>", Ident, NL,
INDENT, PDLBody, DEDENT ;
PDLBody = (* Projection Declaration Language grammar —
see https://cratis.io/chronicle/projections/
projection-declaration-language/grammar/ *) ;
(* -------------------------------------------------------------- *)
(* Captures — CDL sub-language *)
(* -------------------------------------------------------------- *)
CaptureDecl = "capture", Ident, NL,
INDENT, CDLBody, DEDENT ;
CDLBody = (* Change Data Capture Language grammar *) ;
(* -------------------------------------------------------------- *)
(* Sub-language extension point *)
(* -------------------------------------------------------------- *)
(* Any registered keyword not listed above may appear as a
SliceBody construct. The parser delegates to the registered
sub-parser for the indented body. *)
ExtensionConstruct = Ident, Ident, NL,
[ INDENT, { AnyLine }, DEDENT ] ;
(* -------------------------------------------------------------- *)
(* Constraints *)
(* -------------------------------------------------------------- *)
ConstraintDecl = "constraint", Ident, NL,
INDENT, ConstraintBody, DEDENT ;
ConstraintBody = "unique", Ident, "on", Ident, NL (* unique property *)
| "unique", "event", Ident, NL (* unique event *)
| FileDirective ; (* custom C# *)
(* -------------------------------------------------------------- *)
(* Reactors *)
(* -------------------------------------------------------------- *)
ReactorDecl = "reactor", Ident, NL,
INDENT,
"on", Ident, NL,
( FileDirective | InlineBlock ),
DEDENT ;
(* -------------------------------------------------------------- *)
(* Screens *)
(* -------------------------------------------------------------- *)
ScreenDecl = "screen", Ident, NL,
INDENT, ScreenBody, DEDENT ;
ScreenBody = FileDirective (* full external file *)
| { ScreenDirective } ; (* declarative levels *)
ScreenDirective = DataDecl
| ActionDecl
| SectionDecl
| LayoutRef
| InlineBlock ;
DataDecl = "data", TypeRef, "via", "query", Ident,
[ "by", Ident ], NL ;
ActionDecl = "action", Ident, NL,
[ INDENT, { ActionOption }, DEDENT ] ;
ActionOption = NavigateDecl
| "label", StringLiteral, NL ;
NavigateDecl = "navigate", "to", Ident, [ "by", Ident ], NL ;
LayoutRef = "layout", Ident, NL,
INDENT, { SlotDecl }, DEDENT ;
SlotDecl = Ident, NL,
[ INDENT, { ScreenDirective }, DEDENT ] ;
SectionDecl = "section", Ident, NL,
INDENT, { ScreenDirective | WidgetDecl }, DEDENT
| "title", StringLiteral, NL ;
WidgetDecl = ( "table" | "summary" ) , ( TypeRef | Ident ), NL,
[ INDENT, { WidgetOption }, DEDENT ] ;
WidgetOption = "column", Ident, [ "label", StringLiteral ], NL
| "field", Ident, "label", StringLiteral, NL
| "on", "row-click", NavigateDecl ;
(* -------------------------------------------------------------- *)
(* Shared *)
(* -------------------------------------------------------------- *)
FileDirective = "file", FilePath, NL ;
FilePath = (* relative path string *) ;
InlineBlock = LanguageTag, NL, "```", NL, { AnyLine }, "```", NL ;
LanguageTag = "csharp" | "typescript" | "react" | "html" ;
StringLiteral = '"', { ? any char except '"' ? }, '"' ;
Number = [ "-" ], { "0".."9" }, [ ".", { "0".."9" } ] ;
Ident = Letter, { Letter | Digit | "_" } ;
Letter = "A".."Z" | "a".."z" ;
Digit = "0".."9" ;
NL = ? newline ? ;
INDENT = ? increase in indentation level ? ;
DEDENT = ? decrease in indentation level ? ;
AnyLine = ? any text until newline ? ;