Branchline Grammar¶
Use this page for the exact, authoritative syntax. If you only need to learn the language, start with the Tour of Branchline.
The Branchline DSL syntax is defined using Extended Backus–Naur Form (EBNF). The canonical grammar is maintained in the repository at interpreter/src/jvmTest/resources/io/github/ehlyzov/branchline/ebnf.txt and is reproduced below for convenience.
──────────────────────────────────────────────────────────────────────────────
program ::= versionDecl? importDecl* topDecl* EOF ;
versionDecl ::= "#!branchline" VERSION ;
importDecl ::= **IMPORT** STRING ( **AS** name )? **;** ;
topDecl ::= transformDecl | funcDecl | typeDecl | sharedDecl | **;** ;
#---------------------------------------------------------------------------
# Lexical helpers
#---------------------------------------------------------------------------
# IDENTIFIER also includes backtick-escaped names (for example, `if`).
name ::= IDENTIFIER | softKeyword ;
softKeyword ::= **ABORT** | **APPEND** | **AS** | **BACKOFF**
| **buffer** | **CALL** | **enum** | **FOREACH**
| **FUNC** | **INIT** | **LET** | **MANY**
| **MODIFY** | **OPTIONS** | **OUTPUT** | **RETRY** | **RETURN**
| **SET** | **SHARED** | **SINGLE**
| **THROW** | **TIMES** | **TO**
| **TRANSFORM** | **TYPE** | **union** | **USING** ;
#---------------------------------------------------------------------------
# Transforms
#---------------------------------------------------------------------------
transformDecl ::= annotation* **TRANSFORM** name? transformSig?
transformOptions? block ;
annotation ::= **@** name ;
transformOptions ::= **OPTIONS** "{" optionEntry* "}" ;
transformSig ::= "(" transformParamList? ")" "->" typeExpr ;
transformParamList ::= transformParam ( "," transformParam )* ;
transformParam ::= name ":" typeExpr ;
optionEntry ::= optionMode | optionInput | optionOutput | optionShared ;
optionMode ::= "mode" ":" **buffer** ;
optionInput ::= "input" ":" optionAdapterBlock ;
optionOutput ::= "output" ":" optionAdapterBlock ;
optionShared ::= "shared" ":" "[" sharedItem ("," sharedItem)* "]" ;
sharedItem ::= name ( **SINGLE** | **MANY** ) ;
optionAdapterBlock ::= "{" "adapter" ":" adapterSpec "}" ;
adapterSpec ::= name ("(" argList? ")")? ;
#---------------------------------------------------------------------------
# Shared memory
#---------------------------------------------------------------------------
sharedDecl ::= **SHARED** name ( **SINGLE** | **MANY** )? **;** ;
#---------------------------------------------------------------------------
# Functions & Types
#---------------------------------------------------------------------------
funcDecl ::= **FUNC** name "(" paramList? ")" funcBody ;
paramList ::= name ( "," name )* ;
funcBody ::= "=" expression | block ;
typeDecl ::= **TYPE** name "=" typeExpr **;** ;
typeExpr ::= typeTerm ( "|" typeTerm )* ;
typeTerm ::= enumType | recordType | listType | setType
| placeholderType | simpleType ;
enumType ::= **enum** "{" enumVal ("," enumVal)* "}" ;
# "enum" is treated as a type literal only when followed by "{"
enumVal ::= name ;
recordType ::= "{" recordField ("," recordField)* "}" ;
recordField ::= name ["?"] ":" typeExpr ;
listType ::= "[" typeExpr "]" ;
setType ::= **set** "<" typeExpr ">" ;
placeholderType ::= "_" ["?"] ;
simpleType ::= **string** | **bytes** | **number** | **boolean** | **null**
| name ;
#---------------------------------------------------------------------------
# Blocks (braces **or** off-side INDENT / DEDENT tokens)
#---------------------------------------------------------------------------
block ::= "{" statement* "}" | INDENT statement+ DEDENT ;
statement ::= letStmt | ifStmt | forStmt | tryStmt | callStmt
| setStmt | appendStmt | modifyStmt | returnStmt
| sharedWrite | suspendStmt | abortStmt | throwStmt
| nestedOutput | expressionStmt | **;** ;
letStmt ::= **LET** name "=" expression **;** ;
ifStmt ::= **IF** expression block ( **ELSE** block )? ;
forStmt ::= ( **FOR EACH** | **FOR** ) name **IN** expression
( **WHERE** expression )? block ;
tryStmt ::= **TRY** expression **CATCH** "(" name ")" retrySpec? arrow expression ;
callStmt ::= optAwait **CALL** name "(" argList? ")" arrow name **;** ;
optAwait ::= **AWAIT**? ;
arrow ::= "->" | "=>" ;
retrySpec ::= **RETRY** NUMBER **TIMES** ( **BACKOFF** STRING )? ;
setStmt ::= **SET** setTarget "=" expression **;** ;
appendStmt ::= **APPEND** **TO** setTarget expression
( **INIT** expression )? **;** ;
modifyStmt ::= **MODIFY** modifyTarget modifyBlock **;** ;
returnStmt ::= **RETURN** expression? **;** ;
sharedWrite ::= name "[" expression? "]" "=" expression **;** ;
suspendStmt ::= **SUSPEND** expression **;** ;
abortStmt ::= 'ABORT' expression? ';' ;
throwStmt ::= 'THROW' expression? ';' ;
nestedOutput ::= **OUTPUT** expression ;
expressionStmt ::= expression **;** ;
setTarget ::= name pathSeg* ;
modifyTarget ::= name ("." name)* ;
modifyBlock ::= "{" modifyField (("," | ";") modifyField)* [(","|";")] "}" ;
modifyField ::= fieldKey ":" expression
| "[" expression "]" ":" expression ;
#---------------------------------------------------------------------------
# Expressions (standard precedence ladder + ?? coalesce)
#---------------------------------------------------------------------------
expression ::= assignment ;
assignment ::= coalesce ( "=" assignment )? ;
coalesce ::= logicOr ( "??" logicOr )* ;
logicOr ::= logicAnd ( "||" logicAnd )* ;
logicAnd ::= equality ( "&&" equality )* ;
equality ::= comparison ( ( "==" | "!=" ) comparison )* ;
comparison ::= term ( ( "<" | ">" | "<=" | ">=" ) term )* ;
term ::= factor ( ( "+" | "-" ) factor )* ;
factor ::= unary ( ( "*" | "/" | "%" ) unary )* ;
unary ::= ( "!" | "-" | **AWAIT** | **SUSPEND** ) unary
| primary ;
primary ::= literal | pathExpr | name | funCall | arrayLit
| objectLit | caseExpr | tryExpr | "(" expression ")" | lambdaExpr ;
tryExpr ::= **TRY** expression **CATCH** "(" name ")" retrySpec? arrow expression ;
caseExpr ::= **CASE** "{" caseWhen+ caseElse "}" ;
caseWhen ::= **WHEN** expression **THEN** expression ;
caseElse ::= **ELSE** expression ;
literal ::= NUMBER | STRING | **TRUE** | **FALSE** | **NULL** ;
funCall ::= name "(" argList? ")" ;
argList ::= expression ( "," expression )* ;
lambdaExpr ::= "(" paramList? ")" "->" expression ;
arrayLit ::= "[" ( expression ("," expression)* )? "]"
| "[" expression **FOR EACH** name **IN** expression
( **WHERE** expression )? "]" ;
objectLit ::= "{" fieldPair ("," fieldPair)* "}" ;
fieldPair ::= fieldKey ":" expression ;
fieldKey ::= name ["?"] | STRING ;
#---------------------------------------------------------------------------
# Paths ($, identifiers, wildcards, slices, filters)
#---------------------------------------------------------------------------
pathExpr ::= "$" pathSeg* |
**INPUT** pathSeg* |
name pathSeg* ;
pathSeg ::= "." "*"? # *.name or .*
| "." name
| "[" slice "]"
| "[" predicate "]" ;
slice ::= [ NUMBER ] ":" [ NUMBER ] ;
predicate ::= expression ;
#---------------------------------------------------------------------------
# Templates (payloads are expressions)
#---------------------------------------------------------------------------
──────────────────────────────────────────────────────────────────────────────
The grammar file above is exercised by tests to stay synchronized with the implementation.