(* =================================================================== Kite — complete grammar Version 0.1 draft, August 2026 Notation: A B sequence A | B alternation [ A ] optional { A } zero or more ( A ) grouping "a" terminal (* … *) comment NEWLINE is significant: it terminates a statement unless the preceding token is an operator, an open delimiter, or a comma. The lexer applies this rule; the grammar below treats statements as newline-terminated and omits NEWLINE for readability. Two refinements: `>` and `>>` also close type argument lists, so the lexer keeps the line break after them and the parser skips it after one it has read as a binary operator; and `return` is not an operator — `ReturnStmt` may end at the line break after it. A file may begin with a byte-order mark, which is not a token. =================================================================== *) (* ------------------------------------------------------------------ Compilation unit ------------------------------------------------------------------ *) SourceFile = { UseDecl } { Declaration } ; UseDecl = "use" ModulePath [ "as" Ident ] ; ModulePath = Ident { "/" Ident } ; Declaration = [ DeriveAttr ] [ "pub" ] ( FnDecl | StructDecl | EnumDecl | TraitDecl | ImplDecl | TypeAlias | ConstDecl ) | HostAttr [ "pub" ] ExternDecl ; (* the attribute first: `pub @host(…)` is not a declaration *) (* A module-level constant. `let` only: there is no module `var`, because a mutable binding every function in the module can reach is state none of their signatures mentions. The value must be one the compiler can work out — see SPECIFICATION.md §4.2. *) ConstDecl = "let" Ident [ ":" Type ] "=" Expr ; (* `@derive` and `@host` are the only two attributes Kite has, and each is admitted in exactly one place: `@derive` in front of a struct or an enum, `@host` in front of an `extern fn`. There is no general attribute syntax, because an attribute the compiler does not act on is a comment with a grammar. *) DeriveAttr = "@" "derive" "(" Ident { "," Ident } [ "," ] ")" ; (* ------------------------------------------------------------------ Functions ------------------------------------------------------------------ *) FnDecl = [ "async" ] "fn" Ident [ Generics ] "(" [ Params ] ")" [ "->" ReturnType ] Block ; Params = Param { "," Param } [ "," ] ; Param = [ "var" ] Ident ":" Type ; ReturnType = Type | "(" Type "," "error" ")" ; (* fallible result *) Generics = "<" GenericParam { "," GenericParam } ">" ; GenericParam = Ident [ ":" TraitBounds ] ; TraitBounds = TypePath { "+" TypePath } ; (* ------------------------------------------------------------------ Types ------------------------------------------------------------------ *) StructDecl = "struct" Ident [ Generics ] "{" { FieldDecl } "}" ; FieldDecl = [ "pub" ] [ "var" ] Ident ":" Type ; EnumDecl = "enum" Ident [ Generics ] "{" { VariantDecl } "}" ; VariantDecl = Ident [ "(" VariantPayload ")" ] ; VariantPayload = NamedFields | PositionalFields ; NamedFields = Ident ":" Type { "," Ident ":" Type } [ "," ] ; PositionalFields = Type { "," Type } [ "," ] ; TypeAlias = "type" Ident [ Generics ] "=" Type ; Type = "Option" "<" Type ">" (* optional *) | "[" Type "]" (* slice *) | "{" Type ":" Type "}" (* map *) | "(" Type { "," Type } ")" (* tuple *) | "fn" "(" [ TypeList ] ")" [ "->" Type ] | "dyn" TypePath (* trait object *) | TypePath ; TypePath = [ Ident "." ] Ident [ TypeArgs ] ; TypeArgs = "<" Type { "," Type } ">" ; (* a `>>` or `>=` right after one is split *) TypeList = Type { "," Type } [ "," ] ; (* ------------------------------------------------------------------ Traits and implementations ------------------------------------------------------------------ *) TraitDecl = "trait" Ident [ Generics ] "{" { TraitMember } "}" ; TraitMember = [ "pub" ] TraitFn ; TraitFn = [ "async" ] "fn" Ident [ Generics ] "(" [ SelfParam [ "," Params ] | Params ] ")" [ "->" ReturnType ] [ Block ] ; (* Block = default impl *) SelfParam = [ "var" ] "self" ; ImplDecl = "impl" [ Generics ] TypePath [ "for" TypePath ] "{" { ImplMember } "}" ; ImplMember = [ "pub" ] MethodDecl ; MethodDecl = [ "async" ] "fn" Ident [ Generics ] "(" [ SelfParam [ "," Params ] | Params ] ")" [ "->" ReturnType ] Block ; ExternDecl = "extern" "fn" Ident "(" [ Params ] ")" [ "->" Type ] ; HostAttr = "@" "host" "(" StringLit ")" ; (* ------------------------------------------------------------------ Statements ------------------------------------------------------------------ *) Block = "{" { Statement } "}" ; Statement = LetStmt | VarStmt | AssignStmt | DiscardStmt | CheckStmt | DeferStmt | ReturnStmt | BreakStmt | ContinueStmt | IfStmt | ForStmt | MatchStmt | ExprStmt ; LetStmt = "let" Binding [ ":" Type ] [ "=" Expr ] ; VarStmt = "var" Ident [ ":" Type ] "=" Expr ; Binding = Ident | "(" BindElem { "," BindElem } ")" ; (* destructuring *) BindElem = Ident | "_" ; AssignStmt = LValue AssignOp Expr ; LValue = ( Ident | "self" ) { "." Ident | "[" Expr "]" } ; AssignOp = "=" | "+=" | "-=" | "*=" | "/=" | "%=" ; (* `_ = f()` — the result is thrown away on purpose. Only `=`: a compound assignment would be reading the hole, and there is nothing there to read. It exists because a call whose type is `error` or `(T, error)` may not be left as a bare ExprStmt; see SPECIFICATION §7.3 R6. *) DiscardStmt = "_" "=" Expr ; CheckStmt = "check" Expr ; DeferStmt = "defer" Expr ; ReturnStmt = "return" [ ReturnValue ] ; ReturnValue = Expr | Expr "," Expr (* value, nil — success *) | "_" "," Expr ; (* _, err — failure *) BreakStmt = "break" [ Label ] ; ContinueStmt = "continue" [ Label ] ; Label = Ident ; IfStmt = "if" Expr Block { "else" "if" Expr Block } [ "else" Block ] ; ForStmt = [ Label ":" ] "for" ForHeader Block ; ForHeader = Binding "in" Expr (* iterate *) | Expr (* conditional *) | (* empty *) ; (* unconditional *) MatchStmt = "match" Expr "{" { MatchArm } "}" ; MatchArm = Pattern [ "if" Expr ] "=>" ( Expr | Block ) [ "," ] ; ExprStmt = Expr ; (* ------------------------------------------------------------------ Patterns ------------------------------------------------------------------ *) Pattern = OrPattern ; OrPattern = SinglePattern { "|" SinglePattern } ; SinglePattern = "_" (* wildcard *) | "nil" | [ "-" ] Literal | [ "-" ] Literal ( ".." | "..=" ) [ "-" ] Literal (* range *) | Ident (* binding *) | [ Ident "." ] TypePath [ "(" PatternArgs ")" ] (* variant: bare, `Enum.V`, or `module.Enum.V` *) | TypePath "{" FieldPatterns "}" (* struct *) | "(" Pattern { "," Pattern } ")" ; (* tuple *) PatternArgs = Pattern { "," Pattern } [ "," ] | Ident ":" Pattern { "," Ident ":" Pattern } [ "," ] ; FieldPatterns = FieldPattern { "," FieldPattern } [ "," ] [ ".." ] ; FieldPattern = Ident [ ":" Pattern ] ; (* ------------------------------------------------------------------ Expressions — precedence climbing, loosest to tightest ------------------------------------------------------------------ *) Expr = RangeExpr ; (* A range is an infix operator, and the loosest one there is, so `0..n + 1` is `0..(n + 1)` — which is how it reads. It is non-associative: `a..b..c` has no meaning to give. *) RangeExpr = OrExpr [ ( ".." | "..=" ) OrExpr ] ; (* non-assoc *) OrExpr = AndExpr { "||" AndExpr } ; AndExpr = CmpExpr { "&&" CmpExpr } ; CmpExpr = BitExpr [ CmpOp BitExpr ] ; (* non-assoc *) CmpOp = "==" | "!=" | "<" | "<=" | ">" | ">=" ; BitExpr = ShiftExpr { ( "&" | "^" | "|" ) ShiftExpr } ; ShiftExpr = AddExpr { ( "<<" | ">>" ) AddExpr } ; AddExpr = MulExpr { ( "+" | "-" ) MulExpr } ; MulExpr = CastExpr { ( "*" | "/" | "%" ) CastExpr } ; CastExpr = UnaryExpr { "as" CastTarget } ; (* `as` converts between `int` and `float` and nothing else, so what follows it is a name with no type arguments — and a `<` after it is a comparison: `f as int < n`. *) CastTarget = [ Ident "." ] Ident ; UnaryExpr = [ "-" | "!" | "await" ] PostfixExpr ; PostfixExpr = PrimaryExpr { Postfix } ; Postfix = "." ( Ident | DecInt ) (* field, tuple index: `t.0.1` is two *) | "(" [ Args ] ")" (* call *) | "[" Expr "]" (* index, or a range with both ends *) | "[" [ OrExpr ] ".." [ OrExpr ] "]" (* open-ended *) | "[" [ OrExpr ] "..=" OrExpr "]" ; (* Only an index may leave out a range's ends. A missing start is `0` and a missing end the largest `int`, which is what a window clamps to anyway (SPECIFICATION §5.4). *) Args = Expr { "," Expr } [ "," ] ; PrimaryExpr = Literal | Ident | "self" | "(" Expr ")" | TupleLit | SliceLit | MapLit | StructLit | Closure | IfExpr | MatchExpr ; TupleLit = "(" Expr "," [ Expr { "," Expr } ] ")" ; SliceLit = "[" [ Expr { "," Expr } [ "," ] ] "]" ; MapLit = "{" [ MapEntry { "," MapEntry } [ "," ] ] "}" ; MapEntry = Expr ":" Expr ; StructLit = TypePath "{" [ StructBody ] "}" ; StructBody = [ ".." Expr "," ] FieldInit { "," FieldInit } [ "," ] | ".." Expr [ "," ] ; FieldInit = Ident ":" Expr ; Closure = "|" [ ClosureParams ] "|" ( Block | [ "->" Type ] Expr ) ; ClosureParams = ClosureParam { "," ClosureParam } ; ClosureParam = Ident [ ":" Type ] ; IfExpr = "if" Expr Block "else" ( Block | IfExpr ) ; MatchExpr = "match" Expr "{" { MatchArm } "}" ; (* ------------------------------------------------------------------ Literals and lexical elements ------------------------------------------------------------------ *) (* One integer type and one float, so a numeric type suffix names nothing. It used to be parsed and thrown away, which made `300i8` a width the compiler never checked; it is E0004 now. A char literal still lexes, so it is written here — but there is no `char` type, and using one is E0200 saying exactly that. *) Literal = IntLit | FloatLit | StringLit | CharLit | "true" | "false" | "nil" ; (* A `_` separator sits between two digits, in every radix and every part of a literal: `1_000`, `0xFF_FF`, `1.5e1_0`, and not `1_`, `0x_F` or `1_.5`. A float literal too large to be a finite `float` is refused, as an `int` literal too large for 64 bits is. *) IntLit = DecInt | HexInt | OctInt | BinInt ; DecInt = Digit { [ "_" ] Digit } ; HexInt = "0x" HexDigit { [ "_" ] HexDigit } ; OctInt = "0o" OctDigit { [ "_" ] OctDigit } ; BinInt = "0b" BinDigit { [ "_" ] BinDigit } ; FloatLit = DecInt "." DecInt [ Exponent ] | DecInt Exponent ; Exponent = ( "e" | "E" ) [ "+" | "-" ] DecInt ; StringLit = SimpleString | MultiString ; SimpleString = '"' { StringElem } '"' ; MultiString = '"""' NEWLINE { AnyChar } '"""' ; (* dedented by the closing delimiter's indentation, holes or not; §2.4 *) StringElem = Char | Escape | Interpolation ; Interpolation = "\" "(" Expr ")" ; Escape = "\" ( "n" | "t" | "r" | "0" | "\" | '"' | "'" | "u" "{" HexDigit { HexDigit } "}" ) ; CharLit = "'" ( Char | Escape ) "'" ; (* lexes; no `char` type yet *) Ident = XIDStart { XIDContinue } ; (* Unicode; NFC-normalised *) Comment = LineComment | DocComment | ModuleDoc ; LineComment = "//" { AnyCharButNewline } ; DocComment = "///" { AnyCharButNewline } ; ModuleDoc = "//!" { AnyCharButNewline } ; (* the file's own text *) (* Note: `?` is not a token in Kite. There is no optional chaining, no coalescing operator, and no ternary — an `if` expression, which narrows optionals on the branch where they cannot be nil, does that work. *) (* ------------------------------------------------------------------ Reserved words — 27 ------------------------------------------------------------------ *) Keyword = "async" | "await" | "as" | "break" | "check" | "continue"| "defer" | "else" | "enum" | "false" | "fn" | "for" | "if" | "impl" | "in" | "let" | "match" | "nil" | "pub" | "return" | "self" | "struct" | "trait" | "true" | "type" | "use" | "var" ; (* Contextual, not reserved — usable as identifiers: dyn, error, Self `dyn` is only special before a type path in type position. `error` is a built-in type alias, shadowable by a user binding. `Self` is only bound inside trait and impl bodies. *) (* ------------------------------------------------------------------ Notes on parsing ------------------------------------------------------------------ *) (* 1. StructLit vs Block ambiguity. `if x { … }` — is `x { … }` a struct literal? Resolved as Go and Rust do: struct literals are not permitted in the condition position of if/for/match without parentheses. Write `if (Point{x: 1.0, y: 2.0}).is_origin() { … }`. 2. Closure vs bitwise-or. `|` opens a closure only in expression-start position. In infix position it is always bitwise or. The parser knows which by position, requiring no lookahead. 3. Range vs field access. `0..10` — the lexer emits ".." as one token, so `0.` is never scanned as a float when followed by a second dot. Maximal munch on "..=" as well. 4. Non-associative comparison. `a < b < c` is a parse error, not a silent bug. CmpExpr admits at most one CmpOp. 5. `await` binds as a prefix unary operator, tighter than any binary operator: `await f() + 1` is `(await f()) + 1`. 6. Return arity. `return a, b` is valid only when the function's declared return type is `(T, error)`. Enforced in the type checker, not here. *)