Grammar notes
Meta: not a full BNF — the source of truth is pkg/dang/dang.peg. This page captures the user-visible regularities so people don't have to read PEG.
Source of truth
pkg/dang/dang.peg— Pigeon-PEG grammar; generatespkg/dang/dang.peg.go- the tree-sitter grammar at
treesitter/is derived from the same source
Top-level structure
The start rule is Dang, not Module. Import and Reassignment are siblings of Decl/Form, not members of Decl.
Dang := (Expr Sep)* Expr? # Sep = newline or comma
Expr := Import | Decl | Reassignment | Form
Import := 'import' Symbol
Reassignment := Term AssignOp Form
Decl := DocString? ( InterfaceDecl | UnionDecl | EnumDecl | ScalarDecl
| ObjectDecl | NewConstructorDecl | FieldDecl | DirectiveDecl )
Form := Return | TryCatch | Raise | Conditional | ForLoop
| Case | Break | Continue | DefaultExpr | TypeHint | Term
Term := UnaryExpr | IndexOrCall | SelectOrCall | Literal | List
| ObjectLiteral | Block | ParenForm | SymbolOrCall
Separators
- newlines and commas are interchangeable inside arg lists, lists, records
- top-level forms are separated by newlines
Expression form (precedence)
Meta: keep this in sync with Operators precedence table — duplicating it isn't ideal but it's the kind of thing readers expect on a grammar page.
Type syntax
Type dispatches to one of these; non-null is a suffix ! wrapping any inner type. See Types and nullability.
Type := NonNull | NamedType | ListType | ObjectType | TypeVariable
NonNull := Type '!'
NamedType := (NamedType '.')? UpperIdent # qualifier is itself a NamedType
ListType := '[' Type ']'
ObjectType := '{{' (ObjectTypeField Sep)* ObjectTypeField? '}}'
TypeVariable := [a-z] # single lowercase letter
Lexical
- identifiers:
[a-zA-Z_][a-zA-Z0-9_]*(lowercase-leading = value, uppercase-leading = type) - comments:
#to end of line - strings:
"..."(with escapes),"""..."""triple-quoted (docstrings reuse this)`...`backtick templates with${expr}interpolation;\${escapes a literal${. Longer backtick fences (```) nest. See Strings.%word{...}quoted/raw form
- numbers:
Intdecimal;Floatrequires a fraction (1.0) or exponent (scientific notation)
Reserved words
- keyword tokens (each
!WordChar-terminated):and,break,case,catch,continue,directive,else,enum,false,for,if,implements,import,interface,let,new,null,on,or,pub,raise,return,scalar,self,true,try,type,union - see Syntax
Notable productions
SelectOrCall:Term '.' (ObjectSelection | FieldId ArgValues? BlockArg?)— the field path; zero-arg fields auto-call. See Fields:pubandlet.BlockArg:'{' (BlockParams '=>')? Expr (Sep Expr)* '}'— trailing block attached to a call; params are optional. See Blocks.ObjectSelection:'{' ... '}'after a.— two forms: aFieldSelectionlist (user.{name, posts.{title}}), or a list ofInlineFragments for unions/interfaces. See Objects (type).FieldSelection:Id ArgValues? ('.' ObjectSelection)?— a field in a selection, optionally with args and a nested selection.InlineFragment:'...' 'on' Symbol ('{' FieldSelection* '}' | '!'?)— type-narrowing in a selection. See Interfaces and unions.