Skip to content

Grammar Reference (EBNF)

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

SymbolMeaning
=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
PascalCaseNon-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 ] ;
TypeParameters = "<" TypeParameter { "," TypeParameter } ">" ;
TypeParameter = Identifier [ ":" Type ] ;
Decorator = "@" QualifiedName [ "(" [ Arguments ] ")" ] ;
EffectList = "![" EffectIdentifier { "," EffectIdentifier } "]" ;
EffectIdentifier = Identifier ;

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
| LoopStatement
| BreakStatement
| ContinueStatement
| MatchStatement
| TryStatement
| RoutineDeclaration
| 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 ] ;
RoutineDeclaration = "routine" [ Identifier | StringLiteral ] Block ;
ReturnStatement = "return" [ Expression ] ";" ;
ThrowStatement = "throw" Expression ";" ;
WithStatement = "with" Expression { "," Expression } Block ;
ForStatement = "for" Expression "in" Expression Block [ ";" ] ;
LoopStatement = "loop" Block [ ";" ] ;
BreakStatement = "break" [ ";" ] ;
ContinueStatement = "continue" [ ";" ] ;
AssignmentStatement = Assignment ";" ;
Assignment = Expression ( "=" | "+=" | "-=" | "*=" | "/=" ) Expression ;
ExpressionStatement = Expression ";" ;

Note: RoutineDeclaration (routine { }, routine "name" { }) appears in example code today, but full runtime support (scheduling, joins) is planned for 1.0. See the roadmap.

LevelOperatorsNotes
1 (highest)! - + (unary), &, &mut, * (deref), awaitRight-associative
2* /Left-associative
3+ -Left-associative
4< <= > >=Left-associative
5== != isLeft-associative
6&&Left-associative
7||Left-associative
8|> (planned)Left-associative
9 (lowest)= += -= *= /=Right-associative
Expression = LambdaExpression | PipelineExpression ;
LambdaExpression = "fn" "(" [ Parameters ] ")" [ ReturnType ] Block ;
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") Unary
| "&" [ "mut" ] Unary
| "&" "raw" Unary
| "borrow" "(" Expression ")"
| "*" Unary
| Postfix ;
Postfix = Primary { PostfixOp } [ "as" Type ] ;
PostfixOp = "(" [ Arguments ] ")"
| "." Identifier
| "[" Expression "]" ;
Argument = [ Identifier ":" ] Expression ;
Arguments = Argument { "," Argument } ;
FormMeaningSafety
&xCreate shared borrowSafe
&mut xCreate exclusive mutable borrowSafe
&raw xCreate raw pointer from valueunsafe block only
*ptrDereference raw pointerunsafe block only
borrow(x)Explicit shared borrowSafe

The as operator performs type casts. Inside unsafe blocks it can cast 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 } ;
SyntaxDescription
number64-bit float (primary numeric type)
stringUTF-8 string
booleanBoolean
voidNo return value
nullExplicit absence of value
T?Optional (T or null)
A | BUnion type
T[]Array / growable collection
&TShared borrow (read-only) — parsed, not enforced (roadmap)
&mut TExclusive mutable borrow — parsed, not enforced (roadmap)
*TRaw read-only pointer (unsafe only)
*mut TRaw mutable pointer (unsafe only)
*opaqueOpaque foreign pointer (void*)

Sized numeric types for FFI / low-level code: i8i64, u8u64, f32, f64, usize. 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 await 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 routine scope with true false null assert

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