# 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`, `global`, `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 | BarrelGlobalItem | 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 ';' BarrelGlobalItem ::= BarrelVisibility 'global' 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. - A `global` barrel entry governs visibility of one top-level `declare global` declaration. - 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 global Palette; 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. - Ordinary userland source MAY use `[Init]` and `[Frame]` only on top-level `fn` declarations. - Reserved stdlib/toolchain interface modules MAY use attributes where explicitly allowed by syntax and semantics. - Reserved stdlib/toolchain interface modules MAY additionally use `[InitAllowed]` only on `declare host` method signatures where another specification explicitly permits it. - 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 | GlobalDecl | 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 GlobalDecl ::= 'declare' 'global' Identifier ':' TypeRef '=' Expr ';' ``` 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 `;`. - `declare global Name: T = expr;` declares module-owned runtime storage. - `declare global` requires an explicit type annotation and an explicit initializer. - `declare global` is distinct from `declare const` and is not syntactic sugar for `const`. - `declare global` participates in `mod.barrel` visibility only through explicit `global` entries. - The admissible initializer slice for `declare global` is intentionally restricted by static semantics. - 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` 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` in function return surface wraps exactly one payload shape and one error type. - `-> result T` is internally normalized as `result` for single-slot payloads. - `-> result` is valid and equivalent to `-> result 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. - `[InitAllowed]` is a reserved SDK-only attribute surface for host methods admitted during init. - 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` 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` This section defines the `result` return surface. All rules in this section are normative at syntax-contract level. `result` 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` 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` 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`. - A single-slot payload is internally modeled as a one-field tuple payload. - `result` cannot be used for local variable annotations, parameter annotations, or field types. - In function return surface, `result` is the only effect wrapper in use for that declaration. ### 14.3 Construction Surface A `result` 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 Payload`. - The enclosing function must return `result 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 Payload1`. - The enclosing function must return `result 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 Payload`. - `result` cannot be implicitly converted to `optional` and vice versa. - Falling off the end of a function returning `result` 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`, - `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 (left: int, right: int) { return ok((left: 1, right: 2)); } fn pair_or_error_fail() -> result (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 int { return ok(1); } fn outer_propagate() -> result int { let v: int = inner()!; return ok(v + 1); } fn outer_remap() -> result 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; } ```