Skip to content

Grammar

Working direction

Consolidated V1 source grammar extracted from the accepted source-form pages. Parser snapshots should use this page as the grammar map and the linked topic pages as the source of semantic rules.

This page gives Catalyst's source grammar in EBNF-like notation: which token sequences form source syntax and how expressions group. It is a parser map only; focused source-form pages own name resolution, type checking, dispatch, ownership, cleanup, lowering, diagnostics, and runtime evaluation order.

When this page and a focused source-form page disagree, treat the focused page as canonical until the contradiction is fixed here.

The grammar is deliberately permissive. Many forms that this page accepts are rejected later by semantic validation: move operand shape, catch as err requiring a block, declaration guards being comptime, aggregate-suffix targets being types, and similar restrictions live on the focused pages. The reason is diagnostic quality — a structurally-valid AST lets the compiler explain why a construct is illegal and suggest a fix, where a tighter grammar would only report "failed to parse." Treat productions here as "what the parser accepts," not "what the language allows."

Notation

rule        = production
"token"     = literal source token
<token>     = named lexical token or trivia/gap class defined by a linked lexical page
name        = another grammar rule
a | b       = alternative
[ a ]       = optional
{ a }       = zero or more repetitions
( a )       = grouping
(* ... *)   = inline parser-relevant note attached to the preceding production

separator means a newline or ; accepted by the statement-list grammar. Regular comments are trivia between grammar tokens; declaration and module documentation comments are lexical tokens that participate in the metadata blocks described by Comments.

Lexical Tokens

identifier          = <non-keyword-identifier>
doc_comment         = <declaration-doc-comment>
module_doc_comment  = <module-doc-comment>

name_path           = identifier { "." identifier }  (* resolvable qualified name; see member_suffix for postfix "." on expressions *)
reserved_word       = declaration_word | type_word | control_flow_word | operator_word | primitive_value_word
declaration_word    = "fn" | "const" | "var" | "pub" | "priv" | "import" | "comptime"
type_word           = "struct" | "enum" | "error" | "contract" | "impl" | "dyn" | "opaque" | "as" | "is"
control_flow_word   = "if" | "else" | "for" | "in" | "while"
                    | "return" | "break" | "continue"
                    | "try" | "catch" | "defer"
                    | "move"
operator_word       = "and" | "or" | "not" | "orelse"
primitive_value_word
                    = "true" | "false" | "void" | "null" | "undefined"

literal             = integer_literal | float_literal | code_point_literal | string_literal
primitive_literal   = "true" | "false" | "void" | "null" | "undefined"

Numeric, string, code point, suffix, escape, and comment token rules: Literals, Comments. The reserved-word filter for <non-keyword-identifier> is owned by Keywords.

is is reserved for future runtime type or pattern tests; no V1 production consumes it. Self is a type-keyword, not an identifier; see Keywords.

Source Files

source_file         = { separator } [ module_metadata_block { separator } ] { file_item { separator } } eof
module_metadata_block
                    = module_doc_comment { metadata_gap module_doc_comment } [ metadata_gap ]
file_item           = import_decl | top_level_decl

top_level_decl      = metadata_block [ visibility ] decl_without_visibility
metadata_block      = { metadata_item [ metadata_gap ] }
metadata_item       = doc_comment | attribute
metadata_gap        = <metadata-gap>
attribute_block     = { attribute }
visibility          = "pub" | "priv"

import_decl         = "import" expression

decl_without_visibility
                    = const_decl
                    | function_decl
                    | impl_decl

const_decl          = "const" binding_pattern [ ":" type_expr ] [ "=" expression ] [ decl_guard ]
decl_guard          = "if" expression

<metadata-gap> is whitespace and regular-comment trivia that keeps adjacent metadata tokens in one metadata block; it excludes semicolon separators and blank source lines. The trailing [ metadata_gap ] on module_metadata_block allows non-separator trivia before the block-terminating blank line. Blank-line termination, module/declaration doc separation, and invalid doc targets are owned by Comments.

Initializer omission is semantic validation. Ordinary const declarations require an initializer unless finalized declaration metadata supplies an accepted value source.

Function Declarations

function_decl       = [ "comptime" ] "fn" identifier parameter_list [ return_type ] [ decl_guard ] [ function_body ]
parameter_list      = "(" [ parameter { "," parameter } [ "," ] ] ")"
parameter           = attribute_block [ "comptime" ] binding_pattern ":" type_expr [ "=" expression ]
return_type         = [ "comptime" ] type_expr
function_body       = block | "=>" expression

function_body is grammatically optional in every context; validation decides where a body is required or forbidden. In contract bodies, the presence of function_body distinguishes a default method from a required operation. Opaque static returns are accepted only where the focused function and contract-dispatch pages allow them. See Semantic Contracts and Contract Dispatch.

Declaration-position signatures may wrap inside incomplete syntax such as a parameter list or return type, but a body opener attaches only on the same logical line as the completed signature. Statement-position else/catch obey the same-line rule. See Blocks and Statements.

Implementation Declarations

impl_decl           = "impl" impl_endpoint "as" impl_endpoint [ decl_guard ] impl_body
impl_body           = "{" { separator | impl_member } "}"
impl_member         = metadata_block function_decl { separator }

impl_endpoint       = impl_endpoint_primary { member_suffix | impl_endpoint_parameter_list }
impl_endpoint_primary
                    = identifier
                    | "Self"
                    | "(" type_expr ")"
impl_endpoint_parameter_list
                    = "(" [ impl_endpoint_parameter { "," impl_endpoint_parameter } [ "," ] ] ")"
impl_endpoint_parameter
                    = impl_binder_parameter
                    | type_expr
impl_binder_parameter
                    = "comptime" identifier ":" type_expr

impl_endpoint_parameter_list is a type-postfix-shaped header, not an expression call: ordinary entries are type arguments, and comptime name: type_expr entries introduce inline implementation binders. See Implementation Declarations.

Type Constructors

struct_type         = metadata_block "struct" [ contract_entry_list ] struct_body
contract_type       = metadata_block "contract" [ type_argument_list ] contract_body
enum_type           = "enum" enum_body
error_set_type      = "error" error_set_body

contract_entry_list = "(" [ contract_entry { "," contract_entry } [ "," ] ] ")"
contract_entry      = [ "pub" ] type_expr
type_argument_list  = "(" [ type_expr { "," type_expr } [ "," ] ] ")"

struct_body         = "{" { separator | struct_member } "}"
struct_member       = metadata_block [ "priv" ] ( field_decl | function_decl ) { separator }
field_decl          = [ "const" ] identifier ":" type_expr [ "=" expression ]
                      [ decl_guard ]

contract_body       = "{" { separator | contract_member } "}"
contract_member     = metadata_block function_decl { separator }

enum_body           = "{" [ enum_case { "," enum_case } [ "," ] ] "}"
enum_case           = identifier

error_set_body      = "{" [ error_case { "," error_case } [ "," ] ] "}"
error_case          = identifier

Blocks and Statements

block               = "{" statement_list "}"
statement_list      = { separator } [ statement { separator statement } { separator } ]
                      (* commas are not interchangeable with separators *)

statement           = local_decl
                    | defer_stmt
                    | expression

local_decl          = attribute_block ( "var" | "const" ) binding_pattern [ ":" type_expr ] "=" expression
                      [ decl_guard ]
defer_stmt          = "defer" statement

Newlines and ; separate statements only at parser-acceptable boundaries.

Binding Patterns

binding_pattern     = identifier
                    | "_"
                    | struct_binding_pattern
                    | array_binding_pattern

struct_binding_pattern
                    = "{" [ struct_binding_entry { "," struct_binding_entry } [ "," ] ] "}"
struct_binding_entry
                    = attribute_block
                      ( identifier
                      | "&" identifier
                      | identifier "=" identifier
                      | identifier "=" "&" identifier
                      | "_" "=" identifier )

array_binding_pattern
                    = "[" [ array_binding_entry { "," array_binding_entry } [ "," ] ] "]"
array_binding_entry = identifier | "_"

Array binding patterns share bracket spelling with array literals; parser context disambiguates them.

Type Expressions

type_expr           = type_union
type_union          = type_intersection { "|" type_intersection }
type_intersection   = type_error_return { "&" type_error_return }
type_error_return   = type_prefix [ "!" [ type_error_slot ] ]
type_error_slot     = type_error_slot_primary { call_suffix | member_suffix }
type_error_slot_primary
                    = identifier
                    | "(" type_expr ")"

type_prefix         = "?" type_prefix
                    | "*" [ "const" ] pointer_pointee
                    | "[" "]" [ "const" ] type_prefix
                    | "[" expression "]" type_prefix
                    | type_postfix

pointer_pointee     = "opaque"
                    | "dyn" type_expr
                    | "impl" type_expr
                    | type_prefix

type_postfix        = type_primary { call_suffix | member_suffix }
type_primary        = identifier
                    | "Self"
                    | "(" type_expr ")"
                    | function_type
                    | struct_type
                    | contract_type
                    | enum_type
                    | error_set_type

The error slot after ! is an identifier chain with optional member/call suffixes or a parenthesized type_expr. Composed sets and anonymous error { ... } operands must be parenthesized: Module!(ReadError | ParseError), Module!(error { InvalidModule }). T! inference is body-only; bodyless function types require a complete error set. See Operators, Error Types.

function_type       = attribute_block "fn" function_type_parameter_list return_type
function_type_parameter_list
                    = "(" [ function_type_parameter { "," function_type_parameter } [ "," ] ] ")"
function_type_parameter
                    = [ identifier ":" ] type_expr

V1 rejects opaque static returns in bodyless function type expressions.

Expressions

Expression precedence is encoded from lowest to highest.

expression          = assignment_expr

assignment_expr     = fallback_expr [ "=" fallback_expr ]

fallback_expr       = or_expr { fallback_suffix }
fallback_suffix     = orelse_suffix
                    | catch_suffix
orelse_suffix       = "orelse" or_expr
catch_suffix        = "catch" ( "as" identifier block | or_expr )
or_expr             = and_expr { "or" and_expr }
and_expr            = equality_expr { "and" equality_expr }
equality_expr       = ordering_expr [ ( "==" | "!=" ) ordering_expr ]
ordering_expr       = range_expr [ ( "<" | "<=" | ">" | ">=" ) range_expr ]
range_expr          = additive_expr [ ( ".." | "..=" ) additive_expr ]
                      (* omitted-bound forms (..end, start.., ..) live only in index_selector *)
additive_expr       = multiplicative_expr { ( "+" | "-" ) multiplicative_expr }
multiplicative_expr = prefix_expr { ( "*" | "/" | "%" ) prefix_expr }

prefix_expr         = ( "&" | "try" | "not" | "-" ) prefix_expr
                    | "move" move_operand
                    | postfix_expr

postfix_expr        = primary_expr { call_suffix | index_suffix | member_suffix | deref_suffix | optional_unwrap_suffix | aggregate_suffix }
call_suffix         = "(" [ argument_list ] ")"
index_suffix        = "[" index_selector "]"
member_suffix       = "." identifier
deref_suffix        = ".*"
optional_unwrap_suffix
                    = ".?"
aggregate_suffix    = "{" [ aggregate_field_list ] "}"

argument_list       = expression { "," expression } [ "," ]
index_selector      = expression
                    | [ expression ] ( ".." | "..=" ) [ expression ]
aggregate_field_list
                    = aggregate_field { "," aggregate_field } [ "," ]
aggregate_field     = "." identifier "=" expression

primary_expr        = literal
                    | primitive_literal
                    | identifier
                    | "Self"
                    | "(" expression ")"
                    | block
                    | if_expr
                    | while_expr
                    | for_expr
                    | return_expr
                    | break_expr
                    | continue_expr
                    | comptime_expr
                    | leading_dot_expr
                    | array_literal
                    | contextual_aggregate
                    | struct_type
                    | contract_type
                    | enum_type
                    | error_set_type

array_literal       = "[" [ expression { "," expression } [ "," ] ] "]"
leading_dot_expr    = "." identifier
contextual_aggregate
                    = "." "{" [ aggregate_field_list ] "}"

In if, while, and for headers, a { that can start the required body terminates the header expression. Parenthesize a trailing aggregate construction: if (ReadyState{ .ok = true }) { ... }.

Control Flow Expressions

if_expr             = "if" if_condition block [ "else" ( if_expr | block ) ]
if_condition        = expression
                    | optional_binding_condition

optional_binding_condition
                    = "const" binding_pattern [ ":" type_expr ] [ "=" expression ]
                      (* "=" omittable only when binding_pattern is a bare identifier (same-name sugar) *)
                    | "var" binding_pattern [ ":" type_expr ] "=" expression

while_expr          = "while" while_condition block
while_condition     = expression
                    | "const" binding_pattern [ ":" type_expr ] "=" expression

for_expr            = "for" [ "var" ] loop_binding "in" expression block
loop_binding        = binding_pattern [ ":" type_expr ]

return_expr         = "return" [ expression ]
break_expr          = "break"
continue_expr       = "continue"
comptime_expr       = "comptime" expression

Move Places

move_operand        = postfix_expr

move parses broadly; V1 validation accepts only movable local places. See Move Expressions.

Attribute Syntax

attribute           = "@" attribute_name [ call_suffix ]
attribute_name      = name_path