Skip to content

Grammar Reference (EBNF)

The Sailfin grammar is specified in Extended Backus–Naur Form (EBNF) below. This page is the authoritative form.

Symbol Meaning
= Defines a non-terminal
| Alternation (either/or)
[ x ] Optional — zero or one occurrence of x
{ x } Repetition — zero or more occurrences of x
( x ) Grouping
"terminal" Literal terminal token
PascalCase Non-terminal reference
; End of production rule
Program = { ImportDeclaration | ExportDeclaration | Declaration | Statement } ;
ImportDeclaration = "import" ImportSpecifierList "from" StringLiteral ";" ;
ExportDeclaration = "export" ImportSpecifierList [ "from" StringLiteral ] ";" ;
ImportSpecifierList = "{" [ ImportSpecifier { "," ImportSpecifier } ] "}" ;
ImportSpecifier = Identifier [ "as" Identifier ] ;

A Sailfin source file is a flat sequence of imports, exports, declarations, and top-level statements. There is no required ordering between them.

Declaration = StructDeclaration
| EnumDeclaration
| InterfaceDeclaration
| FunctionDeclaration
| TestDeclaration
| TypeAliasDeclaration
| VariableDeclaration ;
StructDeclaration = "struct" Identifier [ TypeParameters ]
[ "implements" NominalType { "," NominalType } ]
"{" { StructMember } "}" ;
StructMember = FieldDeclaration | MethodDeclaration ;
TypeAnnotation = ":" Type ;
ReturnType = "->" Type ;
FieldDeclaration = [ "mut" ] Identifier TypeAnnotation ";" ;
MethodDeclaration = { Decorator } [ "async" ] "fn" Identifier [ TypeParameters ]
"(" [ Parameters ] ")" [ ReturnType ] [ EffectList ] Block ;
InterfaceDeclaration = "interface" Identifier [ TypeParameters ]
"{" { FunctionSignature | PropertySignature } "}" ;
PropertySignature = Identifier TypeAnnotation ";" ;
FunctionSignature = "fn" Identifier "(" [ Parameters ] ")"
[ ReturnType ] [ EffectList ] ";" ;

Variables, parameters, and struct fields use : to introduce the type. Function return types use ->. The parser’s TypeSep production still accepts -> in annotation positions for backward compatibility with early code, but all new code should use : for annotations and -> only for return types. Methods are declared inside the struct body; the first parameter is bare self (no &self or &mut self). There are no impl Foo { } blocks — conformance is declared with implements on the struct. Multiple interfaces: implements A, B, C.

FunctionDeclaration = { Decorator } { FunctionModifier }
"fn" Identifier [ TypeParameters ]
"(" [ Parameters ] ")"
[ ReturnType ] [ EffectList ] ( Block | ";" ) ;
FunctionModifier = "async" | "unsafe" | "extern" ;

When both unsafe and extern modifiers are present, the declaration introduces a foreign C symbol and terminates with ; instead of a block body:

unsafe extern fn malloc(size: usize) -> *u8;
unsafe extern fn free(ptr: *u8) -> void;
Parameters = Parameter { "," Parameter } ;
Parameter = [ "mut" ] Identifier [ TypeAnnotation ] [ "=" Expression ] ;
ExternParameters = Parameter { "," Parameter } [ "," "..." ] ;
TypeParameters = "<" TypeParameter { "," TypeParameter } ">" ;
TypeParameter = Identifier [ ":" Type ] ;
Decorator = "@" QualifiedName [ "(" [ Arguments ] ")" ] ;
EffectList = "![" EffectIdentifier { "," EffectIdentifier } "]" ;
EffectIdentifier = Identifier ;

A declaration carrying the extern modifier uses ExternParameters in place of Parameters. The trailing ... marks a C variadic; it is admissible only there, only last, and only after at least one fixed parameter. Any other placement is E0851, and a ... in an ordinary function’s parameter list is a parse error. See §13 Foreign Interface.

Canonical effect identifiers: io, net, model, gpu, rand, clock, plus unsafe, read, mut.

The effect list attaches after the parameter list and optional return type, before the function body.

TypeAliasDeclaration = "type" Identifier [ TypeParameters ] "=" Type ";" ;
VariableDeclaration = "let" [ "mut" ] Identifier [ TypeAnnotation ]
[ "=" Expression ] ";" ;
TestDeclaration = "test" ( Identifier | StringLiteral ) [ EffectList ] Block ;

Note: model, prompt, tool, and pipeline block declarations were removed from the grammar. AI functionality is delivered through the post-1.0 sfn/ai library capsule; the ![model] effect remains as the capability gate.

Block = "{" { Statement } "}" ;
Statement = VariableDeclaration
| IfStatement
| ForStatement
| WhileStatement
| LoopStatement
| BreakStatement
| ContinueStatement
| MatchStatement
| TryStatement
| RoutineStatement
| ReturnStatement
| ThrowStatement
| WithStatement
| AssertStatement
| UnsafeBlock
| Block
| AssignmentStatement
| ExpressionStatement ;
UnsafeBlock = "unsafe" Block ;
AssertStatement = "assert" Expression ";" ;
IfStatement = "if" Expression Block [ "else" ( IfStatement | Block ) ] ;
MatchStatement = "match" Expression "{" { MatchCase [ "," ] } "}" ;
MatchCase = Pattern [ "if" Expression ] "=>"
( Block | InlineExpression ) ;
InlineExpression = Expression [ ";" ] ;
TryStatement = "try" Block [ "catch" [ "(" Identifier ")" ] Block ]
[ "finally" Block ] ;
RoutineStatement = "routine" Block [ ";" ] ;
ReturnStatement = "return" [ Expression ] ";" ;
ThrowStatement = "throw" Expression ";" ;
WithStatement = "with" Expression { "," Expression } Block ;
ForStatement = "for" Expression "in" Expression Block [ ";" ] ;
WhileStatement = "while" Expression Block [ ";" ] ;
LoopStatement = "loop" Block [ ";" ] ;
BreakStatement = "break" [ ";" ] ;
ContinueStatement = "continue" [ ";" ] ;
AssignmentStatement = Assignment ";" ;
Assignment = Expression ( "=" | "+=" | "-=" | "*=" | "/=" ) Expression ;
ExpressionStatement = Expression ";" ;

Note: routine { } is the shipped v0 nursery boundary. Exiting the block joins all tasks spawned within it. The earlier named form routine "name" { } is not supported.

Level Operators Notes
1 (highest) ! - + (unary), &, &mut, * (deref), spawn, await Right-associative
2 * / Left-associative
3 + - Left-associative
4 < <= > >= Left-associative
5 == != is Left-associative
6 && Left-associative
7 || Left-associative
8 |> (planned) Left-associative
9 (lowest) = += -= *= /= Right-associative
Expression = LambdaExpression | PipelineExpression ;
LambdaExpression = "fn" "(" [ Parameters ] ")" [ ReturnType ] LambdaBody ;
LambdaBody = Block | "=>" Expression ;
// The `=> Expression` short form (SFEP-0029) is additive: it desugars to a
// single-`return` block, so `fn(x) => x * x` is equivalent to
// `fn(x) { return x * x; }`. The `fn` lead-in keeps dispatch zero-lookahead,
// so the body `=>` never collides with the match-arm separator.
PipelineExpression = LogicalOr { "|>" LogicalOr } ;
// Note: |> is not yet implemented.
LogicalOr = LogicalAnd { "||" LogicalAnd } ;
LogicalAnd = Equality { "&&" Equality } ;
Equality = Comparison { (("==" | "!=") Comparison) | ("is" Type) } ;
Comparison = Term { ("<" | "<=" | ">" | ">=") Term } ;
Term = Factor { ("+" | "-") Factor } ;
Factor = Unary { ("*" | "/") Unary } ;
Unary = ("!" | "-" | "+" | "await" | "spawn") Unary
| "&" [ "mut" ] Unary
| "&" "raw" Unary
| "borrow" "(" Expression ")"
| "*" Unary
| Postfix ;
Postfix = Primary { PostfixOp } [ "as" Type ] ;
PostfixOp = "(" [ Arguments ] ")"
| "." Identifier
| "[" Expression "]" ;
Argument = [ Identifier ":" ] Expression ;
Arguments = Argument { "," Argument } ;
Form Meaning Safety
&x Create shared borrow Safe
&mut x Create exclusive mutable borrow Safe
*ptr Dereference raw pointer (load) No block required
*ptr = v Store through a raw pointer No block required
ptr + n Element-scaled pointer arithmetic No block required
borrow(x) Explicit shared borrow Safe

&raw x is not implemented: it does not typecheck (E0818). Use s as *S for a struct’s address. No raw-pointer operation requires an unsafe { } block — see §13 Foreign Interface.

The as operator performs type casts, including between pointer types (ptr as *i32).

Primary = NumberLiteral
| StringLiteral
| "true" | "false" | "null"
| Identifier
| "(" Expression ")"
| ArrayLiteral
| ObjectLiteral
| StructLiteral ;
ArrayLiteral = "[" [ Expression { "," Expression } ] "]" ;
ObjectLiteral = "{" [ ObjectField { "," ObjectField } ] "}" ;
ObjectField = Identifier ":" Expression ;
StructLiteral = Identifier "{" [ StructField { "," StructField } ] "}" ;
StructField = Identifier ":" Expression ;
Type = UnionType ;
UnionType = OptionalType { "|" OptionalType } ;
OptionalType = SimpleType [ "?" ] ;
SimpleType = ReferencePrefix PointerType ;
ReferencePrefix = { "&" [ "mut" ] } ;
PointerType = { "*" [ "mut" ] } BaseType ;
BaseType = QualifiedName [ "<" Type { "," Type } ">" ]
| "opaque" ;
NominalType = SimpleType ;
QualifiedName = Identifier { "." Identifier } ;
Syntax Description
number 64-bit float (primary numeric type)
string UTF-8 string
boolean Boolean
void No return value
null Explicit absence of value
T? Optional (T or null)
A | B Union type
T[] Array / growable collection
&T Shared borrow (read-only) — parsed, not enforced (roadmap)
&mut T Exclusive mutable borrow — parsed, not enforced (roadmap)
*T Raw pointer — reads and writes are permitted
*const T Accepted spelling; read-only is not enforced (SFEP-0079 §3.2)
*mut T Accepted spelling, identical to *T
*void Untyped foreign pointer (void*). *opaque is rejected (E0805)

Sized numeric types for FFI / low-level code: i8i64, u8u64, f16, bf16, f32, f64, usize, isize. The split of number into int (i64) / float (f64) is on the roadmap.

Context-sensitivity of &: In type position A & B is type intersection; in expression position &x is a borrow. The grammar resolves this unambiguously.

Pattern = "_"
| LiteralPattern
| IdentifierPattern
| ConstructorPattern ;
LiteralPattern = NumberLiteral | StringLiteral | BooleanLiteral ;
IdentifierPattern = Identifier ;
ConstructorPattern = Identifier "{" [ PatternField { "," PatternField } ] "}" ;
PatternField = Identifier [ ":" Pattern ] ;

Guards attach to any match case: Pattern [ "if" Expression ] "=>" ...

NumberLiteral = Digit { Digit } [ "." Digit { Digit } ] ;
StringLiteral = '"' { Character | Escape | Interpolation } '"' ;
Interpolation = "${" Expression "}" ;
BooleanLiteral = "true" | "false" ;
Identifier = Letter { Letter | Digit | '_' } ;

String literals support ${ expression } interpolation. Whitespace inside the braces is ignored — ${name} and ${ name } are equivalent.

Argument = [ Identifier ":" ] Expression ;

Named arguments are supported in all call positions:

chunk(by: "semantic", target_tokens: 512)
http.post(url, body: json_payload)

They do not affect overload resolution and exist for readability.

The following identifiers are reserved and may not appear where a plain Identifier is expected:

fn async extern let mut unsafe struct enum interface type import export if else for in while loop match return break continue try catch finally throw model tool pipeline test prompt system user assistant with true false null assert

routine, spawn, and await are contextual keywords: the parser recognizes them only in their statement or prefix-expression positions. scope is an ordinary identifier; the earlier scope { ... } concurrency design was superseded by the shipped routine { ... } nursery.

The array syntax is Type[] (e.g. number[], string[]). Arrays expose .length and .push(value).