prometeu-studio/docs/specs/pbs/3. Core Syntax Specification.md
2026-03-24 13:42:16 +00:00

34 KiB

PBS Core Syntax Specification

Status: Draft v1 (Normative)
Scope: Lexer + parser contract for PBS source files (.pbs) and mod.barrel

1. Purpose

This document defines the canonical syntax for PBS core language.

It is intended to:

  • keep parsing deterministic,
  • provide a stable contract for tooling,
  • support game-first workflows with explicit structure,
  • remain compatible with runtime and bytecode authority.

This document defines syntax only. Runtime behavior, optimizer policy, and bytecode encoding are out of scope.

2. Authority and precedence

Normative precedence:

  1. Runtime authority (docs/specs/hardware/topics/chapter-2.md, chapter-3.md, chapter-9.md, chapter-12.md, chapter-16.md)
  2. Bytecode authority (docs/specs/bytecode/ISA_CORE.md)
  3. This document
  4. Legacy references (docs/specs/random/pbs_old/*)

If a rule here conflicts with higher authority, it is invalid.

3. Source model

  • Source encoding: UTF-8.
  • Valid line terminators: \n, \r\n.
  • Whitespace is insignificant except as token separator.
  • A .pbs file is a declaration unit.
  • Top-level executable statements are forbidden.

4. Lexical specification

4.1 Required token classes

The lexer MUST emit:

  • identifiers,
  • keywords,
  • literals,
  • punctuation,
  • operators,
  • comments,
  • EOF.

Each token MUST include stable source span metadata (byte offsets).

4.2 Comments

  • Line comment: // ... until line end.
  • Block comments are not part of v1 core syntax.

4.3 Identifiers

Identifier rules:

  • first character: _ or alphabetic,
  • following characters: _, alphabetic, or digit.

Keywords are not valid identifiers.

4.4 Keywords

Active keywords in .pbs:

  • import, from, as
  • service, host, fn, apply, bind, new, implements, using, ctor
  • declare, struct, contract, error, enum, callback, Self, this
  • let, const
  • if, else, switch, default, for, from, until, step, while, break, continue, return
  • void, optional, result
  • some, none, ok, err
  • handle
  • true, false, and, or, not

Barrel-only keywords:

  • pub, mod
  • type

Reserved (not active syntax in v1 core):

  • alloc, borrow, mutate, peek, take, weak
  • spawn, yield, sleep
  • match

Note:

  • match remains a reserved keyword for possible future pattern-matching constructs.
  • match is not used for result handling in v1 core syntax.

4.5 Literals

Numeric literals:

  • IntLit: decimal integer (0, 42, 1000)
  • FloatLit: decimal float with dot (3.14, 0.5)

String literals:

  • delimited by ".
  • supported escapes: \\, \", \n, \r, \t.

Boolean literals:

  • true, false.

5. Module and barrel model

5.1 Required files

A valid module contains:

  • one or more .pbs files,
  • exactly one mod.barrel.

Missing mod.barrel is a compile-time error.

5.2 Visibility responsibility

mod.barrel is the single source of module visibility.

Visibility levels:

  • mod: visible across files in the same module.
  • pub: visible to importing modules.

Top-level declarations not listed in mod.barrel are file-private. Using mod/pub as top-level modifiers inside .pbs files is a syntax error. For overloaded functions, visibility is per-signature; each exported overload must appear explicitly in mod.barrel.

5.3 Barrel grammar

BarrelFile ::= BarrelItem* EOF
BarrelItem ::= BarrelFnItem
             | BarrelStructItem
             | BarrelContractItem
             | BarrelErrorItem
             | BarrelEnumItem
             | BarrelServiceItem
             | BarrelConstItem
             | BarrelCallbackItem
BarrelVisibility ::= 'mod' | 'pub'

BarrelFnItem ::= BarrelVisibility 'fn' Identifier ParamList ReturnAnn? ';'
BarrelStructItem ::= BarrelVisibility 'struct' Identifier ';'
BarrelContractItem ::= BarrelVisibility 'contract' Identifier ';'
BarrelErrorItem ::= BarrelVisibility 'error' Identifier ';'
BarrelEnumItem ::= BarrelVisibility 'enum' Identifier ';'
BarrelServiceItem ::= BarrelVisibility 'service' Identifier ';'
BarrelConstItem ::= BarrelVisibility 'const' Identifier ';'
BarrelCallbackItem ::= BarrelVisibility 'callback' Identifier ';'

Rules:

  • No aliases in mod.barrel.
  • Duplicate non-function entries are forbidden by symbol kind + identifier.
  • Duplicate function entries are forbidden by full function signature.
  • Every barrel item must resolve to an existing top-level declaration in module scope.
  • fn barrel entries MUST use the same signature surface as fn declarations.
  • Type declarations in mod.barrel MUST use their specific declaration kind: struct, contract, error, or enum.
  • Labels are part of the declaration surface, but do not participate in function identity or barrel matching.
  • Only pub symbols may be imported from another module.
  • Multiple fn barrel entries with the same identifier are allowed only when signatures differ (overload set).
  • Barrel labels MAY differ from the implementation labels.
  • A fn barrel entry must resolve to exactly one declaration in module scope.
  • A callback barrel entry must resolve to exactly one top-level callback declaration in module scope.

Example:

pub fn func(a: int) -> int;
pub fn bla(b: int, c: int) -> (d: float, e: float);
pub struct Struct;
pub contract TickLike;
pub enum GameState;
pub callback TickCb;

6. File grammar

EBNF conventions:

  • A? optional,
  • A* zero or more,
  • A+ one or more,
  • terminals in single quotes.
File ::= ImportDecl* TopDecl* EOF

6.1 Imports

Imports target modules, never files.

ImportDecl ::= 'import' ( ModuleRef | '{' ImportList '}' 'from' ModuleRef ) ';'
ImportList ::= ImportItem (',' ImportItem)*
ImportItem ::= Identifier ('as' Identifier)?
ModuleRef ::= '@' Identifier ':' ModulePath
ModulePath ::= Identifier ('/' Identifier)*

6.2 Top-level declarations

TopDecl ::= TypeDecl | CallbackDecl | ServiceDecl | FunctionDecl | HostFnDecl | ConstDecl | ImplDecl

Top-level let forms and top-level executable statements are forbidden.

7. Declarations

7.1 Type declarations

TypeDecl ::= StructDecl | ContractDecl | ErrorDecl | EnumDecl
StructDecl ::= 'declare' 'struct' Identifier '(' StructFieldList? ')' (StructMethodBody | ';')
ContractDecl ::= 'declare' 'contract' Identifier ContractBody
ErrorDecl ::= 'declare' 'error' Identifier ErrorBody
EnumDecl ::= 'declare' 'enum' Identifier '(' EnumCaseDecl (',' EnumCaseDecl)* ','? ')' ';'
StructFieldList ::= StructFieldDecl (',' StructFieldDecl)* ','?
StructFieldDecl ::= Identifier ':' TypeRef
StructMethodBody ::= '{' StructMemberDecl* '}'
StructMemberDecl ::= StructMethodDecl | StructCtorDecl
StructMethodDecl ::= 'fn' Identifier ParamList ReturnAnn? Block
StructCtorDecl ::= 'ctor' Identifier ParamList Block
ContractBody ::= '{' FnSigDecl* '}'
ErrorBody ::= '{' ErrorCaseDecl+ '}'
FieldDecl ::= Identifier ':' TypeRef ';'
FnSigDecl ::= 'fn' Identifier ParamList ReturnAnn? ';'
ErrorCaseDecl ::= Identifier ';'
EnumCaseDecl ::= Identifier ('=' IntLit)?
ImplDecl ::= 'implements' Identifier 'for' Identifier 'using' Identifier ImplBody
ImplBody ::= '{' ImplMethodDecl* '}'
ImplMethodDecl ::= 'fn' Identifier ParamList ReturnAnn? Block

Rules:

  • declare struct Name(...); declares a heap-backed nominal user type with fields only.
  • declare struct Name(...) { ... } declares a heap-backed nominal user type with fields and methods.
  • Struct fields are declared positionally in the header and are mutable and accessible by default in v1 core.
  • The enclosing struct name is in scope within its own field types, so self-referential headers such as declare struct Node(next: optional Node); are valid.
  • Struct method and ctor declarations belong only in the optional struct body.
  • Struct methods have an implicit this receiver and do not declare this: Self explicitly.
  • ctor declares a named factory for the enclosing struct type.
  • A named ctor constructs its enclosing struct by initializing the fields of an implicit under-construction this.
  • Multiple ctor declarations with the same name are allowed only when their parameter signatures differ.
  • Struct values are constructed only through new Type(...).
  • declare contract bodies contain only function signatures.
  • A contract declaration introduces a nominal runtime-dispatchable method surface.
  • declare error bodies contain only error case labels.
  • declare enum uses a parenthesized case list and ends with ;.
  • Error case labels in the same declare error body must be unique.
  • Enum case labels in the same declare enum must be unique.
  • If no enum case uses =, case identifiers default to ascending integer values starting at 0 in declaration order.
  • If any enum case uses =, then every enum case in that declaration must use =.
  • Explicit enum case identifiers must be integer literals.
  • Enum values are written as EnumName.caseName.
  • Enum values expose intrinsic method-call surfaces value.name() and value.key().
  • implements Contract for Struct using s { ... } declares the contract implementation block for Struct, with s as the explicit binder name inside the block.
  • implements declarations are top-level declarations and are never exported separately through mod.barrel.
  • Contract values are formed from struct values by explicit or contextual conversion, as defined by static semantics.
  • Structs do not have static methods in v1 core.

7.2 Callbacks

CallbackDecl ::= 'declare' 'callback' Identifier ParamList ReturnAnn? ';'

Rules:

  • A callback declaration introduces a nominal callback type.
  • A callback declaration defines exactly one callable signature.
  • A callback declaration has no body.
  • A callback declaration is distinct from contract and fn.
  • A callback declaration may be exported through mod.barrel using callback.
  • Callback values may be formed either by assigning a compatible top-level fn or by using bind(context, fn_name) under a callback-typed context.

7.3 Services

ServiceDecl ::= 'service' Identifier (':' Identifier)? ServiceBody
ServiceBody ::= '{' ServiceMember* '}'
ServiceMember ::= 'fn' Identifier ParamList ReturnAnn? Block

7.4 Functions

FunctionDecl ::= 'fn' Identifier ParamList ReturnAnn? Block
ParamList ::= '(' (Param (',' Param)*)? ')'
Param ::= Identifier ':' TypeRef
ReturnAnn ::= '->' ReturnSpec
ReturnSpec ::= ReturnVoid | ReturnPlain | ReturnResult
ReturnVoid ::= 'void' | '()'
ReturnPlain ::= ReturnPayload
ReturnResult ::= 'result' '<' TypeRef '>' ReturnPayload?
ReturnPayload ::= TypeRef

Rule:

  • ParamList declares the named fields of the function input tuple.
  • Parameter names are local binding names and are not part of overload identity.
  • A named tuple return annotation declares a named output tuple surface.
  • A function return surface MUST choose exactly one top-level effect form: plain payload or result<Error> payload.
  • Omitted return annotation is equivalent to -> void and -> ().
  • -> T is sugar for -> (value: T).
  • Single-field tuple labels do not create a distinct overload shape.
  • optional T is a valid TypeRef in variables, parameters, fields, and plain function returns.
  • -> optional T is internally normalized as -> optional (value: T) for single-slot payloads.
  • optional and optional void are invalid as type surfaces.
  • result<Err> in function return surface wraps exactly one payload shape and one error type.
  • -> result<Err> T is internally normalized as result<Err, (value: T)> for single-slot payloads.
  • -> result<Err> is valid and equivalent to -> result<Err> void.
  • A function return surface MUST NOT combine optional and result.
  • Overload sets may differ by return shape; overload resolution rules are defined in static semantics.

7.5 Host functions

HostFnDecl ::= 'host' 'fn' Identifier ParamList ReturnAnn? ';'

Rules:

  • host fn has no body.
  • host fn declares callable host surface only.
  • host fn is not first-class.
  • Service-shaped wrappers over host calls belong to stdlib/design patterns, not host syntax.

7.6 Top-level constants

ConstDecl ::= 'declare' 'const' Identifier ':' TypeRef '=' Expr ';'

Rules:

  • Top-level constants MUST use declare const.
  • declare const is a declaration and may be exported through mod.barrel.
  • A declare const initializer MUST be a constant expression as defined by static semantics.
  • declare const values are compile-time constants, not runtime-initialized module storage.

8. Type syntax

TypeRef ::= OptionalType | TypePrimary
TypePrimary ::= SelfType | SimpleType | NamedTupleType | GroupType | UnitType
SelfType ::= 'Self'
SimpleType ::= Identifier
NamedTupleType ::= '(' NamedTupleField (',' NamedTupleField){0,5} ')'
NamedTupleField ::= Identifier ':' TypeRef
GroupType ::= '(' TypeRef ')'
UnitType ::= 'void' | '()'
OptionalType ::= 'optional' TypeRef

Rules:

  • Named tuple arity is 1..6 in v1 core syntax.
  • Tuple types in PBS core are always named.
  • Single-slot tuple types are allowed only in type/signature surface, not as tuple literals.
  • General user-defined generics are not part of PBS core v1.
  • optional T is the canonical optional-type syntax in all type positions.
  • optional void and optional () are invalid.
  • Self is valid only inside struct method and ctor declarations and refers to the enclosing struct type.

9. Statements and blocks

Block ::= '{' Stmt* TailExpr? '}'
TailExpr ::= Expr

Stmt ::= BindingStmt
       | AssignStmt
       | ReturnStmt
       | IfStmt
       | ForStmt
       | WhileStmt
       | BreakStmt
       | ContinueStmt
       | ExprStmt

BindingStmt ::= 'let' LetConstOpt Identifier (':' TypeRef)? '=' Expr ';'
LetConstOpt ::= 'const'?
AssignStmt ::= LValue AssignOp Expr ';'
AssignOp ::= '=' | '+=' | '-=' | '*=' | '/=' | '%='
ReturnStmt ::= 'return' ReturnValue? ';'
ExprStmt ::= Expr ';'

ReturnValue ::= ResultCtorExpr | Expr
ResultCtorExpr ::= OkExpr | ErrExpr
OkExpr ::= 'ok' '(' Expr ')'
ErrExpr ::= 'err' '(' ErrorPath ')'

IfStmt ::= 'if' Expr Block ('else' (IfStmt | Block))?
ForStmt ::= 'for' Identifier ':' TypeRef 'from' Expr 'until' Expr StepClause? Block
StepClause ::= 'step' Expr
WhileStmt ::= 'while' Expr Block
BreakStmt ::= 'break' ';'
ContinueStmt ::= 'continue' ';'

LValue ::= Identifier LValueSuffix*
LValueSuffix ::= '.' Identifier

Rules:

  • Assignment is a statement, not an expression.
  • Local bindings MUST start with let.
  • A local constant binding uses let const.
  • A Block used in expression position evaluates to the TailExpr when present, otherwise to unit.
  • TailExpr is only the final unsemicoloned expression in a block.
  • A function body block does not use TailExpr as implicit return.
  • for is the declarative counted loop form in v1 core.
  • for name: T from start until end { ... } iterates with an implicit step of 1.
  • for name: T from start until end step s { ... } uses explicit step s.
  • while is the general-purpose loop form in v1 core.
  • break and continue are valid only inside loop contexts (for, while).
  • General indexing syntax (expr[index]) is not part of PBS core v1.
  • LValue MUST NOT include function call segments.
  • Compound assignment is syntax sugar over read/compute/write.

10. Expression grammar and precedence

Expr ::= HandleExpr

HandleExpr ::= 'handle' ElseExpr HandleMap | ElseExpr
HandleMap ::= '{' HandleArm (',' HandleArm)* ','? '}'
HandleArm ::= HandlePattern '=>' ErrorPath
HandlePattern ::= ErrorPath | '_'
ErrorPath ::= Identifier ('.' Identifier)*
EnumCasePath ::= Identifier '.' Identifier

ElseExpr ::= IfExpr ('else' ElseExpr)?
IfExpr ::= 'if' Expr Block 'else' (IfExpr | Block) | SwitchExpr
SwitchExpr ::= 'switch' Expr '{' SwitchArm (',' SwitchArm)* ','? '}' | ApplyExpr
SwitchArm ::= SwitchPattern ':' Block
SwitchPattern ::= EnumCasePath | Literal | '_' | 'default'

ApplyExpr ::= OrExpr ('apply' ApplyExpr)?

OrExpr ::= AndExpr (('||' | 'or') AndExpr)*
AndExpr ::= EqualityExpr (('&&' | 'and') EqualityExpr)*

EqualityExpr ::= CompareExpr (('==' | '!=') CompareExpr)?
CompareExpr ::= AsExpr (('<' | '<=' | '>' | '>=') AsExpr)?

AsExpr ::= AddExpr ('as' Identifier)?

AddExpr ::= MulExpr (('+' | '-') MulExpr)*
MulExpr ::= UnaryExpr (('*' | '/' | '%') UnaryExpr)*

UnaryExpr ::= UnaryOp UnaryExpr | PostfixExpr
UnaryOp ::= '!' | '-' | 'not'
PostfixExpr ::= PrimaryExpr PostfixSuffix*
PostfixSuffix ::= CallSugarSuffix | MemberSuffix | PropagateSuffix
CallSugarSuffix ::= '(' ArgList? ')'
MemberSuffix ::= '.' Identifier
PropagateSuffix ::= '!'

ArgList ::= Expr (',' Expr)*

Literal ::= IntLit | FloatLit | StringLit | BoolLit
BoolLit ::= 'true' | 'false'
PrimaryExpr ::= Literal | Identifier | ThisExpr | NewExpr | BindExpr | SomeExpr | NoneExpr | UnitExpr | TupleExpr | GroupExpr | Block
ThisExpr ::= 'this'
NewExpr ::= 'new' NewTarget '(' ArgList? ')'
NewTarget ::= Identifier ('.' Identifier)?
BindExpr ::= 'bind' '(' Expr ',' Identifier ')'
SomeExpr ::= 'some' '(' Expr ')'
NoneExpr ::= 'none'
UnitExpr ::= '(' ')'
TupleExpr ::= '(' TupleExprItem ',' TupleExprItem (',' TupleExprItem){0,4} ')'
TupleExprItem ::= Identifier ':' Expr | Expr
GroupExpr ::= '(' Expr ')'

Non-associative constraints:

  • a < b < c is invalid.
  • a == b == c is invalid.

Rules:

  • apply is the canonical function-application syntax.
  • opt else fallback is the canonical extraction surface for optional.
  • else extraction is right-associative: a else b else c parses as a else (b else c).
  • if cond { a } else { b } is the canonical conditional expression surface.
  • if used as an expression always requires an else branch and uses blocks on both branches.
  • switch value { pattern: { ... }, ... } is the canonical multi-branch selection surface.
  • switch is an expression form; statement-style switch is ordinary ExprStmt usage whose selected arm blocks evaluate to unit.
  • Each switch arm contains a block and may therefore use TailExpr when the surrounding context expects a value.
  • default is the canonical wildcard arm spelling for switch; _ is accepted as equivalent shorthand.
  • switch patterns may use literals or enum cases; error labels are not valid switch patterns.
  • new Type(args...) is the canonical struct construction surface.
  • new Type.ctorName(args...) is the canonical named-constructor surface.
  • Struct construction arguments are positional and follow the declared struct field order.
  • as is reserved in expressions for explicit contract-view conversion only; it does not denote a general cast surface in v1 core.
  • The right operand of expression-form as is a simple contract identifier already visible in scope, never a qualified module path.
  • this is an implicit receiver name available only inside struct method and ctor bodies.
  • apply is right-associative: f1 apply f2 apply x parses as f1 apply (f2 apply x).
  • f1 apply f2 apply f3 apply params parses as f1 apply (f2 apply (f3 apply params)).
  • f() is exact surface sugar for f apply ().
  • f(e) is exact surface sugar for f apply e.
  • f(e1, e2, ..., en) for n >= 2 is exact surface sugar for f apply (e1, e2, ..., en).
  • TupleExpr is a value expression and is distinct from GroupExpr.
  • TupleExpr arity is 2..6 in v1 core syntax.
  • Tuple expressions may be written with explicit labels ((a: 1, b: 2)) or positional sugar ((1, 2)).
  • Positional tuple expression sugar requires an expected named tuple shape supplied by context.
  • No single-slot tuple literal exists in v1 core syntax.
  • bind(context, fn_name) is the canonical surface for explicit callback context binding.
  • bind(context, fn_name) binds only the first parameter of fn_name.
  • The second operand of bind is a function identifier, not an arbitrary expression.
  • Mixing labeled and unlabeled tuple items in the same TupleExpr is a syntax error.
  • Member selection of a struct or contract method is valid only as an immediate call target; bare method extraction such as let m = s.compute; is invalid.

11. Deterministic parser requirements

A conformant parser MUST:

  • parse this grammar deterministically,
  • preserve stable source spans in AST nodes,
  • produce deterministic diagnostics for forbidden constructs,
  • reject unsupported reserved-word syntax in .pbs.

12. Required syntax diagnostics

At minimum, deterministic diagnostics are required for:

  • missing mod.barrel,
  • duplicate barrel entries (including duplicate fn signatures),
  • unresolved or ambiguous fn barrel signatures,
  • unresolved callback barrel entries,
  • invalid error declaration shape,
  • invalid enum declaration shape,
  • invalid struct declaration shape,
  • invalid implements declaration shape,
  • invalid mixed implicit/explicit enum case identifiers,
  • invalid return annotation shape (-> clauses),
  • invalid mixed optional/result function return surface,
  • invalid result<Error> return surface,
  • invalid optional type surface,
  • invalid optional void type surface,
  • invalid named tuple type shape,
  • use of return-surface result<...> outside function return annotations,
  • invalid handle mapping clauses (syntax form),
  • invalid some(...) construction form,
  • invalid none usage without optional type context,
  • invalid ok(...) construction form,
  • invalid err(...) construction form,
  • use of ok(...) or err(...) outside return,
  • invalid bind(...) construction form,
  • invalid else extraction form,
  • invalid if expression form,
  • invalid switch expression form,
  • invalid for loop form,
  • invalid new construction form,
  • invalid enum case path,
  • invalid callback declaration shape,
  • invalid Self type usage,
  • invalid this usage,
  • invalid struct method declaration shape,
  • invalid ctor declaration shape,
  • duplicate enum case label,
  • duplicate enum case identifier,
  • incompatible callback assignment target,
  • ambiguous overloaded function assignment to callback,
  • incompatible bind(...) target,
  • ambiguous overloaded function target in bind(...),
  • malformed apply clause,
  • attempted single-slot tuple literal,
  • positional tuple expression without expected named tuple shape,
  • mixed labeled/unlabeled tuple expression items,
  • use of ? as propagation operator syntax,
  • break/continue outside loops,
  • invalid assignment targets (LValue violations),
  • top-level executable statements,
  • const used outside declare const or let const,
  • non-constant declare const initializer,
  • duplicate declaration names in the same namespace/scope (as defined by semantic phase),
  • use of reserved syntax words as active constructs.

13. optional

This section defines the optional type. All rules in this section are normative.

optional represents the explicit presence or absence of a payload, without exceptions, traps, or implicit unwrapping.

13.1 Purpose and Design Goals

optional exists to model situations where a payload:

  • may or may not be present,
  • is not an error condition by itself,
  • must be handled explicitly by the programmer.

Design goals:

  • no implicit unwrapping,
  • no runtime traps,
  • no heap allocation,
  • stack-only representation.

13.2 Type Definition

optional is a built-in prefix type constructor. It is a special core-language form, not evidence of general generic-type support in v1.

Rules:

  • optional T may appear in any type position where TypeRef is allowed.
  • The payload type is mandatory.
  • optional void and optional () are invalid.
  • A single-slot payload is internally modeled as a one-field tuple payload.
  • optional is a regular value type.

13.3 Construction

An optional value is constructed using:

some(value)
none

Rules:

  • some(...) always requires parentheses and exactly one expression argument.
  • some(...) and none are built-in special forms, not ordinary identifiers.
  • some(value) produces an optional value carrying the declared payload.
  • none produces an empty optional value.
  • none represents absence of value.
  • none must be typed by context.

13.4 Extraction with else

The only way to extract a value from an optional value is using else.

Syntax:

value = opt else fallback

Rules:

  • If opt is some(v), the expression evaluates to v.
  • If opt is none, the expression evaluates to fallback.
  • The type of fallback must be compatible with the declared payload carrier or tuple shape.

13.5 Control Flow and Functions

Functions returning optional T may omit an explicit return none.

Rules:

  • Falling off the end of a function returning optional T is equivalent to return none.
  • This behavior applies only to optional T.

13.6 API Surface

optional exposes a minimal API surface:

opt.hasSome(): bool
opt.hasNone(): bool

Rules:

  • These methods do not extract the contained value.
  • They exist for explicit branching logic.
  • opt.hasSome() and opt.hasNone() are compiler-recognized intrinsic method surfaces over optional.

13.7 Mutability and Copying

Rules:

  • optional follows normal value semantics for its declared payload.
  • Assigning an optional value copies the container and its contents.
  • Mutability of the payload follows normal binding rules.

14. result<Error>

This section defines the result<Error> return surface. All rules in this section are normative at syntax-contract level.

result<Error> represents an explicit success-or-error outcome. In PBS core, it is the mechanism for typed error signaling and is valid only in function return annotations.

14.1 Purpose and Design Goals

result<Error> exists to model operations that:

  • may succeed and produce a declared payload, or
  • may fail with a typed error E.

Design goals:

  • no exceptions,
  • no implicit error propagation,
  • stack-only representation,
  • explicit, typed error handling.

14.2 Return-Only Definition

result<Error> is not part of general TypeRef. It may appear only in a function return annotation. It is a special core-language return form, not evidence of general generic-type support in v1.

Rules:

  • E must be an error type declared via declare error.
  • The payload may be omitted; omitted payload is equivalent to void.
  • If present, the payload must be a valid return payload shape written after result<E>.
  • A single-slot payload is internally modeled as a one-field tuple payload.
  • result<Error> cannot be used for local variable annotations, parameter annotations, or field types.
  • In function return surface, result<Error> is the only effect wrapper in use for that declaration.

14.3 Construction Surface

A result<Error> outcome is produced only by function return forms:

return ok(value);
return err(errorLabel);

Rules:

  • ok(...) always requires parentheses and exactly one expression argument.
  • err(...) always requires parentheses and exactly one expression argument.
  • ok(...) and err(...) are built-in return special forms, not ordinary identifiers.
  • ok(value) produces a success payload.
  • err(label) produces a failure payload.
  • label must match error type E.
  • ok(...) and err(...) are not general-purpose value constructors in v1 core; they are valid only in function return flow for result<Error>.

14.4 ! Propagation Operator

The ! operator propagates errors when error types match.

Syntax:

value = expr!

Rules:

  • expr must be a call or expression producing result<E> Payload.
  • The enclosing function must return result<E> Payload2 with the same error type.
  • If expr is success, expression evaluates to wrapped value.
  • If expr is error, the enclosing function returns err(e) immediately.

14.5 Error Mapping with handle

handle is the only active construct for extracting success values while remapping error labels.

Syntax:

value = handle expr {
  ErrorA.case1 => ErrorB.mapped1,
  ErrorA.case2 => ErrorB.mapped2,
  _            => ErrorB.default,
};

Rules:

  • expr must produce result<E1> Payload1.
  • The enclosing function must return result<E2> Payload2.
  • Arms map E1 labels to E2 labels.
  • Arms must be exhaustive (explicitly or with _).

14.6 Restrictions

Rules:

  • No implicit extraction of result<E> Payload.
  • result<Error> cannot be implicitly converted to optional and vice versa.
  • Falling off the end of a function returning result<Error> is a compile-time error.
  • ? is not active as propagation syntax in v1 core; propagation uses postfix !.
  • No pattern matching construct beyond handle is active in v1 core.
  • match is reserved for future pattern-matching constructs and is not part of result remapping in v1 core.

15. Canonical examples

This section is the canonical reference example set for PBS v1 syntax.

It consolidates the primary surface forms used by tooling and conformance for:

  • apply and call sugar,
  • bind and nominal callbacks,
  • overloads that differ by return shape,
  • optional,
  • result<Error>,
  • switch.

Examples in this section are intended to stay mutually consistent with the syntax and static-semantics documents.

15.1 Host function declaration

host fn input_state() -> (x: int, y: int, buttons: int);

15.2 Top-level and local constants

declare const A: int = 1;

fn demo() -> int {
  let const a: int = 2;
  return A + a;
}

15.3 Function with loops and assignment

fn tick(counter: int) -> int {
  let x = counter;
  for i: int from 0 until 60 {
    x = x + 1;
    if i == 10 {
      continue;
    }
    if i == 49 {
      break;
    }
  }
  return x;
}

15.4 Function application with named output tuple

fn func(a: int, b: int) -> (c: int, d: float) {
  return (c: a + b, d: 2.0);
}

fn demo_apply() -> int {
  let params: (a: int, b: int) = (1, 2);
  let r = func apply params;
  let c = r.c;
  let d = r.d;
  let r2 = func(1, 2);
  let r3 = func apply (a: 1, b: 2);
  let _ = d;
  let _ = r2.d;
  let _ = r3.c;
  return c;
}

fn plus_one(n: int) -> int {
  return n + 1;
}

fn times_two(n: int) -> int {
  return n * 2;
}

fn demo_chain() -> int {
  return plus_one apply times_two apply 10;
}

15.5 Module import and service

import { Vec2 } from @core:math;

service Physics {
  fn step(pos: Vec2, vel: Vec2) -> Vec2 {
    let out = pos;
    out.x += vel.x;
    out.y += vel.y;
    return out;
  }
}

15.6 Nominal callback

declare callback TickCb(dt: int) -> void;

fn on_tick(dt: int) -> void {
  let _ = dt;
}

fn install(cb: TickCb) -> void {
  cb apply 16;
  cb(16);
}

fn demo_callback() -> void {
  let cb: TickCb = on_tick;
  install(cb);
}

15.7 Bound callback

declare callback UpdateCb(dt: int) -> void;

declare struct Enemy(hp: int);

fn enemy_tick(self: Enemy, dt: int) -> void {
  let _ = self.hp;
  let _ = dt;
}

fn install_update(cb: UpdateCb) -> void {
  cb apply 16;
}

fn demo_bind(enemy: Enemy) -> void {
  let cb: UpdateCb = bind(enemy, enemy_tick);
  install_update(cb);
}

15.8 Struct construction, methods, and contract implementation

declare contract StasisProcess {
  fn stasis() -> int;
}

declare struct Struct(
  a: int,
  b: str,
  c: float,
) {
  fn compute_something() -> int {
    return this.a;
  }

  ctor createWithSomethingSpecial(x: int, y: int) {
    let _ = y;
    this.a = x;
    this.b = "special";
    this.c = 3.0;
  }
}

implements StasisProcess for Struct using s {
  fn stasis() -> int {
    return s.compute_something();
  }
}

fn demo_contract() -> int {
  let s = new Struct(1, "2", 3.0);
  let s2 = new Struct.createWithSomethingSpecial(1, 2);
  let sp: StasisProcess = s;
  let sp2 = s as StasisProcess;
  let direct = s.compute_something();
  let via_contract = sp.stasis();
  let _ = s2.compute_something();
  let _ = sp2.stasis();
  return direct + via_contract;
}

15.9 Return-shape equivalence and overload by return

fn a(i: int) { }
fn b(i: int) -> void { }
fn c(i: int) -> () { }

fn length(x: float, y: float) -> float {
  return x * x + y * y;
}

fn transform(a: int, b: int, c: int) -> (left: int, right: int) {
  return (left: a, right: b);
}

fn transform(a: int, b: int, c: int) -> float {
  return 0.0;
}

15.10 Optional and result examples

declare error IOError {
  not_found;
}

fn maybe_number() -> optional int {
  let opt = some(1);
  let n: int = opt else 0;
  let _ = n;
  return some(1);
}

fn maybe_pair() -> optional (left: int, right: int) {
  return none;
}

fn pair_or_error() -> result<IOError> (left: int, right: int) {
  return ok((left: 1, right: 2));
}

fn pair_or_error_fail() -> result<IOError> (left: int, right: int) {
  return err(IOError.not_found);
}

15.11 Result propagation and remapping

declare error ErrorA {
  bad_input;
}

declare error ErrorB {
  invalid;
  generic;
}

fn inner() -> result<ErrorA> int {
  return ok(1);
}

fn outer_propagate() -> result<ErrorA> int {
  let v: int = inner()!;
  return ok(v + 1);
}

fn outer_remap() -> result<ErrorB> int {
  let v: int = handle inner() {
    ErrorA.bad_input => ErrorB.invalid,
    _                => ErrorB.generic,
  };
  return ok(v);
}

15.12 Switch expression and statement-style use

fn state_code(state: int) -> int {
  let code = switch state {
    0: { 0 },
    1: { 1 },
    default: { 2 },
  };
  return code;
}

fn func(v: int) -> void {
  let _ = v;
}

fn dispatch(state: int) -> void {
  switch state {
    0: {
      func(0);
    },
    1: {
      func(1);
    },
    default: {
      func(2);
    },
  };
}

15.13 Enum declaration and use

declare enum Enum(e1 = 0, e2 = 10, e3 = 100);

fn test(e: Enum) -> str {
  let current: Enum = Enum.e1;
  let name: str = current.name();
  let key: int = current.key();
  let _ = key;

  let label = switch e {
    Enum.e1: { "e1" },
    Enum.e2: { "e2" },
    default: { "e3" },
  };

  let _ = name;
  return label;
}