32 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:
- Runtime authority (
docs/specs/hardware/topics/chapter-2.md,chapter-3.md,chapter-9.md,chapter-12.md,chapter-16.md) - Bytecode authority (
docs/specs/bytecode/ISA_CORE.md) - This document
- 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
.pbsfile 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,asservice,host,fn,apply,bind,new,implements,using,ctordeclare,struct,contract,error,enum,callback,Self,thislet,constif,else,switch,default,for,from,until,step,while,break,continue,returnvoid,optional,resultsome,none,ok,errhandletrue,false,and,or,not
Barrel-only keywords:
pub,modtype
Reserved (not active syntax in v1 core):
alloc,borrow,mutate,peek,take,weakspawn,yield,sleepmatch
Note:
matchremains a reserved keyword for possible future pattern-matching constructs.matchis not used forresulthandling in v1 core syntax.
4.5 Literals
Numeric literals:
IntLit: decimal integer (0,42,1000)FloatLit: decimal float with dot (3.14,0.5)BoundedLit: decimal integer withbsuffix (0b,255b)
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
.pbsfiles, - 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.
fnbarrel entries MUST use the same signature surface asfndeclarations.- Type declarations in
mod.barrelMUST use their specific declaration kind:struct,contract,error, orenum. - Labels are part of the declaration surface, but do not participate in function identity or barrel matching.
- Only
pubsymbols may be imported from another module. - Multiple
fnbarrel entries with the same identifier are allowed only when signatures differ (overload set). - Barrel labels MAY differ from the implementation labels.
- A
fnbarrel entry must resolve to exactly one declaration in module scope. - A
callbackbarrel 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
ctordeclarations belong only in the optional struct body. - Struct methods have an implicit
thisreceiver and do not declarethis: Selfexplicitly. ctordeclares a named factory for the enclosing struct type.- A named
ctorconstructs its enclosing struct by initializing the fields of an implicit under-constructionthis. - Struct values are constructed only through
new Type(...). declare contractbodies contain only function signatures.- A contract declaration introduces a nominal runtime-dispatchable method surface.
declare errorbodies contain only error case labels.declare enumuses a parenthesized case list and ends with;.- Error case labels in the same
declare errorbody must be unique. - Enum case labels in the same
declare enummust be unique. - If no enum case uses
=, case identifiers default to ascending integer values starting at0in 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()andvalue.key(). implements Contract for Struct using s { ... }declares the contract implementation block forStruct, withsas the explicit binder name inside the block.implementsdeclarations are top-level declarations and are never exported separately throughmod.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
contractandfn. - A callback declaration may be exported through
mod.barrelusingcallback. - Callback values may be formed either by assigning a compatible top-level
fnor by usingbind(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:
ParamListdeclares 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
-> voidand-> (). -> Tis sugar for-> (value: T).- Single-field tuple labels do not create a distinct overload shape.
optional Tis a validTypeRefin variables, parameters, fields, and plain function returns.-> optional Tis internally normalized as-> optional (value: T)for single-slot payloads.optionalandoptional voidare invalid as type surfaces.result<Err>in function return surface wraps exactly one payload shape and one error type.-> result<Err> Tis internally normalized asresult<Err, (value: T)>for single-slot payloads.-> result<Err>is valid and equivalent to-> result<Err> void.- A function return surface MUST NOT combine
optionalandresult. - 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 fnhas no body.host fndeclares callable host surface only.host fnis 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 constis a declaration and may be exported throughmod.barrel.
8. Type syntax
TypeRef ::= OptionalType | TypePrimary
TypePrimary ::= SelfType | SimpleType | GenericType | NamedTupleType | GroupType | UnitType
SelfType ::= 'Self'
SimpleType ::= Identifier
GenericType ::= Identifier '<' TypeRef (',' TypeRef)* '>'
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.
optional Tis the canonical optional-type syntax in all type positions.optional voidandoptional ()are invalid.Selfis valid only inside struct method andctordeclarations 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 | '[' Expr ']'
Rules:
- Assignment is a statement, not an expression.
- Local bindings MUST start with
let. - A local constant binding uses
let const. - A
Blockused in expression position evaluates to theTailExprwhen present, otherwise to unit. TailExpris only the final unsemicoloned expression in a block.- A function body block does not use
TailExpras implicitreturn. foris the declarative counted loop form in v1 core.for name: T from start until end { ... }iterates with an implicit step of1.for name: T from start until end step s { ... }uses explicit steps.whileis the general-purpose loop form in v1 core.breakandcontinueare valid only inside loop contexts (for,while).LValueMUST 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 | IndexSuffix | PropagateSuffix
CallSugarSuffix ::= '(' ArgList? ')'
MemberSuffix ::= '.' Identifier
IndexSuffix ::= '[' Expr ']'
PropagateSuffix ::= '!'
ArgList ::= Expr (',' Expr)*
Literal ::= IntLit | FloatLit | BoundedLit | 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 < cis invalid.a == b == cis invalid.
Rules:
applyis the canonical function-application syntax.opt else fallbackis the canonical extraction surface foroptional.elseextraction is right-associative:a else b else cparses asa else (b else c).if cond { a } else { b }is the canonical conditional expression surface.ifused as an expression always requires anelsebranch and uses blocks on both branches.switch value { pattern: { ... }, ... }is the canonical multi-branch selection surface.switchis an expression form; statement-styleswitchis ordinaryExprStmtusage whose selected arm blocks evaluate to unit.- Each
switcharm contains a block and may therefore useTailExprwhen the surrounding context expects a value. defaultis the canonical wildcard arm spelling forswitch;_is accepted as equivalent shorthand.switchpatterns 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.
asis reserved in expressions for explicit contract-view conversion only; it does not denote a general cast surface in v1 core.thisis an implicit receiver name available only inside struct method andctorbodies.applyis right-associative:f1 apply f2 apply xparses asf1 apply (f2 apply x).f1 apply f2 apply f3 apply paramsparses asf1 apply (f2 apply (f3 apply params)).f()is exact surface sugar forf apply ().f(e)is exact surface sugar forf apply e.f(e1, e2, ..., en)forn >= 2is exact surface sugar forf apply (e1, e2, ..., en).TupleExpris a value expression and is distinct fromGroupExpr.TupleExprarity 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 offn_name.- The second operand of
bindis a function identifier, not an arbitrary expression. - Mixing labeled and unlabeled tuple items in the same
TupleExpris 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
fnsignatures), - unresolved or ambiguous
fnbarrel signatures, - unresolved callback barrel entries,
- invalid
errordeclaration shape, - invalid
enumdeclaration shape, - invalid
structdeclaration shape, - invalid
implementsdeclaration shape, - invalid mixed implicit/explicit enum case identifiers,
- invalid return annotation shape (
->clauses), - invalid mixed
optional/resultfunction return surface, - invalid
result<Error>return surface, - invalid
optionaltype surface, - invalid
optional voidtype surface, - invalid named tuple type shape,
- use of return-surface
result<...>outside function return annotations, - invalid
handlemapping clauses (syntax form), - invalid
some(...)construction form, - invalid
noneusage without optional type context, - invalid
ok(...)construction form, - invalid
err(...)construction form, - use of
ok(...)orerr(...)outsidereturn, - invalid
bind(...)construction form, - invalid
elseextraction form, - invalid
ifexpression form, - invalid
switchexpression form, - invalid
forloop form, - invalid
newconstruction form, - invalid enum case path,
- invalid callback declaration shape,
- invalid
Selftype usage, - invalid
thisusage, - invalid struct method declaration shape,
- invalid
ctordeclaration 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
applyclause, - 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/continueoutside loops,- invalid assignment targets (
LValueviolations), - top-level executable statements,
constused outsidedeclare constorlet const,- 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.
Rules:
optional Tmay appear in any type position whereTypeRefis allowed.- The payload type is mandatory.
optional voidandoptional ()are invalid.- A single-slot payload is internally modeled as a one-field tuple payload.
optionalis 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(...)andnoneare built-in special forms, not ordinary identifiers.some(value)produces anoptionalvalue carrying the declared payload.noneproduces an emptyoptionalvalue.nonerepresents absence of value.nonemust 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
optissome(v), the expression evaluates tov. - If
optisnone, the expression evaluates tofallback. - The type of
fallbackmust 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 Tis equivalent toreturn 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()andopt.hasNone()are compiler-recognized intrinsic method surfaces overoptional.
13.7 Mutability and Copying
Rules:
optionalfollows normal value semantics for its declared payload.- Assigning an
optionalvalue 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.
Rules:
Emust be anerrortype declared viadeclare 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, field types, or generic type arguments.- 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(...)anderr(...)are built-in return special forms, not ordinary identifiers.ok(value)produces a success payload.err(label)produces a failure payload.labelmust match error typeE.ok(...)anderr(...)are not general-purpose value constructors in v1 core; they are valid only in function return flow forresult<Error>.
14.4 ! Propagation Operator
The ! operator propagates errors when error types match.
Syntax:
value = expr!
Rules:
exprmust be a call or expression producingresult<E> Payload.- The enclosing function must return
result<E> Payload2with the same error type. - If
expris success, expression evaluates to wrapped value. - If
expris error, the enclosing function returnserr(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:
exprmust produceresult<E1> Payload1.- The enclosing function must return
result<E2> Payload2. - Arms map
E1labels toE2labels. - Arms must be exhaustive (explicitly or with
_).
14.6 Restrictions
Rules:
- No implicit extraction of
result<E> Payload. result<Error>cannot be implicitly converted tooptionaland 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
handleis active in v1 core. matchis reserved for future pattern-matching constructs and is not part ofresultremapping in v1 core.
15. Canonical examples
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;
}