Masterbelt

masterbelt/masterbelt

Semantics

Synced from main@9490864MarkdownSource

#Semantics

This document defines the currently implemented user-visible structural meaning of Masterbelt programs after syntax is parsed.

The semantic model is intentionally minimal at this stage. Future semantic additions must extend this document before or together with implementation changes.

#Source Files

A source file evaluates to ordered declarations and statements.

Line comments and block comments have no semantic value. They remain available to syntax tooling but do not appear in the semantic program structure.

#Analysis

Analysis parses a source file, resolves names, and type checks the resolved file.

Analysis returns diagnostics from each implemented phase in phase order: syntax parsing, name resolution, then type checking.

At this stage, analysis may continue after an earlier phase reports diagnostics when that phase produced a partial result. Later diagnostics are still reported, but callers must treat any error diagnostic as making the source unacceptable for operations that require a trustworthy checked program.

#Documentation Comments

Documentation comments attach to the declaration or statement that immediately follows them in the syntax tree.

Multiple documentation comments attached to the same declaration or statement preserve source order.

Documentation comments inside grouped const declarations attach to the const item that immediately follows them.

The text of a documentation comment is the source text after the leading ///.

When a documentation comment line ends with carriage-return line feed, the carriage return is not part of the documentation text.

#Visibility Modifiers

A pub declaration is visible outside its source file. Without pub, a declaration is private to its file.

#Const Declarations

A const declaration binds one or more immutable names to initializer expressions.

The optional type annotation records the declared type for the const item. If no type annotation is written, the item type is inferred from the initializer expression.

Grouped const declarations apply the outer visibility to every item in the group. Documentation comments inside the group are item documentation and preserve source order.

#Type Declarations

A type declaration introduces a new name for a type expression. The declared type may carry zero or more type parameters; the body may be any nested type expression — product, union, generic, or any combination — and the declared name resolves through to that body at every use site.

The declared name and the target type expression are both stored in the resolved declaration. A pub type declaration is visible outside its file.

A type declaration does not introduce a value binding and does not evaluate to a runtime value.

#Failable Handling

Masterbelt is a DSL whose primary surface users are planners, not engineers. Exception-style control flow is therefore not part of the surface language: a failable call from a failable body looks and types like an ordinary call, and the failure path is plumbed transparently by the implementation.

A failable function may produce one of two outcomes per call: the declared success type R, or an Error value. The surface program never expresses the union directly:

  • A failable function's declared return type is exactly its success type R. Call expressions of a failable function are typed R. The Error outcome is not visible at the type level.
  • A fail expression statement completes the surrounding failable function with the Error outcome. When the expression is a string, the runtime constructs Error { message: <string> }; when it is an Error value, that value is used directly.
  • A return value statement completes the function with the success outcome, carrying value (which must be assignable to R).
  • When a failable call inside a failable body produces an Error, the surrounding function completes with that same Error automatically. No surface syntax marks this propagation, and no surface syntax can intercept it.

Every effect — failable, asyncable, cancellable — is inherited silently at call sites: a function that calls a callable carrying any effect behaves as if it carried the same effect, whether or not its own declaration mentions it. The surface program never has to express the obligation; each codegen target observes the call graph and lifts the obligation into the rendered signature (see types.md).

A match statement does not receive an Error arm for a failable call subject, because the call's surface type is R. The built-in Error product type remains available only as the argument to fail.

The evaluator and every codegen target observe these semantics through their native error-passing idioms (see codegen/golang.md, codegen/typescript.md, and codegen/csharp.md). Those idioms are an implementation concern; they must not leak into the surface program.

#For Statements

A for statement evaluates its subject expression exactly once. The result is a sequence of elements decided by the subject's type:

  • A list<T> value yields its elements in stored order.
  • A map<K, V> value yields its entries in insertion order. Each entry contributes two values, a key and a value, in that order.
  • A range(start, end) call yields the integers start, start + 1, ..., end - 1 in ascending order. When start >= end, no element is yielded.
  • A master.toList() call yields the master's records in import order. Records dropped by the master's filter section are not yielded.

For each element, the bindings declared by the for clause are bound to the element's components in the order described above, then the function block is evaluated under a fresh scope. After the block completes, the next element is drawn; iteration ends when the subject is exhausted.

Bindings written as _ discard the corresponding component. The body cannot observe a _-bound value.

A break statement terminates the iteration immediately and resumes after the for statement. A continue statement skips the remainder of the current iteration; the next element is drawn as usual. A return inside the body terminates the surrounding function the same way a top-level return would.

Mutation of the subject collection from within the body is unspecified behavior. Targets may evaluate the iteration over a live or snapshot view; programs must not rely on either choice.

#Match Statements

A match statement evaluates its subject expression exactly once, then selects at most one arm by walking the arms in source order:

  1. The subject is tested against the arm's pattern.
  2. When the pattern matches, the arm's guard expression (if any) is evaluated.
  3. When the guard evaluates to true (or no guard is written), the arm's function block is evaluated. No subsequent arm is considered.
  4. When the pattern does not match, or the guard evaluates to false, evaluation continues with the next arm.

Pattern matching is value-based: a type pattern observes the subject's runtime type; an enum pattern, literal pattern, or product pattern compares the subject to a value or structural shape. Bindings introduced by a matched pattern (as name, the short product-field form, or a nested binding) take effect before the guard expression is evaluated and remain in scope for the entire arm body.

A guard expression is an ordinary boolean expression; it may have side effects. The guard is evaluated at most once per arm and only when its arm's pattern matches.

The set of arms is exhaustive over the subject's static type: type checking guarantees that at least one arm matches every value of the subject's type, except when a guard restricts the matched set (see types.md). When every arm's guard evaluates to false and no later unguarded arm covers the value, evaluation falls through the match statement without executing any arm body.

#Validation Blocks

A master's validation section declares named rules that run over the master's post-filter records during masterbelt export, after import and filtering and before any artifact is written. A validation rule never drops a record; it inspects the data and emits diagnostics. The complete surface form, scoping, and severity model are defined in masterdata/validation.md.

A validation rule body is a statement block that "passes" when it runs to completion. It must not contain a return: a validation block has no value and the checker rejects return inside it. The block reports failures through assert instead.

An all rule binds the master's post-filter relation to table (and to its alias self). A for row in table statement iterates the relation's post-filter records the same way For Statements iterate a relation; because table is a relation, an all rule may also apply the master's scopes to it.

#Assert Statements

An assert statement evaluates its condition expression, which must be a boolean. assert is the validation primitive and is valid only inside a validation rule body.

A failing assert (a condition that evaluates to false) records one diagnostic and evaluation continues with the next statement. Unlike fail or return, an assert does not abort the surrounding block, so one rule can report several failures from several assert statements in a single pass.

#Scope Sections

A master's scope section declares a named, parameterisable relation query that surfaces as a method on the master's relation. A scope body builds and returns a Relation<M> without scanning records; it is effect-free and runs no terminal. The surface form, the self receiver, the relation query DSL, scope chaining, visibility, and the indexed modifier are defined in masterdata/schema.md and masterdata/query.md; the type rules are defined in types.md; the evaluation model is defined in evaluation.md.

#Expression Statements

An expression statement evaluates its expression.

At this stage, expression statements do not bind names and do not produce declarations.

#Literals

Literal expressions evaluate to their literal values.

#Null Literal

null evaluates to the null value.

#Bool Literals

true evaluates to the boolean true value.

false evaluates to the boolean false value.

#Integer Literals

Integer literals evaluate to integer values.

Digit separators are ignored when determining the value.

The representable range and type assignment of integer literal values are defined by the type system.

Radix prefixes determine the base:

  • 0b and 0B use base 2.
  • 0o and 0O use base 8.
  • 0x and 0X use base 16.
  • Integer literals without a radix prefix use base 10, including zero-padded decimal literals.

#String Literals

String literals evaluate to string values.

Escape sequences are decoded when determining the string value.

#Collection Literals

A list literal evaluates to an ordered sequence of element values.

A map literal evaluates to a mapping from key values to value values. When two map entries have equal keys, the entry that appears later in the source replaces the earlier one. This rule applies to both literal and computed keys.

An empty collection literal evaluates to an empty list or an empty map depending on the resolved type as determined by Collection Literal Types.

Specification