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

1213 lines
41 KiB
Markdown

# 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.
- A `.pbs` file 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 `.pbs` syntax 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`, `as`
- `service`, `host`, `fn`, `apply`, `bind`, `new`, `implements`, `using`, `ctor`
- `declare`, `struct`, `contract`, `error`, `enum`, `callback`, `Self`, `this`
- `pub`, `mut`
- `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:
- `mod`
- `type`
Reserved (not active syntax in v1 core):
- `spawn`, `yield`, `sleep`
- `match`
Note:
- `host` is used only by reserved SDK/toolchain `declare host` surfaces in v1 core; ordinary user-authored modules MUST reject those declarations.
- `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` 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
```ebnf
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.
- `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`, `host`, `error`, `enum`, or `service`.
- 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.
- A `host` barrel entry must resolve to exactly one top-level `declare host` declaration in module scope.
- A `struct` barrel entry governs the visibility of the struct declaration and of all methods declared directly in that struct body.
- A `host` barrel entry governs the visibility of the host declaration and of all method signatures declared directly in that host body.
- A `service` barrel 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`, or `service` are never exported independently through `mod.barrel`.
Example:
```barrel
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.
```ebnf
File ::= ImportDecl* TopDecl* EOF
```
### 6.1 Imports
Imports target modules, never files.
```ebnf
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.
```ebnf
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.
- `Slot(...)` is not part of the v1 attribute surface; source that uses `Slot` must be rejected in syntax phase.
### 6.2 Top-level declarations
```ebnf
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
```ebnf
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 `ctor` declarations.
- `pub field: T` permits read access from outside the enclosing struct, but external writes remain invalid.
- `pub mut field: T` permits both read and write access from outside the enclosing struct.
- `mut` is valid in a struct field declaration only immediately after `pub`.
- Methods and `ctor` bodies 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 `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 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`, or `implements` bodies.
- `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, or `implements` bodies.
- 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 `pub` but do not permit `mut`.
- Builtin method signatures MAY carry reserved VM-owned metadata such as `IntrinsicCall(...)`.
- `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 Owner using s { ... }` declares the contract implementation block for a declared `struct` or `service` named `Owner`, 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 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
```ebnf
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
```ebnf
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 value `Name`.
- A service body contains only method declarations.
- Service methods have an implicit receiver binding `this` of type `Self` and do not declare `this: Self` explicitly.
- Services do not declare fields.
- Services do not declare `ctor`.
- Services are never constructed with `new`.
- Service method visibility follows the `service` entry for that declaration in `mod.barrel`; service methods are never exported independently.
### 7.4 Functions
```ebnf
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 Reserved host binding surface
Rules:
- There is no top-level `host fn` declaration syntax in v1 core.
- Reserved host bindings are expressed only through top-level `declare host` declarations.
- `declare host` is reserved to SDK/toolchain-controlled modules and is not available to ordinary user-authored modules.
- Methods declared inside a `declare host` body 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 host` bindings.
### 7.6 Top-level constants
```ebnf
ConstDecl ::= AttrList? 'declare' 'const' Identifier ':' TypeRef ConstInitOpt ';'
ConstInitOpt ::= ('=' 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 is required unless another specification explicitly permits a reserved declaration-only shell such as builtin constant metadata.
- A `declare const` initializer, when present, MUST be a constant expression as defined by static semantics.
- `declare const` values 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
```ebnf
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.
- `SimpleType` may 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`, and `str`.
- Builtin simple types are always available in type position and do not require import.
- `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 declarations, service method declarations, and `ctor` declarations, and refers to the enclosing owner type.
## 9. Statements and blocks
```ebnf
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
```ebnf
Expr ::= HandleExpr
HandleExpr ::= 'handle' ElseExpr HandleMap | ElseExpr
HandleMap ::= '{' HandleArm (',' HandleArm)* ','? '}'
HandleArm ::= HandlePattern '->' (ErrorPath | Block)
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 | OkExpr | ErrExpr | UnitExpr | TupleExpr | GroupExpr | Block
ThisExpr ::= 'this'
NewExpr ::= 'new' NewTarget '(' ArgList? ')'
NewTarget ::= Identifier ('.' Identifier)?
BindExpr ::= 'bind' '(' Expr ',' Identifier ')'
SomeExpr ::= 'some' '(' Expr ')'
NoneExpr ::= 'none'
OkExpr ::= 'ok' '(' Expr ')'
ErrExpr ::= 'err' '(' ErrorPath ')'
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, service method, and `ctor` bodies.
- `Self` is valid only inside struct method declarations, service method declarations, and `ctor` declarations.
- `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, 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 `fn` signatures),
- invalid `error` declaration shape,
- invalid `enum` declaration shape,
- invalid `struct` declaration shape,
- invalid `host` declaration shape,
- invalid `service` 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 `ok(...)` construction form,
- invalid `err(...)` construction form,
- use of `ok(...)` or `err(...)` outside allowed result-flow positions,
- 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 struct field access modifier surface,
- invalid struct method declaration shape,
- invalid service method declaration shape,
- invalid `ctor` declaration shape,
- duplicate enum case label,
- duplicate enum case identifier,
- 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`,
- 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 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 trap behavior of its own,
- explicit fallback extraction.
### 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:
```pbs
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:
```pbs
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.
- `fallback` is evaluated only when `opt` is `none`.
### 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:
```pbs
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,
- 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 through special result-flow forms:
```pbs
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 result-flow 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 and in `handle` arm blocks.
### 14.4 `!` Propagation Operator
The `!` operator propagates errors when error types match.
Syntax:
```pbs
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 Result Processing with `handle`
`handle` is the active construct for extracting success values while processing typed `result` errors.
Syntax:
```pbs
value = handle expr {
ErrorA.case1 -> ErrorB.mapped1,
ErrorA.case2 -> {
ok(fallback_value)
},
_ -> {
err(ErrorB.default)
},
};
```
Rules:
- `expr` must produce `result<E1> Payload1`.
- The enclosing function must return `result<E2> Payload2`.
- Each arm matches one source error case or `_`.
- Arms must be exhaustive (explicitly or with `_`).
- A short arm of the form `E1.case -> E2.case` is sugar for a block arm that returns `err(E2.case)`.
- A block arm must terminate with either `ok(payload)` or `err(E2.case)`.
- `ok(payload)` recovers locally and makes the `handle` expression yield `payload`.
- `err(E2.case)` performs immediate enclosing-function return with `err(E2.case)`.
- `handle` does not intercept `trap`.
### 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 Reserved host declaration
```pbs
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
```pbs
declare const A: int = 1;
fn demo() -> int {
let const a: int = 2;
return A + a;
}
```
### 15.3 Function with loops and assignment
```pbs
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
```pbs
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
```pbs
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
```pbs
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
```pbs
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
```pbs
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
```pbs
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
```pbs
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
```pbs
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
```pbs
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
```pbs
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;
}
```