40 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. - A
.pbsfile may be loaded either as an ordinary project module or as a reserved stdlib interface module. - Top-level executable statements are forbidden.
Interface-module note:
- Reserved stdlib interface modules use the same
.pbssyntax surface as ordinary modules. - Whether a file is interpreted in ordinary mode or interface-module mode is determined by its containing reserved stdlib project space, not by a distinct file extension.
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,thispub,mutlet,constif,else,switch,default,for,from,until,step,while,break,continue,returnvoid,optional,resultsome,none,ok,errhandletrue,false,and,or,not
Barrel-only keywords:
modtype
Reserved (not active syntax in v1 core):
alloc,borrow,mutate,peek,take,weakspawn,yield,sleepmatch
Note:
hostis used only by reserved SDK/toolchaindeclare hostsurfaces in v1 core; ordinary user-authored modules MUST reject those declarations.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)
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 or a top-level standalone pub modifier inside .pbs files is a syntax error.
Inside ordinary .pbs source, pub and mut are reserved for struct field declarations, with mut valid only immediately after pub.
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
| BarrelHostItem
| BarrelErrorItem
| BarrelEnumItem
| BarrelServiceItem
| BarrelConstItem
| BarrelCallbackItem
BarrelVisibility ::= 'mod' | 'pub'
BarrelFnItem ::= BarrelVisibility 'fn' Identifier ParamList ReturnAnn? ';'
BarrelStructItem ::= BarrelVisibility 'struct' Identifier ';'
BarrelContractItem ::= BarrelVisibility 'contract' Identifier ';'
BarrelHostItem ::= BarrelVisibility 'host' 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,host,error,enum, orservice. - 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. - A
hostbarrel entry must resolve to exactly one top-leveldeclare hostdeclaration in module scope. - A
structbarrel entry governs the visibility of the struct declaration and of all methods declared directly in that struct body. - A
hostbarrel entry governs the visibility of the host declaration and of all method signatures declared directly in that host body. - A
servicebarrel entry governs the visibility of the service declaration, its canonical singleton value, and all methods declared directly in that service body. - Methods declared inside a
struct,host, orserviceare never exported independently throughmod.barrel.
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 host Gfx;
pub enum GameState;
pub service Physics;
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.1.1 Attributes
Attributes are bracketed compile-time metadata forms.
AttrList ::= Attribute+
Attribute ::= '[' Identifier AttrArgs? ']'
AttrArgs ::= '(' AttrArgList? ')'
AttrArgList ::= AttrArg (',' AttrArg)*
AttrArg ::= Identifier '=' AttrValue
AttrValue ::= StringLit | IntLit | BoolLit
Rules:
- An attribute attaches syntactic metadata to the declaration surface that immediately follows it.
- Attributes do not introduce values, types, callables, or modules by themselves.
- In v1 core, ordinary user-authored modules MUST reject attributes unless another specification explicitly permits them.
- Reserved stdlib/toolchain interface modules MAY use attributes where explicitly allowed by syntax and semantics.
- Attribute interpretation is compile-time only unless another specification explicitly lowers its effect into runtime-facing metadata.
- Reserved attribute names used by builtin-type and intrinsic shells do not become ordinary value-level syntax.
6.2 Top-level declarations
TopDecl ::= TypeDecl | CallbackDecl | FunctionDecl | ConstDecl | ImplDecl
Top-level let forms and top-level executable statements are forbidden.
declare host and declare builtin type are reserved to SDK/toolchain-controlled modules; ordinary user-authored modules MUST reject them.
7. Declarations
7.1 Type declarations
TypeDecl ::= StructDecl | ServiceDecl | ContractDecl | HostDecl | BuiltinTypeDecl | ErrorDecl | EnumDecl
StructDecl ::= 'declare' 'struct' Identifier '(' StructFieldList? ')' (StructMethodBody | ';')
ServiceDecl ::= 'declare' 'service' Identifier ServiceBody
ContractDecl ::= 'declare' 'contract' Identifier ContractBody
HostDecl ::= 'declare' 'host' Identifier HostBody
BuiltinTypeDecl ::= AttrList? 'declare' 'builtin' 'type' Identifier '(' BuiltinFieldList? ')' BuiltinTypeBody
ErrorDecl ::= 'declare' 'error' Identifier ErrorBody
EnumDecl ::= 'declare' 'enum' Identifier '(' EnumCaseDecl (',' EnumCaseDecl)* ','? ')' ';'
StructFieldList ::= StructFieldDecl (',' StructFieldDecl)* ','?
StructFieldDecl ::= StructFieldAccess? Identifier ':' TypeRef
StructFieldAccess ::= 'pub' MutOpt
BuiltinFieldList ::= BuiltinFieldDecl (',' BuiltinFieldDecl)* ','?
BuiltinFieldDecl ::= AttrList? BuiltinFieldAccess? Identifier ':' TypeRef
BuiltinFieldAccess ::= 'pub'
MutOpt ::= 'mut'?
StructMethodBody ::= '{' StructMemberDecl* '}'
StructMemberDecl ::= StructMethodDecl | StructCtorDecl
StructMethodDecl ::= 'fn' Identifier ParamList ReturnAnn? Block
StructCtorDecl ::= 'ctor' Identifier ParamList Block
ContractBody ::= '{' FnSigDecl* '}'
BuiltinTypeBody ::= '{' FnSigDecl* '}'
ErrorBody ::= '{' ErrorCaseDecl+ '}'
FieldDecl ::= Identifier ':' TypeRef ';'
FnSigDecl ::= AttrList? 'fn' Identifier ParamList ReturnAnn? ';'
HostBody ::= '{' FnSigDecl* '}'
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.
- A struct field with no access modifier is private to the enclosing struct body and its
ctordeclarations. pub field: Tpermits read access from outside the enclosing struct, but external writes remain invalid.pub mut field: Tpermits both read and write access from outside the enclosing struct.mutis valid in a struct field declaration only immediately afterpub.- Methods and
ctorbodies of the enclosing struct may read and write all of that struct's fields regardless of field access modifier. - 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. - Multiple
ctordeclarations with the same name are allowed only when their parameter signatures differ. - 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 host Name { ... }declares a reserved SDK/toolchain host-binding owner with host-backed method signatures only.- A host declaration body contains only function signatures and no bodies.
- Host declarations are reserved to SDK/toolchain-controlled modules and MUST be rejected in ordinary user-authored modules.
- Host declarations do not declare fields,
ctor, orimplementsbodies. declare builtin type Name(...) { ... }declares a reserved SDK/toolchain builtin type shell with field-like projection metadata in the header and signature-only VM-owned member surfaces in the body.- A builtin type declaration body contains only function signatures and no bodies.
- Builtin type declarations are reserved to SDK/toolchain-controlled modules and MUST be rejected in ordinary user-authored modules.
- Builtin type declarations do not declare
ctor, executable methods, orimplementsbodies. - Builtin type field entries are projection declarations, not ordinary struct storage fields.
- Builtin type field entries may carry attributes where another specification explicitly permits them.
- Builtin type field entries permit
pubbut do not permitmut. - Builtin method signatures MAY carry reserved VM-owned metadata such as
IntrinsicCall(...). 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 Owner using s { ... }declares the contract implementation block for a declaredstructorservicenamedOwner, 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 or service singleton 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 ::= 'declare' 'service' Identifier ServiceBody
ServiceBody ::= '{' ServiceMember* '}'
ServiceMember ::= 'fn' Identifier ParamList ReturnAnn? Block
Rules:
declare service Name { ... }declares a nominal service type together with its canonical module-owned singleton valueName.- A service body contains only method declarations.
- Service methods have an implicit receiver binding
thisof typeSelfand do not declarethis: Selfexplicitly. - Services do not declare fields.
- Services do not declare
ctor. - Services are never constructed with
new. - Service method visibility follows the
serviceentry for that declaration inmod.barrel; service methods are never exported independently.
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 Reserved host binding surface
Rules:
- There is no top-level
host fndeclaration syntax in v1 core. - Reserved host bindings are expressed only through top-level
declare hostdeclarations. declare hostis reserved to SDK/toolchain-controlled modules and is not available to ordinary user-authored modules.- Methods declared inside a
declare hostbody are signature-only host-backed call surfaces. - A host method signature in a reserved stdlib/interface module MAY carry attributes.
[Host(module = "...", name = "...", version = N)]is the canonical reserved attribute surface for host-binding metadata in v1 core.- Service-shaped wrappers over host calls MAY exist as SDK/stdlib design patterns, but they do not replace canonical
declare hostbindings.
7.6 Top-level constants
ConstDecl ::= AttrList? 'declare' 'const' Identifier ':' TypeRef ConstInitOpt ';'
ConstInitOpt ::= ('=' Expr)?
Rules:
- Top-level constants MUST use
declare const. declare constis a declaration and may be exported throughmod.barrel.- A
declare constinitializer is required unless another specification explicitly permits a reserved declaration-only shell such as builtin constant metadata. - A
declare constinitializer, when present, MUST be a constant expression as defined by static semantics. declare constvalues are compile-time constants, not runtime-initialized module storage.- A reserved top-level builtin constant shell MAY use attributes such as
BuiltinConst(...)and omit the initializer when another specification explicitly defines VM-owned materialization.
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.
SimpleTypemay denote either a visible nominal type identifier or one of the predeclared builtin simple types.- The predeclared builtin simple types in v1 core are
int,float,bool, andstr. - Builtin simple types are always available in type position and do not require import.
optional Tis the canonical optional-type syntax in all type positions.optional voidandoptional ()are invalid.Selfis valid only inside struct method declarations, service method declarations, andctordeclarations, and refers to the enclosing owner 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
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).- General indexing syntax (
expr[index]) is not part of PBS core v1. 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 | 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 < 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.- The right operand of expression-form
asis a simple contract identifier already visible in scope, never a qualified module path. thisis an implicit receiver name available only inside struct method, service method, andctorbodies.Selfis valid only inside struct method declarations, service method declarations, andctordeclarations.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, service, host, 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:
Diagnostics that require name resolution, overload resolution, or type context are defined by the Static Semantics specification and are not duplicated here.
- missing
mod.barrel, - duplicate barrel entries (including duplicate
fnsignatures), - invalid
errordeclaration shape, - invalid
enumdeclaration shape, - invalid
structdeclaration shape, - invalid
hostdeclaration shape, - invalid
servicedeclaration 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
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 struct field access modifier surface,
- invalid struct method declaration shape,
- invalid service method declaration shape,
- invalid
ctordeclaration shape, - duplicate enum case label,
- duplicate enum case identifier,
- 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,- 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 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.
It is a special core-language return form, not evidence of general generic-type support in v1.
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, 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(...)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
This section is the canonical reference example set for PBS v1 syntax.
It consolidates the primary surface forms used by tooling and conformance for:
applyand call sugar,bindand 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 Reserved host declaration
declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)]
fn draw_pixel(x: int, y: int, c: color);
}
This surface is reserved to SDK/toolchain-controlled modules and is not available to ordinary user-authored PBS v1 core source.
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;
declare 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(pub 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,
pub b: str,
pub mut 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 visible = s.b;
s.c = 4.0;
let direct = s.compute_something();
let via_contract = sp.stasis();
let _ = s2.compute_something();
let _ = sp2.stasis();
let _ = visible;
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;
}