Skip to content

Grammar ​

The consolidated EBNF grammar (spec/grammar.ebnf).

text
(* Linnet .linnet grammar sketch. Semantic restrictions are specified in prose. *)

source_file        = module_decl, { use_decl }, { item } ;

module_decl        = "module", module_path ;
module_path        = identifier, { ".", identifier } ;

use_decl           = "use", import_path, [ "::", import_selector ] ;
import_path        = ( "std" | "crate" | identifier ), { ".", identifier } ;
import_selector    = identifier | "{", import_name, { ",", import_name }, [ "," ], "}" ;
import_name        = identifier, [ "as", identifier ] ;

item               = [ "pub" ], ( const_decl
                                  | type_alias
                                  | struct_decl
                                  | enum_decl
                                  | fn_decl
                                  | op_decl
                                  | block_decl
                                  | entry_decl ) ;

const_decl         = "const", identifier, [ ":", type ], "=", expr ;
type_alias         = "type", identifier, [ generic_params ], "=", type ;

struct_decl        = "struct", identifier, [ generic_params ], "{", { field_decl }, "}" ;
field_decl         = identifier, ":", type ;

enum_decl          = "enum", identifier, [ generic_params ], "{", [ identifier, { ",", identifier }, [ "," ] ], "}" ;

fn_decl            = "fn", identifier, [ generic_params ], parameter_list,
                     [ "->", type ], [ where_clause ], block_body ;
op_decl            = "op", identifier, [ generic_params ], parameter_list,
                     "->", type, [ where_clause ], block_body ;
entry_decl         = "entry", identifier, [ generic_params ], parameter_list,
                     [ "->", type ], [ where_clause ], block_body ;

block_decl         = "block", identifier, [ generic_params ], [ where_clause ],
                     "{", { block_item }, "}" ;
block_item         = param_decl | buffer_decl | state_decl | sub_decl | item ;
param_decl         = "param", identifier, ":", type, [ "=", expr ] ;
buffer_decl        = "buffer", identifier, ":", type ;
state_decl         = "state", identifier, ":", type ;
sub_decl           = "sub", identifier, ":", type, [ "=", expr ] ;   (* `= none` for an optional block *)

generic_params     = "<", generic_param, { ",", generic_param }, [ "," ], ">" ;
generic_param      = [ "*" ], identifier, ":", generic_constraint, [ "=", type_or_expr ] ;
generic_constraint = "Dim" | "Shape" | "DType" | "Numeric" | "Integer" | "Float" ;

parameter_list     = "(", [ parameter, { ",", parameter }, [ "," ] ], ")" ;
parameter          = identifier, ":", type, [ "=", expr ] ;

where_clause       = "where", constraint, { ",", constraint }, [ "," ] ;
constraint         = expr, comparison_op, expr ;

block_body         = "{", { statement }, "}" ;
statement          = let_stmt
                   | var_stmt
                   | assign_stmt
                   | return_stmt
                   | static_for_stmt
                   | while_stmt ;

let_stmt           = "let", ( pattern, [ ":", type ], "=", expr | comprehension_lhs, "=", expr ) ;
var_stmt           = "var", identifier, [ ":", type ], "=", expr ;
assign_stmt        = identifier, "=", expr ;   (* a `var` local or a `state` member *)
return_stmt        = "return", [ expr ] ;
static_for_stmt    = "static", "for", pattern, "in", expr, [ "..", expr ], block_body ;
while_stmt         = "while", expr, block_body ;

pattern            = identifier
                   | "(", pattern, { ",", pattern }, [ "," ], ")"
                   | "some", "(", pattern, ")"
                   | "none" ;

comprehension_lhs  = identifier, "[", index_output, { ",", index_output }, "]" ;
index_output       = identifier | "*", identifier ;

expr               = if_expr | match_expr | logical_or ;
if_expr            = "if", expr, block_expr, "else", block_expr ;
block_expr         = "{", expr, "}" ;
match_expr         = "match", expr, "{", match_arm, { match_arm }, "}" ;
(* An arm pattern never starts with "(": arms have no separator, and a
   parenthesized pattern would read as a call on the previous arm's value. *)
match_arm          = arm_pattern, "=>", expr ;
arm_pattern        = identifier | "some", "(", pattern, ")" | "none" ;

logical_or         = logical_and, { "||", logical_and } ;
logical_and        = equality, { "&&", equality } ;
equality           = comparison, { ( "==" | "!=" ), comparison } ;
comparison         = bit_or, { ( "<" | "<=" | ">" | ">=" ), bit_or } ;
(* Bitwise on integers, elementwise logical on booleans; looser than "+",
   tighter than comparisons, so `(a == b) & (i < n)` needs its parentheses. *)
bit_or             = bit_xor, { "|", bit_xor } ;
bit_xor            = bit_and, { "^", bit_and } ;
bit_and            = additive, { "&", additive } ;
additive           = multiplicative, { ( "+" | "-" ), multiplicative } ;
multiplicative     = unary, { ( "*" | "/" | "%" ), unary } ;
unary              = [ "!" | "-" | "+" ], postfix ;

postfix            = primary, { call_suffix | index_suffix | member_suffix } ;
call_suffix        = [ generic_arguments ], "(", [ argument, { ",", argument }, [ "," ] ], ")" ;
generic_arguments  = "<", type_or_expr, { ",", type_or_expr }, [ "," ], ">" ;
argument           = [ identifier, "=" ], expr ;
member_suffix      = ".", identifier ;
index_suffix       = "[", index_component, { ",", index_component }, "]" ;
index_component    = expr | "..." | "*", identifier | slice_component ;
slice_component    = [ expr ], ":", [ expr ], [ ":", [ expr ] ] ;

primary            = literal
                   | identifier
                   | tuple_expr
                   | shape_literal
                   | reduction_expr
                   | "some", "(", expr, ")"
                   | "none"
                   | "(", expr, ")" ;

tuple_expr         = "(", expr, ",", [ expr, { ",", expr }, [ "," ] ], ")" ;
shape_literal      = "[", [ expr, { ",", expr }, [ "," ] ], "]" ;

reduction_expr     = reduction_kind, [ "<", scalar_type, ">" ],
                     "[", reduction_index, { ",", reduction_index }, "]", expr ;
(* Reduction names are contextual: they begin a reduction only when followed
   by "[" or by "<", scalar_type, ">", "[". Elsewhere they are identifiers. *)
reduction_kind     = "sum" | "prod" | "max" | "min" | "any" | "all" ;
reduction_index    = identifier ;

type               = type_primary, [ "?" ] ;
type_primary       = scalar_type
                   | tensor_type
                   | tuple_type
                   | array_type
                   | named_type ;

scalar_type        = "bool"
                   | "i8" | "i16" | "i32" | "i64"
                   | "u8" | "u16" | "u32" | "u64"
                   | "f16" | "bf16" | "f32" | "f64" ;

tensor_type        = "Tensor", "[", shape_spec, ";", type_atom, "]" ;
shape_spec         = [ shape_element, { ",", shape_element } ] ;
shape_element      = expr | "*", identifier ;

tuple_type         = "(", type, ",", [ type, { ",", type }, [ "," ] ], ")" ;
array_type         = "[", type, ";", expr, "]" ;
named_type         = module_path, [ generic_arguments ] ;
type_atom          = scalar_type | named_type ;
(* A generic argument is a type when it parses as one; otherwise it is an
   arithmetic expression. Comparison and logical operators must be
   parenthesized inside generic argument lists. *)
type_or_expr       = type | additive ;

literal            = integer_literal | float_literal | string_literal | "true" | "false" ;
comparison_op      = "==" | "!=" | "<" | "<=" | ">" | ">=" ;

identifier         = ? ASCII [A-Za-z_][A-Za-z0-9_]* ? ;
integer_literal    = ? integer token ? ;
float_literal      = ? floating token ? ;
string_literal     = ? UTF-8 quoted string token ? ;

Released under the MIT License.