# Syntax Structure

This document describes the currently implemented Masterbelt syntax structure.

Lexical tokens are defined in [lexical.md](lexical.md).

The syntax structure is intentionally minimal at this stage. Future grammar additions must extend this document before or together with implementation changes.

## Source Files

A source file is a sequence of declarations and statements.

```ebnf
source_file = { declaration | statement } ;
declaration = { doc_comment } ( visible_declaration | use_declaration | reexport_declaration ) ;
statement   = { doc_comment } expression_statement ;
visible_declaration = [ visibility_modifier ] ( const_declaration | type_declaration | enum_declaration | function_declaration | master_declaration ) ;
visibility_modifier = "pub" ;
```

An empty source file is valid.

Declarations may appear in any order. `use` and re-export declarations are not required to precede other declarations.

Whitespace, line comments, and block comments are ignored between tokens as defined by the lexical structure.

Documentation comments are not ignored tokens. They are valid only as declaration prefixes, statement prefixes, or item prefixes inside grouped const declarations.

## Declarations

The implemented declaration forms are const declarations, type declarations, enum declarations, function declarations, master declarations, `use` declarations, and re-export declarations.

```ebnf
const_declaration      = "const" ( const_item | const_group ) ;
const_group            = "(" const_group_item { const_group_item } ")" ;
const_group_item       = { doc_comment } const_item ;
const_item             = identifier [ ":" type_expression ] "=" expression ;
type_declaration       = "type" identifier [ type_parameters ] "=" type_expression ;
type_parameters        = "<" type_parameter { "," type_parameter } [ "," ] ">" ;
type_parameter         = identifier ;
enum_declaration       = "enum" identifier [ ":" type_expression ] "{" [ enum_variants ] "}" ;
enum_variants          = enum_variant { "," enum_variant } [ "," ] ;
enum_variant           = { doc_comment } identifier [ "=" integer_literal ] ;
function_declaration   = { effect_modifier } "fn" identifier "(" [ function_parameters ] ")" [ ":" type_expression ] function_body ;
function_body          = function_block | function_arrow ;
function_block         = "{" { statement } "}" ;
function_arrow         = "=>" expression ;
master_declaration     = "master" identifier "{" { master_section } "}" ;
master_section         = master_record_section | master_source_section | master_filter_section
                       | master_validation_section | master_static_section | master_select_section
                       | master_scope_declaration | master_declaration ;
master_record_section  = "record" product_type ;
master_source_section  = "source" "{" { master_source_entry } "}" ;
master_source_entry    = master_source_kind string_literal [ master_source_options ] ;
master_source_kind     = identifier ;
master_source_options  = "{" [ master_source_option { "," master_source_option } [ "," ] ] "}" ;
master_source_option   = identifier ":" expression ;
master_filter_section  = "filter" "{" { master_filter_rule } "}" ;
master_filter_rule     = ( "include" | "exclude" ) string_literal function_block ;
master_validation_section = "validation" "{" { master_validation_group } "}" ;
master_validation_group   = ( "each" | "all" ) "{" { master_validation_rule } "}" ;
master_validation_rule    = "validate" identifier function_block ;
master_static_section  = "static" "{" { master_static_member } "}" ;
master_static_member   = { doc_comment } [ visibility_modifier ] ( const_declaration | function_declaration ) ;
master_select_section  = "select" identifier "{" [ master_select_field { "," master_select_field } [ "," ] ] "}" ;
master_select_field    = identifier ":" identifier ;
master_scope_declaration = [ visibility_modifier ] [ "indexed" ] "scope" identifier
                         "(" [ function_parameters ] ")" ( function_block | scope_expression_body ) ;
scope_expression_body  = "=>" expression ;
```

A function declaration introduces a callable into the value binding space. The optional return type clause may be omitted when the body permits inference; parameter types may also be omitted when a surrounding context supplies them. Both forms are validated in [types.md](types.md).

Statements inside a function block are either expression statements (evaluated for side effects) or return statements (which deliver the function's result and may carry an optional value).

The optional visibility modifier makes the declaration visible outside its file. It is a declaration modifier, not a const-specific keyword.

Documentation comments on a declaration must appear before the visibility modifier, if any.

The const item type annotation may be omitted. In that case, the const type is inferred from the initializer expression.

A const group must contain at least one item.

Grouped const declarations share the outer visibility modifier. Documentation comments inside the group attach to the following const item.

## Use and Re-export Declarations

```ebnf
use_declaration      = "use" import_specifier "from" string_literal ;
reexport_declaration = "pub" import_specifier "from" string_literal ;

import_specifier = named_import_list | star_import ;
named_import_list = "{" [ named_import { "," named_import } [ "," ] ] "}" ;
named_import     = identifier [ "as" identifier ] ;
star_import      = "*" ;
```

A `use` declaration brings the named symbols, or every public symbol when written with `*`, from the module identified by its source string into the current file's scope. A `use` declaration does not make the imported names visible outside the current file.

A re-export declaration brings the same set of symbols into the current file's scope and additionally declares each imported name as a public symbol of the current file. A re-export is equivalent to a `use` followed by re-declaring each imported symbol with the public modifier; tooling may collapse this pair into one declaration.

The `pub` modifier in a re-export declaration is the same keyword used for `pub const` and `pub type`. The token immediately following `pub` distinguishes the form: a `{` or `*` starts an import specifier, while `const` or `type` starts a visible declaration.

The optional `as` clause renames an imported symbol within the current file. A renamed `use { Source as Local }` makes `Local` the file-scope identifier and `Source` the foreign module's identifier.

The source string of a `use` or re-export declaration follows the module path rules defined in [modules.md](modules.md).

Documentation comments may appear before a `use` or re-export declaration but do not attach to individual imported names.

## Type Expressions

A type expression appears as a const item type annotation or as the right-hand side of a type declaration.

```ebnf
type_expression          = union_type | primary_type ;
union_type               = primary_type "|" primary_type { "|" primary_type } ;
primary_type             = named_type | generic_type | product_type | function_type ;
named_type               = identifier | reserved_type_identifier ;
generic_type             = identifier "<" generic_arguments ">" ;
generic_arguments        = type_expression { "," type_expression } ;
product_type             = "{" [ product_type_fields ] "}" ;
product_type_fields      = product_type_field { "," product_type_field } [ "," ] ;
product_type_field       = [ field_modifier ] identifier ":" type_expression ;
field_modifier           = "readonly" | "writable" | "primary" ;
function_type            = { effect_modifier } "fn" "(" [ function_parameters ] ")" ":" type_expression ;
function_parameters      = function_parameter { "," function_parameter } [ "," ] ;
function_parameter       = [ "*" ] identifier ":" type_expression ;
effect_modifier          = "asyncable" | "failable" | "cancellable" ;
reserved_type_identifier = "null" ;
```

A type expression at a single position is one type expression. Parser implementations must produce one type expression node per type expression position, even when the surface form is a single name.

The `|` operator left-associates. A surface form `A | B | C` is one flat union with members `A`, `B`, and `C`.

## Generic Type Declarations

A type declaration may carry a list of type parameters. Type parameters are declaration-level: they belong to the type declaration itself and any type expression on the right-hand side may reference them. There is no special form for "generic product" or "generic union" — a parameter introduced on the declaration is visible to every nested type expression regardless of shape.

```mst
type Box<T> = { value: T }
type Pair<A, B> = { first: A, second: B }
type Maybe<T> = T | null
type Items<T> = list<T>
```

Type parameter names are identifiers in the same identifier name space as the alias itself; inside the alias body they shadow any outer type binding of the same name. Type parameters are not in scope outside the declaration that introduces them.

A type declaration declared with N type parameters must be applied with exactly N type arguments at every use site, written with the same `name<arg, ...>` syntax used for built-in generic types. A bare reference to a generic alias without arguments is a use-site error.

A type declaration declared without a parameter list is non-generic and cannot be applied with type arguments.

## Statements

The implemented statement forms are expression statements, return statements, if statements, match statements, for statements, break statements, continue statements, fail statements, assert statements, local const/let bindings, and assignments.

```ebnf
statement              = expression_statement | return_statement | if_statement | match_statement
                       | for_statement | break_statement | continue_statement | fail_statement
                       | assert_statement | local_const_statement | let_statement | assignment_statement ;
expression_statement   = expression ;
return_statement       = "return" [ expression ] ;
if_statement           = "if" expression function_block [ "else" ( function_block | if_statement ) ] ;
match_statement        = "match" expression "{" { match_arm } "}" ;
for_statement          = "for" for_binding [ "," for_binding ] "in" expression function_block ;
for_binding            = identifier | "_" ;
break_statement        = "break" ;
continue_statement     = "continue" ;
fail_statement         = "fail" expression ;
assert_statement       = "assert" expression ;
local_const_statement  = "const" identifier [ ":" type_expression ] "=" expression ;
let_statement          = "let"   identifier [ ":" type_expression ] "=" expression ;
assignment_statement   = identifier "=" expression ;
```

At this stage, statements have no explicit terminator. Return statements are valid only inside a function body; the type checker reports a use outside a function as `masterbelt.checker.return_outside_function`.

The `assert_statement` is recognized by the grammar everywhere a statement is accepted, but it is only valid inside a [validation rule body](../masterdata/validation.md). The checker rejects an `assert` used elsewhere as `masterbelt.checker.assert_outside_validation`.

### If Statements

An `if` statement chooses one of two branches based on a boolean condition. The condition is an expression that must be assignable to `bool`. A non-bool condition is reported as `masterbelt.checker.if_condition_non_bool`.

The two branches share the surrounding function's scope. A `return` inside either branch terminates the surrounding function the same way a top-level `return` would.

The `else` clause is optional and may take one of two shapes:

- `else function_block` — a final alternative branch.
- `else if_statement` — chained `if`/`else if` decisions. The chain is right-associative; an `else if` matches the innermost preceding `if` that does not already have an `else`.

```mst
fn classify(value: int): string {
  if value < 0 { return "negative" }
  if value == 0 { return "zero" }
  return "positive"
}
```

The keywords `if` and `else` are reserved and cannot be used as identifiers in any position.

### Match Statements

A `match` statement chooses one of a fixed list of branches based on the shape of a subject value. Each branch (called an *arm*) carries a pattern, an optional guard expression, and a function block.

```ebnf
match_arm              = match_pattern { "|" match_pattern } [ "if" expression ] "=>" function_block ;
match_pattern          = type_pattern
                       | enum_pattern
                       | literal_pattern
                       | product_pattern
                       | wildcard_pattern ;
type_pattern           = type_expression [ "as" identifier ] ;
enum_pattern           = identifier "." identifier ;
literal_pattern        = null_literal | bool_literal | integer_literal | string_literal ;
product_pattern        = ( identifier | generic_type ) "{" [ product_pattern_fields ] "}" [ "as" identifier ] ;
product_pattern_fields = product_pattern_field { "," product_pattern_field } [ "," ] ;
product_pattern_field  = identifier [ ":" match_pattern ] ;
wildcard_pattern       = "_" ;
```

The arm body is a function block; an expression-position `match` form is not part of this stage of the grammar.

A `match` statement has at least one arm. A `match` statement with no arms is a syntax error.

Each arm's pattern decides whether the arm matches the subject. The first arm whose pattern matches (and whose guard, if any, evaluates to `true`) is the only arm executed; the other arms are not evaluated.

The `match` and `_` keywords are reserved and cannot be used as identifiers in any position.

#### Pattern Forms

- A **type pattern** matches when the subject's runtime type is assignable to the written type. The optional `as name` clause binds the narrowed value to a new local of the pattern's type inside the arm body.
- An **enum pattern** matches when the subject equals the named variant. The pattern's two-segment form is the same surface form used by enum member access expressions.
- A **literal pattern** matches when the subject equals the literal value. Literal patterns are written using the same literal forms as expressions.
- A **product pattern** matches when the subject's runtime type is assignable to the named product type and every field sub-pattern matches the corresponding field. A product pattern always names its type prefix (bare `{ ... }` patterns without a type prefix are a syntax error). Field sub-patterns are written `field: pattern`. The short form `field` (no `: pattern`) is shorthand for `field: field`: it binds the field's value to a new local named after the field.
- A **wildcard pattern** matches every value.

A `|`-separated pattern list matches when any of its alternatives matches. Every alternative in one arm must be the same surface kind; the type checker further restricts what may appear (see [types.md](types.md#match-statements)).

#### Guards and Body Scope

When a pattern includes a binding (the `as name` clause, the short `field` form, or any nested binding), the binding is in scope inside the optional guard expression and inside the arm's function block. Bindings introduced by alternatives of a `|`-separated pattern list must agree in name and type across every alternative.

A guard expression is a boolean expression evaluated after the pattern matches. When the guard evaluates to `false`, the arm is skipped and matching continues with the next arm.

The arm body shares the surrounding function's scope. A `return` inside an arm body terminates the surrounding function the same way a top-level `return` would.

### For Statements

A `for` statement walks the elements of an iterable subject and runs the body once per element. Each iteration introduces a fresh block scope so locals declared inside the body do not leak across iterations.

```ebnf
for_statement = "for" for_binding [ "," for_binding ] "in" expression function_block ;
for_binding   = identifier | "_" ;
```

The `for`, `in`, `break`, and `continue` keywords are reserved and cannot be used as identifiers in any position. The wildcard `_` is shared with [match statements](#match-statements) and is reserved at every binding position.

The subject expression's type drives the binding count and element types:

- A `list<T>` subject requires exactly one binding, typed `T`.
- A `map<K, V>` subject requires exactly two bindings, typed `K` and `V` in that order (the map's type-parameter order).
- The standard-library helper `range(start, end)` ([builtins.md](builtins.md)) returns a `list<int>` that the for statement treats specially (see [codegen/golang.md](../codegen/golang.md), [codegen/typescript.md](../codegen/typescript.md), [codegen/csharp.md](../codegen/csharp.md) for the lowering); the binding rules follow the `list<int>` shape.
- Master-collection iteration is reached through the master's `all()` method ([masterdata/schema.md](../masterdata/schema.md#iteration)) which yields `list<Record>`.

A subject whose static type is a union containing `null` is not iterable on its own: the value must be narrowed to a list or map first (typically via a [match statement](#match-statements)). Iterating a non-iterable subject is rejected; iterating a possibly-null value is rejected with the same diagnostic.

Bindings introduced by the for binding clause are visible inside the function block. A binding written as `_` skips that position without introducing a name. The two bindings of a map iteration may both be `_`; that form still type-checks but is rarely useful.

Mutating the subject collection while iterating is unspecified behavior; the resulting iteration order and reachable element set are implementation-defined and may differ between target languages.

### Break and Continue

`break` exits the innermost enclosing `for` body; `continue` skips the rest of the current iteration and proceeds with the next one. Both are bare keyword statements with no operand.

```ebnf
break_statement    = "break" ;
continue_statement = "continue" ;
```

Using `break` or `continue` outside any `for` body is a type checking error; see [types.md](types.md#for-statements).

### Failable Handling

A `failable` function declared with the `failable` effect modifier may complete with either the declared success type `R` or an `Error` value. The Error path is transparent at the surface: a call to a `failable` function is typed `R` exactly like a non-failable call, and propagation of an Error through the enclosing function is automatic. See [semantics.md](semantics.md#failable-handling) for the user-visible meaning and rationale.

```mst
/// Failure-producing function. Inside the body, `return v`
/// completes successfully and `fail expr` completes with an Error.
failable fn parseInt(s: string): int {
  if s == "" {
    fail "empty input"
  }
  return 0
}

/// Calling a failable function from another failable function:
/// the surface form is an ordinary call. If parseInt fails, the
/// surrounding sumParsed completes with that same Error
/// automatically; the source program never mentions Error.
failable fn sumParsed(a: string, b: string): int {
  let x = parseInt(a)
  let y = parseInt(b)
  return x + y
}
```

A function that calls a `failable` function does not need to declare the `failable` modifier — the effect propagates silently. The same rule applies to every other effect (`asyncable`, `cancellable`); see [types.md](types.md#effect-inheritance).

#### `fail` statement

```ebnf
fail_statement = "fail" expression ;
```

`fail` is valid only inside the body of a `failable` function. The expression must evaluate to a `string` (sugar for `Error { message: <string> }`) or to an `Error` value directly. The statement completes the enclosing function with the produced Error. Using `fail` outside a `failable` function is reported as `masterbelt.checker.fail_outside_failable`; a non-string-and-non-Error argument is reported as `masterbelt.checker.fail_unsupported_argument`.

The `fail` keyword is reserved and cannot be used as an identifier in any position.

### Local Bindings and Assignment

A function body may introduce local bindings with `const` and `let` statements:

- `const x = expr` declares an immutable local. Reassigning it through an assignment statement is a type-checking error (`masterbelt.checker.assignment_to_const`).
- `let x = expr` declares a mutable local that can be reassigned via the assignment statement form `x = expr`.

Both forms accept an optional type annotation after the name. When omitted, the local's type is inferred from the initializer. When written, the initializer must be assignable to the annotated type (using the same rule as a `const` declaration's annotation).

Locals are block-scoped. A binding declared inside a function block or an `if`/`else` branch is visible from its declaration until the end of the surrounding block. A local that shadows a binding from an enclosing scope is rejected (`masterbelt.checker.local_redeclaration`); reusing a name in a sibling (non-nested) block is allowed.

Assignment looks the binding up through the enclosing block chain. Assigning to a name that is not visible in any enclosing scope is reported as `masterbelt.checker.assignment_to_unknown`. Assigning a value whose type is not assignable to the binding's declared type is reported as `masterbelt.checker.assignment_type_mismatch`.

The local `const` statement is a distinct form from the top-level `const` declaration: the local form does not accept the parenthesised group, does not accept a visibility modifier, and does not expose its name outside the surrounding function.

The keyword `let` is reserved and cannot be used as an identifier in any position. The `const` keyword is already reserved by top-level declarations.

```mst
fn boundedAbs(x: int, limit: int): int {
  let value: int = x
  if value < 0 {
    value = -value
  }
  if value > limit {
    value = limit
  }
  return value
}
```

## Expressions

The implemented expression forms are literal expressions, collection literals, and identifier references.

```ebnf
expression               = binary_expression ;
binary_expression        = unary_expression { binary_operator unary_expression } ;
unary_expression         = { unary_operator } primary_expression ;
primary_expression       = literal | collection_literal | identifier_expression | product_literal | typed_product_literal | cast_expression | member_access_expression | call_expression | function_expression ;
literal                  = null_literal | bool_literal | integer_literal | string_literal ;
collection_literal       = "[" [ collection_items ] "]" ;
collection_items         = collection_item { "," collection_item } [ "," ] ;
collection_item          = expression [ ":" expression ] ;
identifier_expression    = identifier ;
product_literal          = "{" [ product_literal_fields ] "}" ;
product_literal_fields   = product_literal_field { "," product_literal_field } [ "," ] ;
product_literal_field    = identifier ":" expression ;
typed_product_literal    = ( identifier | generic_type ) product_literal ;
cast_expression          = ( identifier | generic_type ) "(" expression ")" ;
member_access_expression = ( identifier | member_access_expression | call_expression ) "." identifier ;
call_expression          = expression "(" [ call_arguments ] ")" ;
call_arguments           = expression { "," expression } [ "," ] ;
function_expression      = { effect_modifier } "fn" "(" [ function_parameters ] ")" [ ":" type_expression ] function_body ;
```

The cast_expression form converts the inner value to the type written before the parentheses. The grammar permits either a bare identifier or a generic type application as the cast target; the type checker further restricts the target to a numeric type (or an alias whose body resolves to a numeric type).

An identifier expression in value position refers to an existing value binding. The reserved literal words `null`, `true`, and `false` are matched as literal expressions, not as identifier expressions. The reserved keywords `const`, `pub`, `type`, `use`, `from`, and `as` are not valid identifier expressions.

A collection literal is one of three forms decided by item shape:

- All items written as a single expression: a list literal.
- All items written as `expression ":" expression`: a map literal.
- No items: an empty collection literal. The empty form has no syntactic discriminator; the type checker resolves it to a list or a map using the surrounding type annotation.

Mixing item shapes is a syntax error. A trailing comma is allowed after the last item.

Map entries with identical keys are not a syntax error. Their runtime semantics is last-wins.

### Unary and Binary Expressions

A unary expression applies a prefix operator token to a primary expression. A binary expression applies an infix operator token between two operands. Both forms desugar to a method call on the operand (unary) or the left-hand operand (binary). The operator-to-method mapping and the intrinsic methods that the language provides for each built-in type are defined in [builtins.md](builtins.md).

Operator precedence, highest to lowest:

| Level | Operators                        | Associativity |
|-------|----------------------------------|---------------|
| 1     | unary `!` `+` `-`                | n/a (prefix)  |
| 2     | `*` `/` `%`                      | left          |
| 3     | `+` `-` (binary)                 | left          |
| 4     | `<<` `>>`                        | left          |
| 5     | `<` `<=` `>` `>=`                | left          |
| 6     | `==` `!=`                        | left          |
| 7     | `&`                              | left          |
| 8     | `^`                              | left          |
| 9     | `\|`                             | left          |

Parentheses around an expression are not currently part of the grammar; if multiple operators are mixed, precedence as listed above determines the parse.

The `|` binary operator and the `|` between type expressions are lexically the same token. They do not conflict because a binary expression appears only in an expression position, while the `|` of a union type appears only in a type expression position. The `<` and `>` binary operators are similarly disambiguated from the `<` `>` of generic type applications and the `Type<args>(value)` cast prefix: a generic type or cast prefix is only matched when the token sequence following the identifier is a parseable type-argument list closed by `>` and followed by `(` (cast) or `{` (typed product literal). In every other context, `<` and `>` are binary comparison operators.

## Product Types and Literals

A product type denotes a record whose values are tuples of named fields. Field names within one product type must be unique; duplicates are a syntax error reported on the later occurrence.

Each field may carry an optional modifier. The `readonly` modifier declares that the field is immutable after construction. The `writable` modifier declares the field as mutable; it is the explicit form of the default and exists so source can spell out the field's intent. A field with no modifier behaves the same as a `writable` field. The `primary` modifier is meaningful only inside a master declaration's record section; its semantics is defined in [../masterdata/keys.md](../masterdata/keys.md). Field modifiers are surface-level metadata: they do not affect type identity or assignability, but they influence the shape of generated code (see the codegen specifications for each target).

The keywords `readonly`, `writable`, and `primary` are reserved and cannot be used as identifiers in any position.

A product literal constructs a product value. The bare form `{ field: value, ... }` requires a surrounding annotation (such as a `const` item type annotation or a list/map element type) that supplies the expected product type. The typed-prefix form `TypeName { field: value, ... }` names the product type directly and does not require an outer annotation.

A product literal must list every field declared in the resolved product type exactly once. Field order in the literal does not need to match the type's declaration order; structural equality of product types is independent of field order.

### Product Type Methods

A product type may declare methods alongside its fields, separated by the same comma as fields. A method's surface form mirrors a top-level function declaration: `[effect ...] fn name(params)[: R] body`. Methods do not affect the product type's structural identity (see [types.md](types.md)); two product types whose field sets agree but whose method sets differ are still the same type structurally.

A product type may declare multiple methods with the same name as long as their signatures differ — that is, parameter types differ at least in one position. Overload resolution is described in [types.md](types.md).

## Function Types

A function type denotes the signature of a callable: an ordered parameter list, a return type, and an optional set of effect modifiers. Function types appear as type expressions and currently take no value-level form; they are used to annotate callable shapes for downstream consumers.

```mst
type BinaryOp = fn(left: int, right: int): int
type Mapper<T, U> = fn(value: T): U
type Sum = fn(initial: int, *values: int): int
type Lookup = asyncable cancellable fn(key: string): string
type Parser = failable fn(input: string): int
```

A function type's parameter list is comma-separated. Each parameter is written `name: T` and may be marked variadic by prefixing the parameter name with `*`. A trailing comma after the last parameter is allowed. An empty parameter list is allowed.

A return type is mandatory; the colon and the return type expression must follow the closing parenthesis.

Effect modifiers appear before the `fn` keyword and are separated by whitespace. The effect modifiers are `asyncable`, `failable`, and `cancellable`; their semantics are defined in [codegen/model.md](../codegen/model.md) and their target mappings are defined in each target's specification. Effects are a set: source order is not significant and duplicates collapse.

The keywords `fn`, `asyncable`, `failable`, and `cancellable` are reserved and cannot be used as identifiers in any position.

### Variadic Parameters

A parameter prefixed with `*` is variadic: the function accepts zero or more arguments of the declared element type at that position. Only the last parameter of a function type may be variadic; a `*`-prefixed parameter in any earlier position is a syntax error.

Variadic parameters expand to a homogeneous sequence of the declared element type at every target language's call site.

## Enum Declarations

An enum declaration introduces a nominal type whose values are a closed, ordered list of named variants. Each variant is backed by a numeric integer value fixed at compile time.

```mst
pub enum Status { Active, Inactive }
pub enum Priority: int32 { Low = 1, Normal = 5, High = 10 }
enum Color {
  Red,
  Green = 5,
  Blue,
}
```

The optional storage clause `: type_expression` names the integer type that backs every variant value. The storage type is a type expression so it accepts type declarations whose target is a built-in numeric type as well as the numerics directly; it must resolve to a numeric type after declared-type resolution. When the storage clause is omitted, the storage type is `int8`.

An enum declaration must list at least one variant; declaring an enum with an empty body is a type checking error reported as `masterbelt.checker.enum_empty`.

Variant names within one enum declaration must be unique; declaring two variants with the same name is a syntax error reported on the later occurrence.

A variant value, if written, must be an integer literal. Any other expression is a syntax error.

The keyword `enum` is reserved and cannot be used as an identifier in any position.

### Variant Value Assignment

Variant values are assigned in source order:

- A variant with an explicit `= integer_literal` takes that literal as its value.
- A variant without an explicit value at the start of the declaration takes the value `0`.
- A variant without an explicit value after at least one previous variant takes the previous variant's value plus one.

Variant values are not required to be unique or monotonically increasing; if a variant with an explicit value collides with an earlier variant's value, both retain their declared values and no error is reported. Tooling consumers (linters, target indexes) may flag such collisions independently.

Every assigned variant value must fit the resolved storage type's value range. A value outside the range is a lowering-time error reported with the standard integer-out-of-range diagnostic for the storage type.

## Master Declarations

A master declaration introduces a named master data collection. The body is a brace-delimited list of sections; each section begins with a section-kind keyword (`record` or `source`) and carries the section's payload.

```mst
master Items {
  record {
    primary id: int,
    name: string,
  }

  source {
    csv "data/items.csv" {
      separator: ",",
    }
  }
}
```

The record section's payload is a product type expression: the same surface form that follows `=` in a `type Foo = { ... }` declaration. The `primary` field modifier is valid only inside a master record section; the language defines the modifier as a field-modifier keyword here, and [../masterdata/keys.md](../masterdata/keys.md) defines its semantics.

The source section's payload is a brace-delimited list of source entries. Each entry begins with a source-kind identifier (for example `csv`), followed by a path expressed as a string literal, optionally followed by an option list. Source kinds are not reserved keywords: they are matched only at the source-kind position. The set of recognised source kinds is defined by the importer specifications under [../masterdata/](../masterdata/).

The keywords `master`, `record`, `source`, and `primary` are reserved and cannot be used as identifiers in any position.

The semantics of master declarations — primary keys, record element type, validation, and import semantics — are defined in [../masterdata/schema.md](../masterdata/schema.md).

## Member Access Expressions

A member access expression names a member of a target identifier with the form `Target.Member`.

```mst
const default: Status = Status.Active
const aliceName: string = Alice.name
```

The target identifier resolves either to a value (then the access selects an instance member of the value's type) or to a type (then the access selects a static member of that type). The semantics of member lookup, including which types expose which members, are defined in [types.md](types.md).

The target may itself be a member access (`Parent.Child.Member`) or a call expression (`target().member`). The call-target form supports [scope chaining](../masterdata/schema.md#scope-section), where a member is read off the relation returned by a scope call (`self.adult().gendered(g)`).

A scope declaration's `=> expression` body and a function arrow body share the `"=>" expression` shape but are distinct grammar productions: a scope arrow body sits in the master-scope position and its expression must produce a `Relation<M>` (see [schema.md#scope-section](../masterdata/schema.md#scope-section)).
