diff --git a/docs/specs/pbs/PBS - Canonical Addenda.md b/docs/specs/pbs/PBS - Canonical Addenda.md deleted file mode 100644 index f5e34d40..00000000 --- a/docs/specs/pbs/PBS - Canonical Addenda.md +++ /dev/null @@ -1,359 +0,0 @@ -# PBS v0 Canonical Addenda - -> **Purpose:** eliminate ambiguity for Junie and for golden tests. -> -> This document is **normative** for PBS Frontend v0 and complements: -> -> * **PBS Frontend Spec v0 — Implementer Edition** -> * **Junie PR Plan** -> -> These addenda define: -> -> 1. operator precedence & associativity -> 2. canonical AST JSON shape (v0) -> 3. canonical diagnostic codes (v0) - ---- - -## 1) Operator Precedence and Associativity (v0) - -### 1.1 Guiding rule - -PBS v0 prioritizes **minimal ambiguity** and **easy parsing**. - -* Most operators are **left-associative**. -* Assignment is **not** an expression in v0 (no `=` operator expressions). -* Member access and indexing are not part of v0 surface syntax unless already defined elsewhere. - -### 1.2 Precedence table - -From **highest** to **lowest**: - -1. **Primary** - - * literals (`10`, `3.14`, `"text"`, `none`, `some(x)`, `ok(x)`, `err(e)`) - * identifiers (`foo`) - * parenthesized expression (`(expr)`) - * block expression (`{ ... }`) (when allowed as `Expr`) - -2. **Call** (left-associative) - - * `callee(arg1, arg2, ...)` - -3. **Unary prefix** (right-associative) - - * `-expr` - * `!expr` - * `as` casts are **not unary**; see level 6. - -4. **Multiplicative** (left-associative) - - * `*`, `/`, `%` - -5. **Additive** (left-associative) - - * `+`, `-` - -6. **Cast** (left-associative) - - * `expr as Type` - -7. **Comparison** (non-associative) - - * `<`, `<=`, `>`, `>=` - -8. **Equality** (non-associative) - - * `==`, `!=` - -9. **Logical AND** (left-associative) - - * `&&` - -10. **Logical OR** (left-associative) - - * `||` - -11. **Control expressions** (special) - - * `if ... { ... } else { ... }` (expression form only if your v0 allows it; otherwise statement) - * `when { ... }` (expression) - -### 1.3 Non-associative rule - -Comparison and equality are **non-associative**: - -* `a < b < c` is an error -* `a == b == c` is an error - -Diagnostic: `E_PARSE_NON_ASSOC`. - -### 1.4 Notes on `when` - -`when` binds weaker than all binary operators. - -Example: - -```pbs -let x = a + b when { ... }; -``` - -Parses as: - -``` -let x = (a + b) when { ... }; -``` - ---- - -## 2) Canonical AST JSON (v0) - -### 2.1 Canonicalization goals - -Canonical AST JSON is used for: - -* golden tests -* frontend determinism validation -* diff-friendly debugging - -Rules: - -* JSON keys are **stable** and **ordered** (when writing JSON) -* All nodes include `kind` and `span` -* Spans are byte offsets into the file content - -### 2.2 Span encoding - -```json -{"file":"main.pbs","start":12,"end":18} -``` - -Where: - -* `start` is inclusive -* `end` is exclusive - -### 2.3 Root - -```json -{ - "kind": "File", - "span": {"file":"...","start":0,"end":123}, - "imports": [ ... ], - "decls": [ ... ] -} -``` - -### 2.4 Import node - -```json -{ - "kind": "Import", - "span": {"file":"...","start":0,"end":20}, - "spec": {"kind":"ImportSpec","path":["Foo","Bar"]}, - "from": "./lib.pbs" -} -``` - -`ImportSpec.path` is an array of identifiers. - -### 2.5 Declarations - -#### 2.5.1 Service - -```json -{ - "kind": "ServiceDecl", - "span": {"file":"...","start":0,"end":50}, - "vis": "pub", - "name": "Audio", - "extends": null, - "members": [ ... ] -} -``` - -A service member (method signature only in v0): - -```json -{ - "kind": "ServiceFnSig", - "span": {"file":"...","start":0,"end":10}, - "name": "play", - "params": [ {"name":"sound","ty": {"kind":"TypeName","name":"Sound"}} ], - "ret": {"kind":"TypeName","name":"void"} -} -``` - -#### 2.5.2 Function - -```json -{ - "kind": "FnDecl", - "span": {"file":"...","start":0,"end":80}, - "name": "main", - "params": [], - "ret": null, - "else": null, - "body": {"kind":"Block", ... } -} -``` - -#### 2.5.3 TypeDecl (struct/contract/error) - -```json -{ - "kind": "TypeDecl", - "span": {"file":"...","start":0,"end":100}, - "vis": "pub", - "typeKind": "struct", - "name": "Vector", - "body": {"kind":"TypeBody","members":[ ... ]} -} -``` - -### 2.6 Blocks and statements - -Block: - -```json -{ - "kind": "Block", - "span": {"file":"...","start":0,"end":20}, - "stmts": [ ... ], - "tail": null -} -``` - -* `stmts` are statements. -* `tail` is an optional final expression (only if your parser supports expression blocks). - -Statement kinds (v0 minimum): - -* `LetStmt` -* `ExprStmt` -* `ReturnStmt` - -Let: - -```json -{ - "kind": "LetStmt", - "span": {"file":"...","start":0,"end":20}, - "name": "x", - "isMut": false, - "ty": null, - "init": {"kind":"IntLit", "value": 10, "span": ...} -} -``` - -Return: - -```json -{ - "kind": "ReturnStmt", - "span": {"file":"...","start":0,"end":10}, - "expr": null -} -``` - -### 2.7 Expressions - -All expressions include `kind` and `span`. - -Minimal v0 expression node kinds: - -* `IntLit` `{ value: i64 }` -* `FloatLit` `{ value: f64 }` -* `BoundedLit` `{ value: u32 }` -* `StringLit` `{ value: string }` -* `Ident` `{ name: string }` -* `Call` `{ callee: Expr, args: Expr[] }` -* `Unary` `{ op: "-"|"!", expr: Expr }` -* `Binary` `{ op: string, left: Expr, right: Expr }` -* `Cast` `{ expr: Expr, ty: TypeRef }` -* `IfExpr` `{ cond: Expr, then: Block, els: Block }` (if expression is supported) -* `WhenExpr` `{ arms: WhenArm[] }` - -Type references: - -```json -{"kind":"TypeName","name":"int"} -``` - -Generics: - -```json -{"kind":"TypeApp","base":"optional","args":[{"kind":"TypeName","name":"int"}]} -``` - -### 2.8 Canonical JSON ordering - -When writing AST JSON, always order fields as: - -1. `kind` -2. `span` -3. semantic fields (stable order) - -This makes diffs deterministic. - ---- - -## 3) Diagnostic Codes (v0) - -### 3.1 Diagnostic format - -All diagnostics must be serializable to canonical JSON: - -```json -{ - "severity": "error", - "code": "E_PARSE_UNEXPECTED_TOKEN", - "message": "Unexpected token '}'", - "span": {"file":"main.pbs","start":12,"end":13} -} -``` - -Severity is one of: `error`, `warning`. - -### 3.2 Parse/Lex errors (E_PARSE_*) - -* `E_LEX_INVALID_CHAR` — invalid character -* `E_LEX_UNTERMINATED_STRING` — string literal not closed -* `E_PARSE_UNEXPECTED_TOKEN` — token not expected in current context -* `E_PARSE_EXPECTED_TOKEN` — missing required token -* `E_PARSE_NON_ASSOC` — chained comparison/equality (non-associative) - -### 3.3 Symbol/Resolve errors (E_RESOLVE_*) - -* `E_RESOLVE_UNDEFINED` — undefined identifier -* `E_RESOLVE_DUPLICATE_SYMBOL` — duplicate symbol in same namespace -* `E_RESOLVE_NAMESPACE_COLLISION` — name exists in both type and value namespaces -* `E_RESOLVE_VISIBILITY` — symbol not visible from this scope/module -* `E_RESOLVE_INVALID_IMPORT` — import spec/path invalid - -### 3.4 Type errors (E_TYPE_*) - -* `E_TYPE_MISMATCH` — type mismatch -* `E_TYPE_UNKNOWN_TYPE` — unknown type name -* `E_TYPE_MUTABILITY` — mutability violation -* `E_TYPE_RETURN_PATH` — not all paths return a value -* `E_TYPE_INVALID_CAST` — invalid cast - -### 3.5 Lowering errors (E_LOWER_*) - -* `E_LOWER_UNSUPPORTED` — feature not supported in v0 lowering - -### 3.6 Warnings (W_*) - -Warnings are allowed in v0 but should be used sparingly. - -* `W_UNUSED_LET` — unused local binding -* `W_SHADOWING` — local shadows another binding - ---- - -## Implementation Notes (Non-normative) - -* Keep these addenda in `spec/` in the repo. -* Use them to drive golden tests for AST and diagnostics. -* If a future change alters canonical AST, bump a version and regenerate goldens deliberately. diff --git a/docs/specs/pbs/PBS - Module and Linking Model.md b/docs/specs/pbs/PBS - Module and Linking Model.md deleted file mode 100644 index f6aaa886..00000000 --- a/docs/specs/pbs/PBS - Module and Linking Model.md +++ /dev/null @@ -1,321 +0,0 @@ -# Prometeu PBS v0 — Unified Project, Module, Linking & Execution Specification - -> **Status:** Canonical / Replaces all previous module & linking specs -> -> This document **fully replaces**: -> -> * "PBS – Module and Linking Model" -> * Any partial or implicit module/linking descriptions in earlier PBS documents -> -> After this document, there must be **no parallel or competing spec** describing project structure, modules, imports, or linking for PBS v0. - ---- - -## 1. Purpose - -This specification defines the **single authoritative model** for how a Prometeu PBS v0 program is: - -1. Organized as a project -2. Structured into modules -3. Resolved and linked at compile time -4. Emitted as one executable bytecode blob -5. Loaded and executed by the Prometeu Virtual Machine - -The primary objective is to **eliminate ambiguity** by enforcing a strict separation of responsibilities: - -* **Compiler / Tooling**: all symbolic, structural, and linking work -* **Runtime / VM**: verification and execution only - ---- - -## 2. Core Principles - -### 2.1 Compiler Finality Principle - -All operations involving **names, symbols, structure, or intent** must be completed at compile time. - -The VM **never**: - -* Resolves symbols or names -* Loads or links multiple modules -* Applies relocations or fixups -* Interprets imports or dependencies - -### 2.2 Single-Blob Execution Principle - -A PBS v0 program is executed as **one fully linked, self-contained bytecode blob**. - -At runtime there is no concept of: - -* Projects -* Modules -* Imports -* Dependencies - -These concepts exist **only in the compiler**. - ---- - -## 3. Project Model - -### 3.1 Project Root - -A Prometeu project is defined by a directory containing: - -* `prometeu.json` — project manifest (required) -* One or more module directories - -### 3.2 `prometeu.json` Manifest - -The project manifest is mandatory and must define: - -```json -{ - "name": "example_project", - "version": "0.1.0", - "dependencies": { - "core": "../core", - "input": "../input" - } -} -``` - -#### Fields - -* `name` (string, required) - - * Canonical project identifier -* `version` (string, required) -* `dependencies` (map, optional) - - * Key: dependency project name - * Value: filesystem path or resolver hint - -Dependency resolution is **purely a compiler concern**. - ---- - -## 4. Module Model (Compile-Time Only) - -### 4.1 Module Definition - -* A module is a directory inside a project -* Each module contains one or more `.pbs` source files - -### 4.2 Visibility Rules - -Visibility is enforced **exclusively at compile time**: - -* `file`: visible only within the same source file -* `mod`: visible within the same module -* `pub`: visible to importing modules or projects - -The VM has **zero awareness** of visibility. - ---- - -## 5. Imports & Dependency Resolution - -### 5.1 Import Syntax - -Imports reference **projects and modules**, never files: - -``` -import @core:math -import @input:pad -``` - -### 5.2 Resolution Pipeline - -The compiler performs the following phases: - -1. Project dependency graph resolution (via `prometeu.json`) -2. Module discovery -3. Symbol table construction -4. Name and visibility resolution -5. Type checking - -Any failure aborts compilation and **never reaches the VM**. - ---- - -## 6. Linking Model (Compiler Responsibility) - -### 6.1 Link Stage - -After semantic validation, the compiler executes a **mandatory link stage**. - -The linker: - -* Assigns final `func_id` indices -* Assigns constant pool indices -* Computes final `code_offset` and `code_len` -* Resolves all jumps and calls -* Merges all module bytecode into one contiguous code segment - -### 6.2 Link Output Format - -The output of linking is a **Linked PBS Program** with the following layout: - -```text -[ Header ] -[ Constant Pool ] -[ Function Table ] -[ Code Segment ] -``` - -All references are: - -* Absolute -* Final -* Fully resolved - -No relocations or fixups remain. - ---- - -## 7. Runtime Execution Contract - -### 7.1 VM Input Requirements - -The Prometeu VM accepts **only linked PBS blobs**. - -It assumes: - -* All function references are valid -* All jumps target instruction boundaries -* No unresolved imports exist - -### 7.2 VM Responsibilities - -The VM is responsible for: - -1. Loading the bytecode blob -2. Structural and control-flow verification -3. Stack discipline verification -4. Deterministic execution - -The VM **must not**: - -* Perform linking -* Resolve symbols -* Modify code offsets -* Load multiple modules - ---- - -## 8. Errors and Runtime Traps - -### 8.1 Compile-Time Errors - -Handled exclusively by the compiler: - -* Unresolved imports -* Visibility violations -* Type errors -* Circular dependencies - -These errors **never produce bytecode**. - -### 8.2 Runtime Traps - -Runtime traps represent **deterministic execution faults**, such as: - -* Stack underflow -* Invalid local access -* Invalid syscall invocation -* Explicit `TRAP` opcode - -Traps are part of the **execution model**, not debugging. - ---- - -## 9. Versioning and Scope - -### 9.1 PBS v0 Guarantees - -PBS v0 guarantees: - -* Single-blob execution -* No runtime linking -* Deterministic behavior - -### 9.2 Out of Scope for v0 - -The following are explicitly excluded from PBS v0: - -* Dynamic module loading -* Runtime imports -* Hot reloading -* Partial linking - ---- - -## 10. Canonical Ownership Summary - -| Concern | Owner | -| ----------------- | ------------- | -| Project structure | Compiler | -| Dependencies | Compiler | -| Modules & imports | Compiler | -| Linking | Compiler | -| Bytecode format | Bytecode spec | -| Verification | VM | -| Execution | VM | - -> **Rule of thumb:** -> If it requires names, symbols, or intent → compiler. -> If it requires bytes, slots, or PCs → VM. - ---- - -## 11. Final Note - -After adoption of this document: - -* Any existing or future document describing PBS modules or linking **must defer to this spec** -* Any behavior conflicting with this spec is considered **non-compliant** -* The Prometeu VM is formally defined as a **pure executor**, not a linker - ---- - -## Addendum — `prometeu.json` and Dependency Management - -This specification intentionally **does not standardize the full dependency resolution algorithm** for `prometeu.json`. - -### Scope Clarification - -* `prometeu.json` **defines project identity and declared dependencies only**. -* **Dependency resolution, fetching, version selection, and conflict handling are responsibilities of the Prometeu Compiler**, not the VM and not the runtime bytecode format. -* The Virtual Machine **never reads or interprets `prometeu.json`**. - -### Compiler Responsibility - -The compiler is responsible for: - -* Resolving dependency sources (`path`, `git`, registry, etc.) -* Selecting versions (exact, range, or `latest`) -* Applying aliasing / renaming rules -* Detecting conflicts and incompatibilities -* Producing a **fully linked, closed-world Program Image** - -After compilation and linking: - -* All symbols are resolved -* All function indices are fixed -* All imports are flattened into the final bytecode image - -The VM consumes **only the resulting bytecode blob** and associated metadata. - -### Separate Specification - -A **dedicated specification** will define: - -* The complete schema of `prometeu.json` -* Dependency version semantics -* Resolution order and override rules -* Tooling expectations (compiler, build system, CI) - -This addendum exists to explicitly state the boundary: - -> **`prometeu.json` is a compiler concern; dependency management is not part of the VM or bytecode execution model.** diff --git a/docs/specs/pbs/PBS - prometeu.json specs.md b/docs/specs/pbs/PBS - prometeu.json specs.md deleted file mode 100644 index 0288b370..00000000 --- a/docs/specs/pbs/PBS - prometeu.json specs.md +++ /dev/null @@ -1,268 +0,0 @@ -# Prometeu.json — Project Manifest Specification - -## Status - -Draft · Complementary specification to the PBS Linking & Module Model - -## Purpose - -`prometeu.json` is the **project manifest** for Prometeu-based software. - -Its role is to: - -* Identify a Prometeu project -* Declare its dependencies -* Provide **input metadata to the compiler and linker** - -It is **not** consumed by the Virtual Machine. - ---- - -## Design Principles - -1. **Compiler-owned** - - * Only the Prometeu Compiler reads `prometeu.json`. - * The VM and runtime never see this file. - -2. **Declarative, not procedural** - - * The manifest declares *what* the project depends on, not *how* to resolve it. - -3. **Closed-world output** - - * Compilation + linking produce a single, fully resolved bytecode blob. - -4. **Stable identity** - - * Project identity is explicit and versioned. - ---- - -## File Location - -`prometeu.json` must be located at the **root of the project**. - ---- - -## Top-level Structure - -```json -{ - "name": "my_project", - "version": "0.1.0", - "kind": "app", - "dependencies": { - "std": { - "git": "https://github.com/prometeu/std", - "version": ">=0.2.0" - } - } -} -``` - ---- - -## Fields - -### `name` - -**Required** - -* Logical name of the project -* Used as the **default module namespace** - -Rules: - -* ASCII lowercase recommended -* Must be unique within the dependency graph - -Example: - -```json -"name": "sector_crawl" -``` - ---- - -### `version` - -**Required** - -* Semantic version of the project -* Used by the compiler for compatibility checks - -Format: - -``` -MAJOR.MINOR.PATCH -``` - ---- - -### `kind` - -**Optional** (default: `app`) - -Defines how the project is treated by tooling. - -Allowed values: - -* `app` — executable program -* `lib` — reusable module/library -* `system` — firmware / system component - ---- - -### `dependencies` - -**Optional** - -A map of **dependency aliases** to dependency specifications. - -```json -"dependencies": { - "alias": { /* spec */ } -} -``` - -#### Alias semantics - -* The **key** is the name by which the dependency is referenced **inside this project**. -* It acts as a **rename / namespace alias**. - -Example: - -```json -"dependencies": { - "gfx": { - "path": "../prometeu-gfx" - } -} -``` - -Internally, the dependency will be referenced as `gfx`, regardless of its original project name. - ---- - -## Dependency Specification - -Each dependency entry supports the following fields. - -### `path` - -Local filesystem dependency. - -```json -{ - "path": "../std" -} -``` - -Rules: - -* Relative paths are resolved from the current `prometeu.json` -* Absolute paths are allowed but discouraged - ---- - -### `git` - -Git-based dependency. - -```json -{ - "git": "https://github.com/prometeu/std", - "version": "^0.3.0" -} -``` - -The compiler is responsible for: - -* Cloning / fetching -* Version selection -* Caching - ---- - -### `version` - -Optional version constraint. - -Examples: - -* Exact: - - ```json - "version": "0.3.1" - ``` - -* Range: - - ```json - "version": ">=0.2.0 <1.0.0" - ``` - -* Latest: - - ```json - "version": "latest" - ``` - -Semantics are defined by the compiler. - ---- - -## Resolution Model (Compiler-side) - -The compiler must: - -1. Load root `prometeu.json` -2. Resolve all dependencies recursively -3. Apply aliasing rules -4. Detect: - - * Cycles - * Version conflicts - * Name collisions -5. Produce a **flat module graph** -6. Invoke the linker to generate a **single Program Image** - ---- - -## Interaction with the Linker - -* `prometeu.json` feeds the **module graph** -* The linker: - - * Assigns final function indices - * Fixes imports/exports - * Emits a closed bytecode image - -After linking: - -> No module boundaries or dependency information remain at runtime. - ---- - -## Explicit Non-Goals - -This specification does **not** define: - -* Lockfiles -* Registry formats -* Caching strategies -* Build profiles -* Conditional dependencies - -These may be added in future specs. - ---- - -## Summary - -* `prometeu.json` is the **single source of truth for project identity and dependencies** -* Dependency management is **compiler-owned** -* The VM executes **only fully linked bytecode** - -This file completes the boundary between **project structure** and **runtime execution**. diff --git a/docs/specs/pbs/Prometeu Base Script (PBS) - Implementation Spec.md b/docs/specs/pbs/Prometeu Base Script (PBS) - Implementation Spec.md deleted file mode 100644 index 7946d8b7..00000000 --- a/docs/specs/pbs/Prometeu Base Script (PBS) - Implementation Spec.md +++ /dev/null @@ -1,446 +0,0 @@ -# Prometeu Base Script (PBS) - -## Frontend Spec v0 — Implementer Edition - -> **Normative specification for building a PBS frontend (lexer, parser, AST, resolver, typechecker) targeting the Prometeu Fantasy Console runtime.** -> -> This document is **not** a user guide. -> It exists to make PBS *implementable*, *deterministic*, and *testable*. - ---- - -## 0. Scope and Non‑Goals - -### 0.1 Scope - -This specification defines: - -* lexical structure (tokens) -* grammar and parsing rules -* canonical AST shapes -* frontend phases and their responsibilities -* name resolution and visibility rules -* type system rules -* desugaring and lowering rules -* diagnostic categories (errors vs warnings) -* required guarantees for runtime integration - -The goal is that **two independent frontends** built from this spec: - -* accept the same programs -* reject the same programs -* produce equivalent ASTs -* emit equivalent diagnostics - -### 0.2 Non‑Goals - -This spec does **not** define: - -* runtime performance characteristics -* bytecode layout -* JIT or interpreter design -* editor tooling or IDE features - -Those are explicitly out of scope. - ---- - -## 1. Frontend Pipeline Overview - -A PBS frontend **must** be structured as the following pipeline: - -``` -Source Text - ↓ -Lexer - ↓ -Parser - ↓ -Raw AST - ↓ -Symbol Collection - ↓ -Resolver - ↓ -Typed AST - ↓ -Desugaring / Lowering - ↓ -Runtime‑ready IR / AST -``` - -Each stage has **strict responsibilities**. -No stage may perform work assigned to a later stage. - ---- - -## 2. Lexical Structure - -### 2.1 Tokens - -A PBS lexer must recognize at minimum the following token classes: - -* identifiers -* keywords -* numeric literals -* string literals -* punctuation -* operators -* comments - -Whitespace is insignificant except as a separator. - ---- - -### 2.2 Keywords (Reserved) - -The following keywords are **reserved** and may not be used as identifiers: - -``` -import -pub -mod -service -fn -let -mut -declare -struct -contract -host -error -optional -result -some -none -ok -err -if -else -when -for -in -return -handle -borrow -mutate -peek -take -alloc -weak -as -``` - ---- - -### 2.3 Literals - -#### Numeric Literals - -* `int` — decimal digits -* `float` — decimal with `.` -* `bounded` — decimal digits suffixed with `b` - -Examples: - -```pbs -10 -42 -3.14 -0b -255b -``` - -#### String Literals - -* delimited by `"` -* UTF‑8 encoded -* immutable - ---- - -### 2.4 Comments - -* line comment: `// until end of line` -* block comments are **not supported** in v0 - ---- - -## 3. Grammar (EBNF‑style) - -> This grammar is **normative** but simplified for readability. -> Implementers may refactor internally as long as semantics are preserved. - -### 3.1 File Structure - -``` -File ::= Import* TopLevelDecl* -Import ::= 'import' ImportSpec 'from' StringLiteral -TopLevelDecl::= TypeDecl | ServiceDecl | FnDecl -``` - ---- - -### 3.2 Type Declarations - -``` -TypeDecl ::= Visibility? 'declare' TypeKind Identifier TypeBody -TypeKind ::= 'struct' | 'contract' | 'error' -``` - ---- - -### 3.3 Services - -``` -ServiceDecl ::= Visibility 'service' Identifier (':' Identifier)? Block -``` - -Visibility is mandatory for services. - ---- - -### 3.4 Functions - -``` -FnDecl ::= Visibility? 'fn' Identifier ParamList ReturnType? ElseFallback? Block -``` - -Top‑level `fn` are `mod` or `file-private` (default). They cannot be `pub`. - ---- - -### 3.5 Expressions (Partial) - -``` -Expr ::= Literal - | Identifier - | CallExpr - | Block - | IfExpr - | WhenExpr - | ForExpr - | ReturnExpr -``` - -Expression grammar is intentionally restricted in v0. - ---- - -## 4. Canonical AST - -The frontend **must** produce a canonical AST. - -### 4.1 AST Invariants - -* AST nodes are immutable after creation -* Parent pointers are optional -* Source spans must be preserved for diagnostics - ---- - -### 4.2 Core Node Kinds - -Minimal required node kinds: - -* `FileNode` -* `ImportNode` -* `ServiceNode` -* `FunctionNode` -* `StructDeclNode` -* `ContractDeclNode` -* `BlockNode` -* `LetNode` -* `CallNode` -* `IfNode` -* `WhenNode` -* `ForNode` -* `ReturnNode` - -Implementers may add internal nodes but must normalize before later phases. - ---- - -## 5. Symbol Collection Phase - -### 5.1 Purpose - -Symbol Collection builds **module‑level symbol tables** without resolving bodies. - -Collected symbols: - -* `pub` and `mod` type declarations -* `pub` and `mod` services - -Excluded: - -* function bodies -* expressions -* local bindings - ---- - -### 5.2 Namespaces - -PBS has **two namespaces**: - -* Type namespace -* Value namespace - -Rules: - -* A name may not exist in both namespaces -* Violations are compile‑time errors - ---- - -## 6. Resolver Phase - -### 6.1 Responsibilities - -Resolver must: - -* resolve all identifiers -* enforce visibility rules -* bind references to symbols - -Resolution order (per namespace): - -1. local bindings -2. file‑private declarations -3. module symbols -4. imported symbols - ---- - -### 6.2 Visibility Rules (Normative) - -* file‑private: visible only in the same file -* `mod`: visible within the module -* `pub`: visible across modules via import - -Violations are errors. - ---- - -## 7. Type Checking - -### 7.1 Type Categories - -Frontend must support: - -* primitive types -* struct value types -* `optional` -* `result` -* gate‑backed types (opaque at frontend level) - ---- - -### 7.2 Mutability Rules - -* mutability belongs to bindings, not types -* `mut` is part of binding metadata -* mutability violations are compile‑time errors - ---- - -### 7.3 Function Checking - -Rules: - -* all paths must return a value unless `else` fallback exists -* `optional` may implicitly return `none` -* `result` must return explicitly - ---- - -## 8. Desugaring and Lowering - -### 8.1 Purpose - -Lowering transforms surface syntax into a minimal core language. - -Examples: - -* `take x.push(v)` → `mutate x as t { t.push(v) }` -* `when` → conditional expression node -* implicit `return none` for `optional` - -Lowered AST must contain **no syntactic sugar**. - ---- - -## 9. Diagnostics Model - -Diagnostics are first‑class frontend outputs. - -### 9.1 Categories - -* Error — compilation must fail -* Warning — compilation may continue - ---- - -### 9.2 Required Errors - -Examples: - -* unresolved identifier -* visibility violation -* type mismatch -* mutability violation -* invalid gate conversion - ---- - -## 10. Runtime Interface Assumptions - -Frontend assumes: - -* host‑bound contracts map to runtime syscalls -* allocation primitives exist (`alloc`) -* reference counting is handled by runtime - -Frontend must **not** assume GC or heap layout. - ---- - -## 11. Determinism Guarantees - -A valid PBS frontend **must guarantee**: - -* deterministic parsing -* deterministic name resolution -* deterministic type checking -* deterministic diagnostics - -No frontend stage may depend on execution order or host state. - ---- - -## 12. Conformance Criteria - -A frontend is PBS‑v0‑conformant if: - -* it implements all rules in this document -* it produces canonical ASTs -* it rejects all invalid programs defined herein - -This document is the **source of truth** for PBS v0 frontend behavior. - ---- - -## 13. Future Evolution (Non‑Normative) - -Future versions may add: - -* pattern matching -* richer type inference -* macros - -No v0 frontend is required to support these. - ---- - -## End of Spec diff --git a/docs/specs/pbs/Prometeu Runtime Traps.md b/docs/specs/pbs/Prometeu Runtime Traps.md deleted file mode 100644 index 5db0c2b5..00000000 --- a/docs/specs/pbs/Prometeu Runtime Traps.md +++ /dev/null @@ -1,175 +0,0 @@ -# Runtime Traps v0 — Prometeu VM Specification - -> **Status:** Proposed (requires explicit owner approval) -> -> **Scope:** Prometeu VM / PBS v0 execution model - ---- - -## 1. Motivation - -Prometeu aims to be a **deterministic, industrial-grade virtual machine**. -To achieve this, execution errors that are: - -* caused by **user programs**, -* predictable by the execution model, -* and recoverable at the tooling / host level, - -must be **explicitly represented** and **ABI-stable**. - -This specification introduces **Runtime Traps** as a *formal concept*, consolidating behavior that already existed implicitly in the VM. - ---- - -## 2. Definition - -A **Runtime Trap** is a **controlled interruption of program execution** caused by a semantic violation detected at runtime. - -A trap: - -* **terminates the current execution frame** (or program, depending on host policy) -* **does not corrupt VM state** -* **returns structured diagnostic information** (`TrapInfo`) -* **is deterministic** for a given bytecode + state - -A trap is **not**: - -* a debugger breakpoint -* undefined behavior -* a VM panic -* a verifier/load-time error - ---- - -## 3. Trap vs Other Failure Modes - -| Category | When | Recoverable | ABI-stable | Example | -| ------------------ | ---------------------- | ----------- | ---------- | -------------------------------- | -| **Verifier error** | Load-time | ❌ | ❌ | Stack underflow, bad CFG join | -| **Runtime trap** | Execution | ✅ | ✅ | OOB access, invalid local | -| **VM panic** | VM invariant violation | ❌ | ❌ | Handler returns wrong slot count | -| **Breakpoint** | Debug only | ✅ | ❌ | Developer inspection | - ---- - -## 4. Trap Information (`TrapInfo`) - -All runtime traps must produce a `TrapInfo` structure with the following fields: - -```text -TrapInfo { - code: u32, // ABI-stable trap code - opcode: u16, // opcode that triggered the trap - pc: u32, // program counter (relative to module) - message: String, // human-readable explanation (non-ABI) -} -``` - -### ABI Guarantees - -* `code`, `opcode`, and `pc` are ABI-relevant and stable -* `message` is diagnostic only and may change - ---- - -## 5. Standard Trap Codes (v0) - -### 5.1 Memory & Bounds - -| Code | Name | Meaning | -| -------------------- | ------------- | ------------------------------ | -| `TRAP_OOB` | Out of bounds | Access beyond allowed bounds | -| `TRAP_INVALID_LOCAL` | Invalid local | Local slot index out of bounds | - -### 5.2 Execution & Types - -| Code | Name | Meaning | -| ------------------------- | -------------------- | ------------------------------------------------------ | -| `TRAP_ILLEGAL_INSTRUCTION`| Illegal instruction | Unknown/invalid opcode encountered | -| `TRAP_TYPE` | Type violation | Type mismatch for operation or syscall argument types | -| `TRAP_DIV_ZERO` | Divide by zero | Division/modulo by zero | -| `TRAP_INVALID_FUNC` | Invalid function | Function index not present in function table | -| `TRAP_BAD_RET_SLOTS` | Bad return slots | Stack height mismatch at return | - -### 5.3 System - -| Code | Name | Meaning | -| ---------------------- | --------------- | --------------------------------------- | -| `TRAP_INVALID_SYSCALL` | Invalid syscall | Unknown syscall ID | -| `TRAP_STACK_UNDERFLOW` | Stack underflow | Missing arguments for syscall or opcode | - -> This list is **closed for PBS v0** unless explicitly extended. - ---- - -## 6. Trap Semantics - -### 6.1 Execution - -When a trap occurs: - -1. The current instruction **does not complete** -2. No partial side effects are committed -3. Execution stops and returns `TrapInfo` to the host - -### 6.2 Stack & Frames - -* Operand stack is left in a **valid but unspecified** state -* Call frames above the trapping frame are not resumed - -### 6.3 Host Policy - -The host decides: - -* whether the trap terminates the whole program -* whether execution may be restarted -* how the trap is surfaced to the user (error, log, UI, etc.) - ---- - -## 7. Verifier Interaction - -The verifier **must prevent** traps that are statically provable, including: - -* stack underflow -* invalid control-flow joins -* invalid syscall IDs -* incorrect return slot counts - -If a verifier rejects a module, **no runtime traps should occur for those causes**. - ---- - -## 8. What Is *Not* a Trap - -The following are **VM bugs or tooling errors**, not traps: - -* handler returns wrong number of slots -* opcode implementation violates `OpcodeSpec` -* verifier and runtime disagree on stack effects - -These must result in **VM panic**, not a trap. - ---- - -## 9. Versioning Policy - -* Trap codes are **ABI-stable within a major version** (v0) -* New trap codes may only be added in a **new major ABI version** (v1) -* Removing or reinterpreting trap codes is forbidden - ---- - -## 10. Summary - -Runtime traps are: - -* an explicit part of the Prometeu execution model -* deterministic and ABI-stable -* reserved for **user-program semantic errors** - -They are **not** debugging tools and **not** VM panics. - -This spec formalizes existing behavior and freezes it for PBS v0. - ---- diff --git a/docs/specs/pbs/Prometeu Scripting - Prometeu Bytecode Script (PBS).md b/docs/specs/pbs/Prometeu Scripting - Prometeu Bytecode Script (PBS).md deleted file mode 100644 index a31169ab..00000000 --- a/docs/specs/pbs/Prometeu Scripting - Prometeu Bytecode Script (PBS).md +++ /dev/null @@ -1,4901 +0,0 @@ -# Prometeu Base Script (PBS) - -**Status:** v0 (frontend-freeze) -**Goal:** -* explicit semantics -* predictable runtime -* two worlds: - - SAFE: stack/value-first, no aliasing - - HIP storage/heap: gate-baked with aliasing -* no GC tracing, but with explicit memory management by refcounts + reclaim by frame - ---- - -## 0. Philosophy (anchor section) - -## 0. Philosophy (anchor section) - -PBS is a **stack-only**, **value-first** language designed for **explicit semantics** and a **predictable runtime**. - -### 0.1 Non-negotiable constraints (v0) - -In v0, the following are true: - -* **No implicit references on SAFE model**. - - The HIP introduces handles (gates) and aliasing with an explicit sintax (`alloc`, `borrow`, `mutate`, `peek`, `take`). -* **No aliasing of mutable state between values on the stack**. Gate-baked types may alias and mutate state. -* **No exceptions**: failure is expressed only via explicit types (e.g. `optional`, `result`) and explicit syntax (`else`, `?`, `handle`). -* **No top-level execution**: modules/files contain declarations only; initialization occurs only through explicit function/service calls. - -### 0.2 Design principles (how v0 is shaped) - -PBS favors: - -* **Value-first**: values are conceptually independent; copying is always safe; identity does not exist for user-defined values. -* **Explicit mutability**: mutability is a property of bindings, never a property of types. -* **Didactic rules**: important rules must be visible in syntax, not hidden in conventions. -* **Runtime-cheap execution**: PBS shifts complexity to compile time (frontend-heavy) to keep the runtime simple and predictable. - ---- - -## 1. Project, Files & Modules - -This section defines the physical and logical structure of a PBS project. -All rules in this section are **normative**. - ---- - -### 1.1 Project Root and Source Layout - -A PBS project has a single **project root directory** (`{root}`). - -Rules: - -* `{root}` is the directory that contains the file `prometeu.json`. -* A project **must** contain the directory `{root}/src/main/modules`. -* All PBS modules of the project live **exclusively** under `{root}/src/main/modules`. - -Import resolution: - -* The import prefix `@project:` is resolved relative to `{root}/src/main/modules`. -* Any path after `@project:` is interpreted as a **module path**, not a file path. -* `project` is declared into `prometeu.json` as the project name. and int the case of -missing it we should use `{root}` as project name. - -If `{root}/src/main/modules` does not exist, compilation fails. - ---- - -### 1.2 Module Model - -* **One directory = one module**. -* The name of a module is the directory name as it appears on disk. -* Module names are case-sensitive and are not normalized. - -A module boundary is **absolute**: - -* Parent directories do **not** implicitly see child modules. -* Child directories do **not** implicitly see parent modules. -* Sibling directories are completely isolated. - -Example: -``` -{root}/src/main/modules/ -├─ gfx/ -│ └─ draw.pbs -└─ gfx/math/ -└─ vec.pbs -``` - -In this structure: - -* `@project:gfx` and `@project:gfx/math` are **two distinct modules**. -* `@project:gfx` does **not** see any declarations in `@project:gfx/math`. -* `@project:gfx/math` does **not** see any declarations in `@project:gfx`. - -Cross-module access is **only** possible via `import`, and only for `pub` symbols. - ---- - -### 1.3 Automatic Module Index - -PBS has no mandatory barrel or index file. - -For each module directory `M`: - -* The compiler scans all `.pbs` files **directly inside** `M`. -* Files in subdirectories of `M` are ignored. -* The module’s **public index** consists of: - * all `pub` symbols - * declared in those `.pbs` files - * separated by namespace (type and value). - -The public index is the **only** surface visible to other modules. - ---- - -### 1.4 Visibility Modifiers - -Visibility is explicit and exhaustive. There are exactly three visibility levels. - -#### File-private (default) - -* No visibility modifier. -* Visible **only** within the declaring `.pbs` file. -* Never visible to other files, even inside the same module. - -#### `mod` (module-visible) - -* Visible to all files in the same module (same directory). -* Not visible outside the module. -* No `import` is required within the module. - -#### `pub` (public) - -* Exported as part of the module’s public API. -* Visible to other modules via `import`. -* Within the same module, `pub` behaves exactly like `mod`. - -Important rules: - -* A symbol has **exactly one** visibility level. -* There is no implicit or inferred visibility. -* Visibility is checked independently in the **type namespace** and the **value namespace**. -* Directory structure, file names, and compilation order have **no effect** on visibility. - ---- - -### 1.5 Symbols Covered by Visibility Rules - -Visibility rules apply uniformly to: - -* Type-level declarations: - * `declare struct` - * `declare error` - * `declare contract` -* Value-level declarations: - * `service` - * `fn` - * `let` - -No other mechanism may expose a symbol. - ---- - -## 2. Namespaces & Visibility - -PBS uses **two distinct and independent namespaces**: Type and Value. -This separation is strict, explicit, and fully enforced by the compiler. - -There are no implicit namespaces and no name overloading across namespaces. - ---- - -### 2.1 Namespace Model - -PBS defines exactly **two namespaces**: - -#### Type namespace - -The **type namespace** contains only type-level declarations introduced by `declare`. - -Valid type declarations are: - -```pbs -declare struct Name { ... } -declare error Name { ... } -declare contract Name { ... } -``` - -Rules: - -* Only declarations introduced by `declare` may appear in the type namespace. -* No executable or runtime symbol may appear in this namespace. -* Type declarations participate in visibility rules (`file-private`, `mod`, `pub`) but always remain in the type namespace. - ---- - -#### Value namespace - -The **value namespace** contains executable and runtime-visible symbols. - -Symbols in the value namespace are introduced by: - -* `service` -* top-level `fn` — `mod` or `file-private` (default). -* top-level `let` are not allowed. - -Rules: - -* Only top-level declarations participate in the value namespace. -* Local `let` bindings do **not** participate in the file, module, or project namespace. -* Local bindings are scoped strictly to their enclosing block. - ---- - -### 2.2 Strict Namespace Separation - -A name **MUST NOT** exist in both namespaces. - -Rules: - -* If a name appears in the type namespace, it cannot appear in the value namespace. -* If a name appears in the value namespace, it cannot appear in the type namespace. -* Any attempt to declare the same name in both namespaces is a **compile-time error**. - -Example (INVALID): - -```pbs -declare struct Vector { ... } - -service Vector { ... } // ERROR: `Vector` already exists in the type namespace -``` - ---- - -### 2.3 Scope of Name Uniqueness - -Name uniqueness is enforced based on **visibility scope**. - ---- - -#### File-private symbols (default visibility) - -* File-private symbols are visible **only** within the declaring file. -* File-private symbols may reuse names that appear in other files or modules. - -Example (VALID): - -```pbs -// file A.pbs -fn helper() { ... } - -// file B.pbs (same module) -fn helper() { ... } // OK: file-private symbols are isolated per file -``` - ---- - -#### `mod` and `pub` symbols - -* Symbols declared with `mod` or `pub` visibility must be **unique inside the module**. -* Declaring two `mod` or `pub` symbols with the same name in the same module is a **compile-time error**, even if they appear in different files. - -Example (INVALID): - -```pbs -// file A.pbs -mod service Gfx { ... } - -// file B.pbs (same module) -mod service Gfx { ... } // ERROR: duplicate symbol in module - -// file C.pbs (same module) -mod declare error Gfx { ... } // ERROR: duplicate symbol in module -``` - ---- - -### 2.4 Visibility Does Not Create Namespaces - -Visibility controls **who can see a symbol**, but does **not** create separate namespaces. - -Rules: - -* `file-private`, `mod`, and `pub` symbols all live in the same namespace. -* Visibility only restricts access; it never permits name reuse at the same scope. - -Example (INVALID): - -```pbs -mod service Audio { ... } -pub service Audio { ... } // ERROR: duplicate symbol in module -``` - ---- - -### 2.5 Summary of Namespace Rules - -* PBS has exactly **two namespaces**: type and value. -* A name cannot exist in both namespaces. -* File-private symbols are unique only within a file. -* `mod` and `pub` symbols are unique at the module level. -* Visibility never creates new namespaces or exceptions to uniqueness rules. - ---- - -## 3. Top-level Declarations - -This section defines which declarations are allowed at the top level of a `.pbs` file and their semantic role in the language. -All rules in this section are **normative**. - ---- - -### 3.1 Allowed Top-level Declarations - -A `.pbs` file may contain the following declarations, in any order: - -* `import` -* **type declarations** via `declare`: - - * `declare struct` - * `declare error` - * `declare contract` -* `service` -* `fn` - -No other constructs are allowed at the top level. -In particular: - -* `let` is **not allowed** at the top level. -* No executable statements may appear at the top level. -* There is no top-level initialization or execution. - ---- - -### 3.2 Top-level Execution Model - -PBS has **no top-level execution**. - -Rules: - -* A `.pbs` file is a collection of declarations only. -* Code is executed only when: - - * a `service` method is invoked, or - * a `fn` is called from within another function or service method. -* Module loading does not execute any user-defined code. - -This rule ensures that: - -* module import order has no semantic effect -* import cycles can be resolved purely at the symbol level -* the runtime remains predictable and side-effect free during loading - ---- - -### 3.3 Functions (`fn`) - -Top-level `fn` declarations define reusable executable logic. - -Rules: - -* A top-level `fn` is always **mod** or **file-private**. -* A top-level `fn` cannot be declared as `pub`. -* `fn` defaults to **file-private** visibility. - -Example (VALID): - -```pbs -// math.pbs -fn clamp(x: int, min: int, max: int): int { ... } -``` - -Example (INVALID): - -```pbs -pub fn clamp(x: int): int { ... } // ERROR: top-level fn cannot be pub -``` - -Rationale: - -* `fn` exists for implementation and helper logic. -* Any logic intended to cross file or module boundaries must be exposed via a `service`. - ---- - -### 3.4 Services (`service`) - -A `service` is a top-level declaration that represents an explicit API boundary. - -Rules: - -* A `service` must always be declared with an explicit visibility modifier: - - * `pub service` — public API of the module - * `mod service` — internal API of the module -* There is no private service. -* A service may optionally implement a `contract`. - -Example: - -```pbs -pub service Audio -{ - fn play(sound: Sound): void { ... } -} -``` - -Services are the **only** mechanism for exposing executable behavior across files or modules. - ---- - -### 3.5 Type Declarations (`declare`) - -Type declarations introduce symbols into the **type namespace**. - -Rules: - -* All type declarations must use the `declare` keyword. -* Type declarations may be `file-private`, `mod`, or `pub`. -* Visibility controls where the type can be referenced. - -Example: - -```pbs -pub declare struct Vector { ... } -mod declare error IOError { ... } -``` - ---- - -### 3.6 Imports at the Top Level - -Rules: - -* `import` declarations are allowed only at the top level of a file. -* Imports must appear before any usage of imported symbols. -* Imports have no runtime effect and exist solely for name resolution. - -Example: - -```pbs -import { Vector } from "@project:math"; - -pub service Physics { ... } -``` - ---- - -### 3.7 Summary of Top-level Rules - -* A `.pbs` file contains **declarations only**. -* No `let` or executable statements are allowed at the top level. -* Top-level `fn` are always file-private. -* `service` is the only way to expose executable logic beyond a single file. -* Module loading is side effect free. - ---- - -## 4. Resolver Rules - -This section defines how the compiler resolves modules, imports, and symbol names into concrete declarations. -All rules in this section are **normative** and fully deterministic. - -The resolver operates purely at the **symbol level**. There is no top-level execution, and resolution never depends on runtime behavior. - ---- - -### 4.1 Units of Compilation - -PBS is compiled as a **project**. - -Rules: - -* The compilation unit is a **project**, consisting of: - - * the project root, - * all `.pbs` files under `{root}/src/main/modules`, and - * all resolved dependency projects copied into `prometeu-cache`. -* A **module** is exactly one directory. -* A **file unit** is exactly one `.pbs` file. - -There is no partial compilation of individual files or modules. - ---- - -### 4.2 Resolver Phases - -Name resolution is performed in **two explicit phases**. - -#### Phase 1 — Symbol Collection - -The compiler performs a project-wide scan to collect symbols. - -Rules: - -* For each module: - - * All `.pbs` files directly inside the module directory are scanned. - * All `mod` and `pub` symbols are collected into the module symbol table. -* Symbols are collected separately for the **type namespace** and the **value namespace**. -* Function bodies and service method bodies are **not** inspected in this phase. - -If any of the following occur during Phase 1, compilation fails: - -* Duplicate `mod` or `pub` symbols in the same namespace within a module. -* A symbol declared in both the type and value namespaces. - ---- - -#### Phase 2 — File Resolution - -Each file is resolved independently using the symbol tables built in Phase 1. - -Rules: - -* All names referenced in a file must resolve to a symbol that is: - - * visible under visibility rules, and - * uniquely identifiable. -* Resolution of one file does not depend on the resolution of another file. - ---- - -### 4.3 Visibility Gates - -For any candidate symbol `S`, visibility is checked explicitly. - -Rules: - -* **file-private (default)** — visible only within the declaring file. -* **`mod`** — visible to all files in the same module. -* **`pub`** — visible to other modules via `import`. - -No implicit visibility exists. - ---- - -### 4.4 Module Public Index - -For each module directory `M`, the compiler builds a **public index**. - -Rules: - -* The public index contains **only** `pub` symbols. -* Only symbols declared in `.pbs` files directly inside `M` are considered. -* Subdirectories never contribute to the parent module’s index. -* The public index is immutable once constructed. - -The public index is the **only source** for resolving imports from other modules. - ---- - -### 4.5 Import Resolution - -Imports are the **only mechanism** for cross-module name resolution. - -Syntax: - -```pbs -import { X, Y as Z } from "@project:module"; -``` - -Rules: - -* Imports may only reference **modules**, never individual files. -* Only `pub` symbols may be imported. -* Each imported name must exist in the target module’s public index. -* Aliased imports (`as`) introduce a new local name bound to the imported symbol. - -Invalid imports are compile-time errors. - ---- - -### 4.6 Local Name Resolution Order - -Within a file, name lookup follows this order **within each namespace**: - -1. Local bindings (`let`, parameters), innermost scope first. -2. File-level declarations in the same file (file-private, `mod`, `pub`). -3. Imported symbols. - -Rules: - -* Local `let` shadowing is allowed, with warnings. -* Shadowing of `service` names is **not allowed**. -* Shadowing of top-level `fn` names is **not allowed**. -* If an imported symbol conflicts with an existing `mod` or `pub` symbol in the module, compilation fails. - ---- - -### 4.7 Cross-file and Cross-module Access - -#### Within the same module - -* `mod` and `pub` symbols are visible across files without `import`. -* File-private symbols are never visible outside their declaring file. - -#### Across modules - -* Only `pub` symbols are visible. -* Access always requires an explicit `import`. - ---- - -### 4.8 Import Cycles - -Import cycles are permitted **only** if symbol resolution can be completed. - -Rules: - -* PBS has no top-level execution. -* Cycles are evaluated purely at the symbol level. -* Any cycle that prevents completion of Phase 1 (symbol collection) is a compile-time error. - ---- - -### 4.9 Contracts and Services - -Rules: - -* `declare contract C` introduces `C` in the **type namespace**. -* `service S: C` resolves `C` as a type during Phase 2. -* The compiler validates that `S` implements all signatures declared in `C`. -* Signature matching is exact: - - * name - * parameter types - * parameter mutability - * return type - -Contracts themselves contain no executable logic and have no runtime behavior. - ---- - -### 4.10 Resolver Guarantees - -The resolver guarantees that: - -* Name resolution is deterministic. -* Resolution does not depend on file order or import order. -* All symbol conflicts are reported at compile time. -* No runtime name lookup is required. - ---- - -## 5. Services - -This section defines the `service` construct. -A service represents an **explicit API boundary** and the only mechanism for exposing executable behavior beyond a single file. -All rules in this section are **normative**. - ---- - -### 5.1 Role of a Service - -A `service` has the following properties: - -* an explicit API boundary between PBS modules -* conceptually a **singleton**. -* it has no identity beyond its name. -* there is never an implementation detail. - -Any behavior intended to be visible outside a file **must** be exposed via a service. - ---- - -### 5.2 Declaration and Visibility - -A service must always be declared at the **top level** of a `.pbs` file. - -Rules: - -* A service **must** declare its visibility explicitly. -* Valid visibility modifiers are: - - * `pub service` — public API of the module - * `mod service` — internal API, visible only inside the module -* There is **no such thing** as a private service. - -Example: - -```pbs -pub service Audio -{ - fn play(sound: Sound): void { ... } -} -``` - ---- - -### 5.3 Services and Contracts - -A service may optionally implement a contract. - -Syntax: - -```pbs -service S: ContractName { ... } -``` - -Rules: - -* `ContractName` must resolve to a `declare contract` type. -* If a service declares a contract, it **must** implement all declared signatures. -* Signature matching is exact and includes: - - * method name - * parameter count and order - * parameter types - * parameter mutability - * return type - -Failure to satisfy a contract is a **compile-time error**. - ---- - -### 5.4 Service Methods - -Methods declared inside a service define its executable interface. - -Rules: - -* All service methods are **public within the service**. -* There are no private, protected, or helper methods inside a service. -* Service methods are **not** top-level symbols. -* Service methods are accessible only via the service name. - -Example: - -```pbs -Audio.play(sound); -``` - ---- - -### 5.5 Interaction with Other Code - -A service method may call: - -* top-level `fn` declared in the same file -* other services that are visible under normal visibility rules - -Rules: - -* A service method may not access file-private symbols from other files. -* A service may not directly access implementation details of another service. - ---- - -### 5.6 Implementation Rule - -If logic is **not conceptually part of the service API**, it **must not** live inside the service. - -Rules: - -* Implementation logic must be written as file-private `fn`. -* Services act as thin API layers that delegate to internal functions. - -Example: - -```pbs -pub service Math -{ - fn add(a: int, b: int): int - { - return add_impl(a, b); - } -} - -fn add_impl(a: int, b: int): int { ... } -``` - ---- - -### 5.7 Summary of Service Rules - -* `service` is the only construct for exposing executable behavior across files or modules. -* A service is always declared with explicit visibility. -* Services may implement contracts, which are checked statically. -* All service methods are public within the service. -* Services contain no hidden implementation logic. - ---- - -## 6. Types - -This section defines all value types available in PBS v0 and their semantic properties. -All rules in this section are **normative**. - -PBS is divided into: -* Value types (stack): usually primitive types, including `string`, `struct`, `optional`, `result` and `tuples`. -* Gate-backed types (storage/heap): `text` (string builders and concats), `array[N]` (fixed-size arrays), -`list` (dynamic storage), `map` (hash maps) and `declare storage struct` (custom gate-backed). - - Gate-backed types are **not** value types, they are handles. Copy is inexpensive, not a deep copy. - - On HIP a deep copy should be made using `copy(x)`. - ---- - -### 6.1 Type Categories - -PBS defines three categories of types: - -1. **Primitive types** -2. **User-defined struct types** -3. **Composite container types** (defined elsewhere, e.g. fixed arrays) - -All types in PBS are **value types**. - ---- - -### 6.2 Primitive Types - -PBS provides a small, fixed set of primitive types: - -* `void` -* `int` — 32-bit signed integer -* `long` — 64-bit signed integer -* `float` — 32-bit IEEE-754 -* `double` — 64-bit IEEE-754 -* `bool` -* `char` — Unicode scalar value (32-bit) -* `string` -* `bounded` — unsigned 16-bit integer for indices and sizes - -Rules: - -* Primitive types have no identity. -* Primitive values are copied on assignment (except for `string` when on constant pool). -* Primitive values cannot be partially mutated. - ---- - -### 6.3 `string` - -The `string` type represents an immutable sequence of Unicode characters. - -Rules: - -* A `string` value is immutable. -* String literals produce pool-backed string values. -* Some operations may produce stack-owned (for example, `concat`) string snapshots. - - when pool-backed, copy is cheap, since it is just a "handle" (pool-backed, compile time, constant pool). - - when stack-owned, copy is expensive, since it is a deep copy (stack-owned, runtime). -* Doesn't exist such a thing like `alloc string`, it never goes to HIP. -* There is no heap/storage allocation for string at runtime. - -Example: - -```pbs -let s1: string = "hello"; // pool-backed (cheap copy) -let s2: mut string = mut "hello"; // ERROR: string cannot be mutated - -s1 += " world"; // ERROR: no mutation, no += -let s3: string = s1 + " world"; // OK: produces a runtime-owned snapshot (stack-owned) -``` - ---- - -### 6.4 User-defined Struct Types - -User-defined value types are declared using `declare struct`. - -Syntax: - -```pbs -declare struct Name(field1: T1, field2: T2) { ... } -``` - -Rules: - -* A struct is a **pure value type**. -* A struct has no identity. -* A struct has no subtyping, inheritance, or polymorphism. -* A struct value conceptually exists only as data. - -Assignment and passing: - -* Assigning a struct value copies the value. -* Passing a struct as a function argument passes a value. -* Returning a struct returns a value. - -The compiler may optimize copies internally, but such optimizations must not be observable. - ---- - -### 6.5 Mutability and Structs - -Mutability is a property of **bindings**, not of struct types. - -Rules: - -* A struct bound to an immutable binding cannot be mutated. -* A struct bound to a mutable binding may be mutated via mutating methods. -* Mutating a struct affects only that binding. - -Example: - -```pbs -let v = Vector.ZERO; -v.scale(); // ERROR: immutable binding - -let w = mut Vector.ZERO; -w.scale(); // OK -``` - ---- - -### 6.6 The `this` Type - -Inside a `declare struct` body, the special type `this` refers to the enclosing struct type. - -Rules: - -* `this` may appear only inside a struct declaration. -* `this` always refers to the concrete struct type, not to an abstract or dynamic type. -* Outside a struct body, use of `this` is illegal. - -Example: - -```pbs -declare struct Vector(x: float, y: float) -{ - pub fn len(self: this): float { ... } - pub fn scale(self: mut this): void { ... } -} -``` - ---- - -### 6.7 Type Visibility - -Type declarations follow the same visibility rules as other symbols. - -Rules: - -* A type declaration may be file-private, `mod`, or `pub`. -* Visibility controls where the type name can be referenced. -* Visibility does not affect the runtime representation of a type. - ---- - -### 6.8 Summary of Type Rules - -* All types in PBS are value types. -* Types have no identity, ownership, or lifetime. -* Mutability applies only to bindings. -* Structs are copied by value; optimizations are not observable. -* `this` provides explicit self-reference inside struct bodies. - ---- - -## 7. Contracts - -This section defines **contracts** in PBS. -Contracts specify **pure interface-level guarantees** and may represent either: - -* an interface that a `service` must implement, or -* a **host-bound API** implemented directly by the Prometeu runtime / host environment. - -All rules in this section are **normative**. - -Contracts introduce **no user-defined runtime behavior** and contain no executable code bodies. - ---- - -### 7.1 Role of a Contract - -A `contract` represents a **static promise** about an available API surface. - -A contract: - -* defines a set of method signatures, -* is validated entirely at compile time, -* may be bound to a PBS `service` or directly to the runtime, -* has no identity and no state. - -Contracts are never instantiated and never called directly (unless it is a host-bound contract). - ---- - -### 7.2 Declaring a Contract - -Contracts are declared using `declare contract`. - -Syntax: - -```pbs -declare contract ContractName -{ - fn methodName(param1: T1, param2: T2): R; -} -``` - -Rules: - -* A contract declaration introduces a symbol into the **type namespace**. -* Contract declarations may be `mod`, or `pub` (same as services). -* A contract body should contain **only method signatures** (no {}). -* Contract methods have no bodies and no default implementations. - ---- - -### 7.3 Contract Method Signatures - -A contract method signature specifies: - -* method name -* parameter list -* parameter types -* parameter mutability -* return type - -Rules: - -* Contract methods must not declare bodies. -* Contract methods must not declare `else` fallbacks (it is implementation specifics). -* Contract methods must not declare visibility modifiers. -* Contract methods exist only for static validation. - -Example: - -```pbs -declare contract AudioAPI -{ - fn play(sound: Sound): void; - fn stop(id: int): void; -} -``` - ---- - -### 7.4 Host-bound Contracts - -A contract may be declared as **host-bound**, meaning its implementation is provided directly by the runtime or host environment. - -Syntax: - -```pbs -pub declare contract Gfx host -{ - fn drawText(x: int, y: int, message: string, color: Color): void; -} -``` - -Rules: - -* A host-bound contract **should be called directly** and **should never be implemented by a `service`** (`Gfx.drawText(...)`). -* A host-bound contract is always bound to the target runtime or host environment. -* A call of the form `ContractName.method(...)` resolves to a **host call** (syscall). -* The runtime must provide an implementation for every method declared in a host-bound contract. -* If a host-bound contract is unavailable in the target runtime, compilation or linking fails. - -Host-bound contracts define the **official API boundary between PBS and the runtime**. - ---- - -### 7.5 Calling Contracts - -Rules: - -* Contract methods are invoked using the syntax: - -```pbs -ContractName.method(...); -``` - -* This syntax is valid **only** for host-bound contracts. -* Calling a non-host-bound contract directly is a compile-time error. - -Example (INVALID): - -```pbs -declare contract Foo -{ - fn bar(): void; -} - -Foo.bar(); // ERROR: Foo is not host-bound -``` - ---- - -### 7.6 Implementing a Contract in a Service - -A service may implement a non-host-bound contract. - -Syntax: - -```pbs -pub service Audio: AudioAPI -{ - fn play(sound: Sound): void { ... } - fn stop(id: int): void { ... } -} -``` - -Rules: - -* A service that declares a contract **must** implement all contract methods. -* Method matching is **exact**. -* Missing or mismatched methods are compile-time errors. - ---- - -### 7.7 Signature Matching Rules - -For a service method to satisfy a contract method, all of the following must match exactly: - -* method name -* number of parameters -* parameter order -* parameter types -* parameter mutability -* return type - -Rules: - -* Parameter names do not need to match. -* Overloading is not permitted. -* A contract method may be implemented by **exactly one service method**. - ---- - -### 7.8 Contracts and Visibility - -Rules: - -* A contract may be implemented only by a service that can legally see it. -* A host-bound contract must be visible at the call site. -* Contract visibility affects name resolution only, never runtime behavior. - ---- - -### 7.9 Runtime Semantics of Contracts - -Contracts: - -* do not generate PBS code bodies, -* do not introduce dynamic dispatch, -* do not allocate memory, -* do not exist as runtime values. - -All contract validation is performed at compile time. - ---- - -### 7.10 Summary of Contract Rules - -* Contracts define static API surfaces only. -* Contracts live in the type namespace. -* Contracts may be **service-bound** or **host-bound**. -* Host-bound contracts define the PBS ↔ runtime API. -* Only host-bound contracts may be called directly. -* Contract satisfaction is checked statically and exactly. - ---- - -## 8. Structs & Constructors - -This section defines `declare struct`, its fields, constructor aliases, static constants, and struct methods. -All rules in this section are **normative**. - -Structs are **pure value types** (§6). They have no identity and no inheritance. - ---- - -### 8.1 Declaring a Struct - -A struct is declared using `declare struct`. - -Syntax: - -```pbs -declare struct Name(field1: T1, field2: T2) -[ - // constructor aliases -] -[[ - // static constants -]] -{ - // methods -} -``` - -Rules: - -* The field list in parentheses defines the struct’s stored data. -* The constructor alias block `[...]` and static constant block `[[...]]` are optional. -* The method body block `{...}` is optional. - ---- - -### 8.2 Fields - -Rules: - -* Struct fields are **private by default**. -* Fields cannot be accessed directly outside the struct declaration. -* All field reads/writes must occur through struct methods. - -Example (INVALID): - -```pbs -declare struct Vector(x: float, y: float) -let v = Vector(1, 2); -let x = v.x; // ERROR: field `x` is private -``` - -Example (VALID): - -```pbs -declare struct Vector(x: float, y: float) -{ - pub fn getX(self: this): float { ... } -} -``` - ---- - -### 8.3 Constructor Aliases - -Constructor aliases are **named constructors** defined inside the alias block `[...]`. - -Example: - -```pbs -declare struct Vector(x: float, y: float) -[ - (): (0.0, 0.0) as default { } - (a: float): (a, a) as square { } -] -{ -} -``` - -Rules: - -* each struct has a **default constructor** that produces a full-initialized value. -* Each alias defines a parameter list uses a default constructor or alias to create a new struct value. -* Alias bodies `{ ... }` are **compile-time only** and may contain only compile-time constructs (see §8.5). -* Constructor aliases live in the **type namespace**. -* Constructor aliases are **not importable**. Only the struct type name is importable. - -Calling a constructor and constructor alias: - -```pbs -declare struct Vector(x: float, y: float) -[ - (s: float): (s, s) as square { } - (s: float): square(s * s) as doubleSquare { } - (x, float, y, float): (x, y) as normalized - { - let l = sqrt(x * x + y * y); - this.x /= l; - this.y /= l; - } - (): square(0) as zero { } -] -``` - -That is the default constructor, it takes all parameters always; there is no alias: -```pbs -let v0 = Vector(1, 3); -``` - -That is the zero alias, it will call the default constructor with x = 0 and y = 0 -```pbs -let v1 = Vector.zero(); -``` - -That is the square alias, it will call the default constructor with x = 2.0 and y = 2.0 -```pbs -let v2 = Vector.square(2.0); -``` - -That is the doubleSquare alias, it will call the square alias, and load x = 4.0 and y = 4.0 -```pbs -let v3 = Vector.doubleSquare(2.0); -``` - -That is the normalized alias, it will call the default constructor with x = 3.0 and y = 4.0 and normalize it (x = 0.6, y = 0.8) -```pbs -let v4 = Vector.normalized(3.0, 4.0); -``` - -Name rules: - -* Alias names must be unique within the struct. -* Alias names must not conflict with method names (to keep it didactic). - ---- - -### 8.4 Methods - -Struct methods are declared inside the struct body `{ ... }`. - -Rules: - -* Struct methods may be `mod`, or `pub`. `file-private` methods are not allowed and should rely on `fn`. -* Methods are not top-level symbols. -* Methods are invoked on a struct value using dot syntax. - -Example: - -```pbs -declare struct Vector(x: float, y: float) -[ - (): (0.0, 0.0) as default { } -] -{ - pub fn len(self: this): float { ... } -} - -let v = Vector.default(); -let l = v.len(); -``` - -Receiver rules: - -* Methods must declare the receiver as the first parameter, named `self`. -* The receiver type must be either `this` or `mut this`. - -Example: - -```pbs -declare struct Vector(x: float, y: float) -{ - pub fn len(self: this): float { ... } - pub fn scale(self: mut this, s: float): void { ... } -} -``` - -Mutability of `self` follows the binding rules in §9. - ---- - -### 8.5 Static Constants Block - -The static constants block `[[ ... ]]` declares named constants associated with the struct. - -Example: - -```pbs -declare struct Vector(x: float, y: float) -[ - (): (0.0, 0.0) as default { } - (a: float): (a, a) as square { } -] -[[ - ZERO: default() - ONE: square(1.0) -]] -{ -} -``` - -Rules: - -* Static constants are **compile-time constants**. -* Static constants are stored in the constant pool. -* Static constants are **immutable** and can never be mutated. -* The initializer of a static constant may use only: - - * struct constructor aliases of the same struct, and - * other static constants of the same struct declared earlier in the same static block. - -Static constant access: - -```pbs -let z = Vector.ZERO; -``` - ---- - -### 8.6 No Static Methods - -PBS has no static methods on structs. - -Rules: - -* All executable behavior must be expressed as instance methods. -* Constructor aliases are the only “static-like” callable members of a struct. -* Static constants are values only. - -This avoids overloading the meaning of `TypeName.member`. - ---- - -### 8.7 Summary of Struct Rules - -Full example of `struct`: -```pbs -declare struct Vector(x: float, y: float) -[ - (): (0.0, 0.0) as default { } - (a: float): (a, a) as square { } -] -[[ - ZERO: default() -]] -{ - pub fn len(self: this): float { ... } - pub fn scale(self: mut this): void { ... } -} -``` - - -* Structs are declared with `declare struct`. -* Fields are private and cannot be accessed directly. -* Constructor aliases exist only inside the type and are called as `Type.alias(...)`. -* Constructor aliases are not importable. -* Static constants are compile-time values and are immutable. -* Struct methods use an explicit `self: this` / `self: mut this` receiver. -* There are no static methods. - ---- - -## 9. Mutability & Borrow Model - -PBS defines two distinct and explicit mutability worlds: -* SAFE world (Stack / Value-first) -* HIP world (Storage / Gate-backed) - -These worlds have *different guarantees*, *different costs*, and *different risks*. -Crossing between them is always explicit in the language. - ---- - -### 9.1 The SAFE World (Stack / Value-first) -The SAFE world includes: -* Primitive types -* declare struct value types -* `optional` and `result` -* All values that are *not allocated* via `alloc` - -Rules (SAFE World): -* All values are value-first. -* Assignment copies values conceptually. -* Mutation is always local to the binding. -* There is no observable aliasing between stack values. -* No pointers, references, or handles exist in this world. - -Example: -``` -let a: Vector = Vector(1, 2); -let b = a; // conceptual copy -b.x = 10; - -// a.x is still 1 -``` - -Mutation in the SAFE world never affects other bindings. - ---- - -### 9.2 Method Receivers - -Methods declare mutability explicitly on the receiver. - -Rules: - -* A receiver declared as `self: this` is read-only. -* A receiver declared as `self: mut this` allows mutation. -* A mutating method requires a **mutable lvalue** at the call site. Whatever it is passed in will be a copy from there on. - -Example: - -```pbs -let v = Vector.ZERO; -v.scale(); // ERROR - -let w = mut Vector.ZERO; -w.scale(); // OK -``` - -A mutating method may not be called on: - -* static constants -* immutable bindings - ---- - -### 9.3 The HIP World (Storage / Gate-Backed) - -The HIP world is entered explicitly via allocation: -``` -let a = alloc array[4b]; -``` - -Types allocated with alloc are gate-backed and live in Storage (heap). -* Properties of the HIP World -* Values are accessed via gates (handles). -* Assignment copies the handle, not the underlying data. -* Aliasing is intentional and observable. -* Mutation affects all aliases. -* Lifetime is managed via reference counting (RC). - -Example: -``` -let a = alloc array[2b]; -let b = a; // alias (shared storage) - -mutate a as aa -{ - aa[0b] = 10; -} - -let x = peek b[0b]; // x == 10 - -``` - -### 9.4 Gate Semantics (Strong and Weak) - -All gate-backed types are strong gates by default. - -* T (gate-backed type) → strong gate -* weak → weak gate - -Strong Gates -* Increment reference count (RC) -* Keep the storage object alive -* Assignment (let b = a) increases RC - -Weak Gates -* Do not increment RC -* Do not keep objects alive -* Observe storage without ownership - -Conversions: -``` -let s: Node = alloc Node; -let w: weak = s as weak; // always succeeds - -let maybeS: optional = w as strong; // may fail -``` - -* `strong as weak` is always valid -* `weak as strong` returns `optional` - ---- - -### 9.5 Reference Counting and Collection - -PBS does not use tracing garbage collection. - -Instead: -* Storage objects are managed via reference counting -* RC tracks: - - references from the stack - - references stored inside other storage objects -* When RC reaches zero, objects become eligible for reclamation - -*Collection Model* -* Reclamation occurs at safe runtime points, typically: - - end of frame - - explicit GC step (implementation-defined) -* Cycles of strong references are not collected automatically - -This behavior is intentional and part of the learning model. - ---- - -### 9.6 Weak References and Cycles - -Cycles of strong references prevent RC from reaching zero: -``` -a -> b -b -> a -``` - -To break cycles, PBS provides weak gates: - -``` -declare storage struct Node -{ - next: optional>; -} -``` - -Weak gates allow cyclic graphs without leaks, at the cost of requiring explicit validation when accessing. - ---- - -### 9.7 Controlled Access: peek, borrow, and mutate - -All access to gate-backed data is explicit and controlled. - -`peek` — Copy to SAFE World (it is a `borrow` sugar) -``` -let v = peek a[i]; -``` - -* Returns a *value copy* -* Never exposes references -* Always SAFE - -`borrow` — Read-only Access (HIP) -``` -let len = borrow a as aa -{ - aa.len() -}; -``` - -* `aa` is a read-only reference -* The reference cannot escape -* The block returns a value - -`mutate` — Mutable Access (HIP) -``` -mutate a as aa -{ - aa[1b] = 42; -} -``` - -* `aa` is a mutable reference -* Mutation is shared across aliases -* The reference cannot escape - ---- - -#### 9.8 take — Mutation Sugar - -For single mutation operations, PBS provides take as syntactic sugar: -``` -take out.push(value); -``` - -Equivalent to: -``` -mutate out as o -{ - o.push(value); -} -``` - -`take`: -* Is only valid on gate-backed types -* Signals intent clearly -* Keeps HIP usage concise - ---- - -### 9.9 Summary of Guarantees -| Context | Aliasing | Mutation | Risk | -| SAFE (stack) | None | Local only | None | -| HIP (storage) | Explicit | Shared | Intentional | - -PBS makes power explicit and risk visible. -If you do not allocate, you are safe. -If you allocate, you are responsible. - ---- - -## 10. Expressions & Control Flow - -This section defines the expression model and control-flow constructs of PBS. -All rules in this section are **normative**. - -PBS favors **explicit, expression-oriented control flow** with no hidden execution paths. - ---- - -### 10.1 Expression Model - -In PBS, most constructs are expressions. - -Rules: - -* An expression always evaluates to a value. -* The value of an expression is immutable unless bound to a mutable binding. -* Expressions have no side effects except through explicit mutation of mutable bindings. - -Statements exist only as a syntactic convenience; semantically, they are expressions whose value is ignored. - ---- - -### 10.2 Blocks - -A block is a sequence of expressions enclosed in `{ ... }`. - -Rules: - -* A block introduces a new lexical scope. -* The value of a block is the value of its last expression. -* A block with no final expression evaluates to `void`. - -Example: - -```pbs -let x = { - let a = 10; - let b = 20; - a + b -}; // x == 30 -``` - ---- - -### 10.3 `if / else` - -The `if / else` construct is an expression. The blocks on `if` doesn't return a value, it is purely a control flow construct. - -Syntax: - -```pbs -if (condition) -{ - // do something -} -else -{ - // do something else -} -``` - -Rules: - -* `condition` must be of type `bool`. -* Both branches will not produce any value. - ---- - -### 10.4 `when` Expression - -`when` is a conditional expression equivalent to a ternary operator. - -Syntax: - -```pbs -when condition then expr1 else expr2 -``` - -Rules: - -* `when` always requires an `else` branch. -* Both branches must produce values of the same type. -* `when` is an expression and cannot be used without binding or return. - -Example: - -```pbs -let sign: int = when x >= 0 then 1 else -1; - -let complex: int = when x < 0 then -{ - // do something really complex here - return 0; -} -else -{ - // do something else even more complex here - return 1; -} -``` - ---- - -### 10.5 Loops - -PBS supports explicit looping constructs. - ---- - -#### 10.5.1 `for` Loop - -The `for` loop iterates over a bounded range. - -Syntax: - -```pbs -for i in [start .. end] { - // body -} -``` - -Rules: - -* `start` and `end` must be of type `bounded` unless explicitly annotated otherwise. -* The loop variable `i` is immutable by default. -* The range is **half-open**: `[start .. end)`. -* The body executes zero or more times. - -Example: - -```pbs -for i in [0b..10b] { - sum += i; -} -``` - -```pbs -for i in [..10b] { - sum += i; -} -``` - -```pbs -for i in [0b..] { - sum += i; -} -``` ---- - -### 10.6 `return` - -The `return` expression exits the current function or service method. - -Rules: - -* `return` must return a value compatible with the enclosing function’s return type. -* `return` immediately terminates execution of the current body. -* `return` is permitted anywhere inside a function or method body. -* `fn` and `service` allow a falback `return` value. - -Examples: - -```pbs -fn abs(x: int): int // no fallback -{ - return when x >= 0 then x else -x; -} - -fn abs(x: int): int // ERROR: compilation error, there is no fallback -{ -} - -fn abs(x: int): int else 1000 // fallback value, no error -{ - if (x>0) return x; - - // missing a return... -} - -``` - ---- - -### 10.7 Control Flow Restrictions - -Rules: - -* There is `break` and `continue`. - - `break` terminates the innermost enclosing loop. - - `continue` continues to the next iteration of the innermost enclosing loop. -* There is no implicit fallthrough. -* All control flows are explicit and structured. - ---- - -### 10.8 Summary of Expression Rules - -* `when`, and blocks are expressions. -* All branching is explicit and typed. -* `if` is flow control. -* Loops operate over bounded ranges. -* Control flow is predictable and structured. - ---- - -## 10. Return Fallback (`else`) - -A function can have an `else` clause, it is sugar for the sake of a fallback value. -* `else` can be explicit for `optional` but it is not required, since `none` is always a return value if nothing else is returned. - - `fn f(): optional else none {}` == `fn f(): optional { return none; }` -* `result` can have an `else` clause. however, it should be explicit `error` or provided by function body (no fallback). - -```pbs -fn v(): Vector else Vector.ZERO -{ - if cond return Vector.ONE; -} -``` - -Used to guarantee total functions without boilerplate. - ---- - -## 11. Numeric Rules & `bounded` - -This section defines numeric types, numeric conversions, the special `bounded` type, and the built-in `range` value type. -All rules in this section are **normative**. - -PBS numeric rules are designed to be **explicit**, **predictable**, and **safe by default**. - ---- - -### 11.1 Numeric Type Hierarchy - -PBS defines the following numeric types: - -* `int` — 32-bit signed integer -* `long` — 64-bit signed integer -* `float` — 32-bit IEEE-754 floating point -* `double` — 64-bit IEEE-754 floating point -* `bounded` — unsigned 16-bit integer (`0 .. 65535`) - -Rules: - -* All numeric types are value types. -* Numeric types have no identity and no implicit mutability. - ---- - -### 11.2 Implicit Widening Conversions - -PBS allows **implicit widening** conversions only. - -Allowed implicit conversions: - -``` -int → long -int → float -int → double -long → float -long → double -float → double -bounded → int -bounded → long -``` - -Rules: - -* Implicit widening never loses information. -* Any conversion not listed above requires an explicit cast. - ---- - -### 11.3 Explicit Casts - -Explicit casts use the syntax: - -```pbs -expr as Type -``` - -Rules: - -* Explicit casts are required for any narrowing or potentially lossy conversion. -* Explicit casts are checked at compile time and/or runtime as specified below. - ---- - -### 11.4 The `bounded` Type - -`bounded` is a dedicated scalar type for **indices, sizes, and counters**. - -Purpose: - -* Make indexing and counting explicit in the type system. -* Prevent accidental misuse of general-purpose integers. -* Enable predictable and safe looping semantics. - -Representation: - -* Internally represented as an unsigned 16-bit integer (`u16`). -* Valid range: `0 .. 65535`. - -Literal syntax: - -```pbs -let a: bounded = 10b; -let b = 0b; -``` - ---- - -### 11.5 Conversions Involving `bounded` - -Rules: - -* `bounded → int` and `bounded → long` are implicit. -* `int → bounded` and `long → bounded` require an explicit cast. -* `float` and `double` cannot be cast to `bounded`. - -Casting to `bounded`: - -```pbs -let b: bounded = x as bounded; -``` - -Semantics: - -* Casting to `bounded` performs a **clamping operation**: - - * values `< 0` become `0b` - * values `> 65535` become `65535b` -* A compile-time or runtime **warning** must be issued when clamping occurs. - ---- - -### 11.6 Arithmetic on `bounded` - -Operations on `bounded` are intentionally limited. - -Allowed operations: - -* comparison (`==`, `!=`, `<`, `<=`, `>`, `>=`) -* addition (`+`) -* subtraction (`-`) - -Disallowed operations: - -* multiplication -* division -* modulo -* bitwise operations - -Rules: - -* Addition and subtraction on `bounded` are **checked**. -* If an operation would overflow or underflow: - - * the result is clamped to the valid range, and - * a warning is emitted. - ---- - -### 11.7 The `range` Type - -PBS provides a built-in value type `range` for representing bounded intervals. - -A `range` represents a **half-open interval**: - -`[min .. max)` - min is inclusive and max is exclusive. - -That is, the sequence: - -`min, min + 1, ..., max - 1` - ---- - -#### Construction - -A `range` is constructed using the built-in `range` constructor. - -```pbs -range(max: bounded) -range(min: bounded, max: bounded) -``` - -Examples: - -```pbs -let r1: range = range(10b); // [0b .. 10b) -let r2: range = range(5b, 15b); // [5b .. 15b) -let r3: range = range(5b, a.len()); // [5b .. a.len()) -``` - -Rules: - -* The single-argument form sets `min` to `0b`. -* The two-argument form sets both bounds explicitly. -* `max` must always be provided. -* If `min >= max`, the range is empty. -* `range` values are immutable. - ---- - -#### API - -A `range` exposes the following read-only fields: - -* `min: bounded` -* `max: bounded` - -Optional helper operations: - -* `count(): bounded` -* `isEmpty(): bool` - ---- - -### 11.8 Numeric Safety Guarantees - -PBS guarantees that: - -* No implicit narrowing conversions occur. -* All potentially lossy conversions are explicit. -* `bounded` operations cannot produce undefined behavior. -* `range` values have well-defined, deterministic semantics. -* Numeric behavior is deterministic across platforms. - ---- - -### 11.9 Summary of Numeric Rules - -* Implicit conversions are widening only. -* Narrowing conversions require explicit casts. -* `bounded` is used for indices and sizes. -* `bounded` arithmetic is limited and checked. -* `range` represents half-open bounded intervals. -* Numeric behavior is explicit and predictable. - ---- - -## 12. `optional` - -This section defines the `optional` type. -All rules in this section are **normative**. - -`optional` represents the **explicit presence or absence** of a value, without exceptions, traps, or implicit unwrapping. - ---- - -### 12.1 Purpose and Design Goals - -`optional` exists to model situations where a value: - -* 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 - ---- - -### 12.2 Type Definition - -`optional` is a built-in generic value type. - -Rules: - -* `T` must be a valid value type. -* `optional` itself is a value type. -* `optional` has no identity and no aliasing semantics. - ---- - -### 12.3 Construction - -An `optional` value is constructed using one of the following forms: - -```pbs -some(value) -none -``` - -Rules: - -* `some(value)` produces an `optional` containing `value`. `T` follow the same type as `value`. -* `none` produces an empty `optional`. -* `none` represents the absence of a value. -* `none` must be typed by context. - -Examples: - -```pbs -let a: optional = some(10); -let b: optional = none; -``` - -The following is invalid: - -```pbs -let x = none; // ERROR: type of `none` cannot be inferred -``` - ---- - -### 12.4 Extraction with `else` - -The **only** way to extract a value from an `optional` is using the `else` operator. - -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 `T`. - -Example: - -```pbs -let v: int = maybeInt else 0; -``` - -No other form of extraction is permitted. - ---- - -### 12.5 Control Flow and Functions - -Functions returning `optional` may omit an explicit `return none` if no value is returned. - -Example: - -```pbs -fn find(id: int): optional -{ - if (id == 0) return some(42); - // implicit return none -} -``` - -Rules: - -* Falling off the end of a function returning `optional` is equivalent to `return none`. -* This behavior applies only to `optional`. - ---- - -### 12.6 API Surface - -`optional` exposes a minimal, explicit API: - -```pbs -opt.hasSome(): bool -opt.hasNone(): bool -``` - -Rules: - -* These methods do not extract the contained value. -* They exist only for explicit branching logic. - -Example: - -```pbs -if opt.hasSome() then { - // handle presence -} else { - // handle absence -} -``` - ---- - -### 12.7 Mutability and Copying - -Rules: - -* `optional` follows normal value semantics. -* Assigning an `optional` copies the container and its contents. -* Mutability of `T` inside `optional` follows normal binding rules. - ---- - -### 12.8 Summary of `optional` Rules - -* Absence is explicit and normal. -* No implicit unwrapping or traps exist. -* Extraction is only possible via `else`. -* `optional` is stack-only and allocation-free. - ---- - -## 13. `result` - -This section defines the `result` type. -All rules in this section are **normative**. - -`result` represents an **explicit success-or-error outcome**. -It is the only mechanism for error signaling in PBS. - ---- - -### 13.1 Purpose and Design Goals - -`result` exists to model operations that: - -* may succeed and produce a value of type `T`, or -* may fail with a **typed error** `E`. - -Design goals: - -* No exceptions -* No implicit error propagation -* Explicit, typed error handling -* Stack-only representation - ---- - -### 13.2 Type Definition - -`result` is a built-in generic value type. - -Rules: - -* `T` must be a valid value type. -* `E` must be an `error` label type declared via `declare error`. -* `result` itself is a value type. -* `result` has no identity and no aliasing semantics. - ---- - -### 13.3 Construction - -A `result` value is constructed using one of the following forms: - -```pbs -ok(value) -err(errorLabel) -``` - -Rules: - -* `ok(value)` produces a successful result containing `value`. -* `err(label)` produces a failed result containing an `error` label of type `E`. -* The type of `label` must match `E` exactly. - -Examples: - -```pbs -let r1: result = ok(42); -let r2: result = err(IOError.not_found); -``` - ---- - -### 13.4 The `?` Propagation Operator - -The `?` operator propagates errors **only when error types match**. - -Syntax: - -```pbs -value = expr? -``` - -Rules: - -* `expr` must have type `result`. -* The enclosing function must return `result<_, E>` with the **same error type**. -* If `expr` is `ok(v)`, the expression evaluates to `v`. -* If `expr` is `err(e)`, the enclosing function returns `err(e)` immediately. - -Example: - -```pbs -fn g(): result -{ - return ok(true); -} - -fn f(): result -{ - let x: int = when g()? then 1 else 0; - return ok(x + 1); -} -``` - ---- - -### 13.5 Error Handling with `handle` - -`handle` is the **only** construct that allows extracting a value from a `result` while mapping errors. - -Syntax: - -```pbs -value = handle expr -{ - ErrorA.case1 => ErrorB.mapped1, - ErrorA.case2 => ErrorB.mapped2, - _ => ErrorB.default, -}; -``` - -Rules: - -* `expr` must have type `result`. -* The enclosing function must return `result<_, E2>`. -* Each arm maps an error label of `E1` to a label of `E2`. -* Arms must be exhaustive, either explicitly or via `_`. - -Semantics: - -* If `expr` is `ok(v)`, the expression evaluates to `v`. -* If `expr` is `err(e)`, the first matching arm is selected and the function returns `err(mapped)` immediately. - ---- - -### 13.6 Restrictions - -Rules: - -* There is no implicit extraction of `result`. -* There is no pattern matching beyond `handle`. -* `result` cannot be implicitly converted to `optional` or vice versa. -* Falling off the end of a function returning `result` is a compile-time error. - ---- - -### 13.7 Mutability and Copying - -Rules: - -* `result` follows normal value semantics. -* Assigning a `result` copies the container and its contents. -* Mutability of `T` follows normal binding rules. - ---- - -### 13.8 Summary of `result` Rules - -* `result` is the only error signaling mechanism. -* Errors are typed and explicit. -* `?` propagates errors only when types match. -* `handle` is the only construct for error mapping and extraction. -* No exceptions or traps exist. - ---- - -## 14. Tuples - -PBS provides **tuples** as a lightweight, stack-only aggregation type. - -Tuples exist to support **small, local groupings of values** without introducing heap allocation, gates, aliasing, or reference counting. They intentionally fill the ergonomic gap left by removing arrays from the stack. - -Tuples belong entirely to the **SAFE world**. - ---- - -### 14.1 Tuple Type - -The tuple type is written as: - -```pbs -Tuple(T1, T2, ..., Tn) -``` - -Each element has a fixed position and type. Tuples are **heterogeneous** and their size is known at compile time. - -Example: - -```pbs -let t: Tuple(int, float, bool) = tuple(1, 2.0, true); -``` - ---- - -### 14.2 Construction - -Tuples are constructed using the `tuple` expression: - -```pbs -tuple(expr1, expr2, ..., exprN) -``` - -Each expression is evaluated and stored **inline on the stack**. - -Example: - -```pbs -let position = tuple(10, 20); -let state = tuple("idle", true, 3); -``` - ---- - -### 14.3 Properties - -Tuples have the following properties: - -* Tuples are **value types** -* Tuples live exclusively on the **stack** -* Assignment performs a **conceptual copy** of all elements -* Tuples have **no identity** -* Tuples have **no observable aliasing** -* Tuples never allocate storage - -Because of these properties, tuples are always safe to use and cannot leak memory. - ---- - -### 14.4 Element Access - -Tuple elements are accessed by **zero-based positional index** using dot syntax: - -```pbs -let t = tuple(1, 2.0, true); - -let a: int = t.0; -let b: float = t.1; -let c: bool = t.2; -``` - -Index access is statically checked and always safe. - ---- - -### 14.5 Destructuring - -Tuples may be destructured into individual bindings: - -```pbs -let t = tuple(1, 2.0, true); -let (a, b, c) = t; -``` - -Types may be explicitly annotated if desired: - -```pbs -let (x: int, y: float, z: bool) = t; -``` - -Destructuring copies each element into a new stack binding. - ---- - -### 14.6 Mutability - -Tuple bindings may be mutable: - -```pbs -let t = mut tuple(1, 2, 3); -t.0 = 10; -``` - -Mutation is always **local to the binding** and never affects other values. - -Example: - -```pbs -let a = tuple(1, 2, 3); -let b = a; // copy - -b.0 = 99; -// a.0 is still 1 -``` - ---- - -### 14.7 Tuples vs Structs - -Tuples and structs are closely related but serve different purposes: - -| Struct | Tuple | -| --------------------- | ----------------- | -| Named type | Anonymous type | -| Declared once, reused | Created inline | -| Named fields | Positional fields | -| Stack value | Stack value | - -Tuples are intended for **local composition**, while structs are intended for **named data models**. - ---- - -### 14.8 Tuples vs Arrays - -Tuples are **not arrays**. - -| Tuple | Array | -| ------------- | --------------------- | -| Stack-only | Gate-backed (storage) | -| Heterogeneous | Homogeneous | -| No aliasing | Aliasing possible | -| No allocation | Requires `alloc` | -| SAFE world | HIP world | - -Tuples are suitable for small, fixed groupings of values. Arrays are suitable for collections and data structures. - ---- - -### 14.9 Didactic Intent - -Tuples exist to reinforce the core PBS design principle: - -> If you do not allocate, you are safe. - -They allow expressive local grouping without weakening the guarantees of the SAFE world or introducing hidden costs. - ---- - -### 14.10 Summary - -* Tuples are stack-only value types -* Tuples are heterogeneous and fixed-size -* Tuples never allocate or alias -* Tuples provide ergonomic local aggregation - -Tuples completes the SAFE world without compromising the explicit nature of the PBS memory model. - ---- - -## 15. Memory Model - -PBS defines a **segmented and explicit memory model** designed to be: - -* **Didactic**: each memory region teaches a distinct mental model -* **Predictable**: no implicit allocation or tracing garbage collection -* **Game-oriented**: execution happens inside loops (frames), with controlled reclamation points -* **Explicit about cost and risk**: moving between regions is always visible in code - -The memory model is divided into four regions: - -1. Constant Pool -2. Stack -3. Gate Pool -4. Storage (Heap) - -Each region has a distinct role, lifetime, and set of guarantees. - ---- - -### 15.1 Constant Pool - -The **Constant Pool** stores **immutable data known at compile time**. - -Examples include: - -* Numeric literals -* String literals -* Compile-time constant structs (e.g. `Vector.ZERO`) - -### Properties - -* Immutable -* Read-only -* Shared globally -* Never reclaimed during execution - -### Example - -```pbs -let a: int = 10; -let s: string = "Hello World"; -let v: Vector = Vector.ZERO; -``` - -In the example above: - -* `10`, `"Hello World"`, and `Vector.ZERO` live in the Constant Pool -* Assignments copy references to constant data, not mutable memory -* Constant Pool values can never be mutated - -The Constant Pool is **not** a general-purpose memory region and cannot store dynamically created data. - ---- - -### 15.2 Stack (SAFE World) - -The **Stack** is the default execution memory for PBS. - -It stores: - -* Local variables -* Function parameters -* Temporary values -* Return values - -### Properties - -* Value-first semantics -* Conceptual copy on assignment -* No observable aliasing -* No pointers or handles -* Deterministic lifetime (scope-bound) - -### Example - -```pbs -fn example(): void -{ - let a: Vector = Vector(1, 2); - let b = a; // conceptual copy - - b.x = 10; - // a.x is still 1 -} -``` - -All values in the Stack are **isolated**. Mutating one value never affects another binding. - -The Stack is the **SAFE world** of PBS. If a program only uses stack values and the Constant Pool, it cannot create memory leaks or aliasing bugs. - ---- - -### 15.3 Gate Pool - -The **Gate Pool** is an internal runtime structure that manages **handles (gates)** to objects stored in the Storage region. - -A **gate** is a small, copyable value that represents access to a storage object. - -### Conceptual Structure - -Each Gate Pool entry contains: - -* A pointer to a storage object -* A strong reference count (RC) -* A weak reference count -* Optional runtime metadata (type id, flags, debug info) - -The Gate Pool is **not directly addressable** by user code, but all gate-backed types are implemented on top of it. - ---- - -### 15.4 Storage (Heap / HIP World) - -The **Storage** region (heap) stores **dynamically allocated, mutable data**. - -Objects in Storage are created explicitly using `alloc`. - -### Example - -```pbs -let a = alloc array[4b]; -``` - -This allocation: - -* Creates a new storage object -* Creates a Gate Pool entry -* Returns a **strong gate** to the caller - -### Properties - -* Mutable -* Aliasing is possible and intentional -* Lifetime managed via reference counting -* Not reclaimed immediately when references disappear - -The Storage region is the **HIP world** of PBS. Power and responsibility are explicit. - ---- - -### 15.5 Strong and Weak Gates - -All gate-backed types use **gates** to access storage objects. - -### Strong Gates - -* Represented directly by the type `T` -* Increment strong reference count -* Keep the storage object alive - -```pbs -let a: Node = alloc Node; -let b = a; // RC++ -``` - -### Weak Gates - -* Represented by `weak` -* Do not increment strong RC -* Do not keep storage alive - -```pbs -let w: weak = a as weak; -``` - -### Conversion Rules - -```pbs -let w: weak = strongNode as weak; // always valid -let o: optional = w as strong; // may fail -``` - -Weak gates must be promoted to strong gates before use. - ---- - -### 15.6 Reference Counting (RC) - -PBS uses **reference counting** to manage storage lifetimes. - -### RC Tracks - -* Strong gates stored on the stack -* Strong gates stored inside other storage objects - -RC is adjusted automatically when: - -* A strong gate is copied or destroyed -* A strong gate is assigned into or removed from storage - -### Important Notes - -* Cycles of strong references are not reclaimed automatically -* Weak gates do not participate in RC - -This behavior is intentional and mirrors real-world systems such as C++ `shared_ptr` / `weak_ptr`. - ---- - -### 15.7 Reclamation and Frame Boundaries - -PBS does not use tracing garbage collection. - -Instead, storage reclamation happens at **safe runtime points**, typically: - -* End of a frame -* Explicit runtime GC step (implementation-defined) - -At reclamation time: - -* Storage objects with strong RC == 0 are destroyed -* Associated Gate Pool entries are cleaned up when no weak references remain - -This model is predictable and suitable for real-time systems. - ---- - -### 15.8 Crossing Memory Regions - -Transitions between memory regions are always explicit: - -| Operation | Effect | -| --------- | -------------------------- | -| `alloc` | Stack → Storage | -| `peek` | Storage → Stack (copy) | -| `borrow` | Temporary read-only access | -| `mutate` | Temporary mutable access | -| `take` | Single mutation sugar | - -### Example - -```pbs -let a = alloc array[2b]; - -mutate a as aa -{ - aa[0b] = 10; -} - -let v = peek a[0b]; // copy back to stack -``` - ---- - -### 15.9 Didactic Intent - -The memory model is intentionally structured to teach: - -* Stack vs heap -* Value vs identity -* Ownership and aliasing -* Cost of allocation -* Why cycles leak under RC - -Programs that remain in the Stack and Constant Pool are **safe by construction**. -Programs that enter Storage gain power, flexibility, and responsibility. - -PBS does not hide these trade-offs. - ---- - -### 15.10 Summary - -* Constant Pool: immutable, global -* Stack: safe, value-first -* Gate Pool: handle management and RC -* Storage: mutable, shared, explicit - -PBS makes memory **visible**, **intentional**, and **teachable**. - ---- - -## 16. Gates (Strong & Weak) - -PBS introduces **gates** as the fundamental mechanism for accessing dynamically allocated storage. - -Gates make **identity, aliasing, and lifetime** explicit, while keeping the SAFE world free of references and pointers. - -This chapter defines what gates are, how **strong** and **weak** gates behave, and how they interact with the memory model. - ---- - -### 16.1 What Is a Gate - -A **gate** is a small, copyable value that represents access to an object stored in **Storage (heap)**. - -Gates are not pointers in the traditional sense, but they serve a similar role: - -* They refer to a storage object -* They can be copied cheaply -* They may alias the same underlying data - -All gate-backed types are created explicitly using `alloc`. - -```pbs -let a = alloc array[4b]; -``` - -The value returned by `alloc` is a **gate**. - ---- - -### 16.2 Gate-Backed Types - -The following kinds of types are gate-backed: - -* Built-in collections (`array`, `list`, `map`, `text`) -* User-defined storage types (`declare storage struct`) - -Gate-backed types live in Storage and are accessed exclusively through gates. - ---- - -### 16.3 Strong Gates - -A **strong gate** is the default form of a gate-backed value. - -Properties: - -* Represented directly by the type `T` -* Increment the strong reference count (RC) -* Keep the storage object alive -* Assignment creates an alias - -Example: - -```pbs -let a: Node = alloc Node; -let b = a; // alias, RC++ -``` - -Both `a` and `b` refer to the same storage object. - ---- - -### 16.4 Weak Gates - -A **weak gate** is a non-owning reference to a storage object. - -Properties: - -* Represented by the type `weak` -* Do not increment strong RC -* Do not keep the storage object alive -* May become invalid when the object is reclaimed - -Weak gates are used to break reference cycles and express non-owning relationships. - -Example: - -```pbs -let s: Node = alloc Node; -let w: weak = s as weak; -``` - ---- - -### 16.5 Conversions Between Strong and Weak - -PBS provides explicit conversions between strong and weak gates. - -### Strong to Weak - -```pbs -let w: weak = s as weak; -``` - -* Always succeeds -* Does not affect strong RC - -### Weak to Strong - -```pbs -let o: optional = w as strong; -``` - -* May fail -* Returns `none` if the object has already been reclaimed -* Returns `some(T)` if promotion succeeds - -These conversions make ownership and lifetime explicit. - ---- - -### 16.6 Reference Counting and Lifetime - -Each storage object has: - -* A strong reference count (RC) -* A weak reference count - -Rules: - -* Strong gates increment and decrement RC automatically -* Weak gates do not affect strong RC -* When strong RC reaches zero, the storage object becomes eligible for reclamation - -Reclamation occurs at safe runtime points (e.g. end of frame). - -Weak gates may outlive the storage object they reference. - ---- - -### 16.7 Gate Validity - -A strong gate is always valid. - -A weak gate may be: - -* **Valid**: the storage object still exists -* **Expired**: the storage object has been reclaimed - -Expired weak gates cannot be used directly and must be promoted first. - -```pbs -let maybeNode: optional = w as strong; -``` - ---- - -### 16.8 Interaction with Access Control - -Only **strong gates** may be used with: - -* `peek` -* `borrow` -* `mutate` -* `take` - -Weak gates must be promoted before any access is allowed. - -This rule prevents accidental use of expired references. - ---- - -### 16.9 Didactic Intent - -Gates are designed to teach: - -* The difference between value and identity -* Ownership versus observation -* Why aliasing exists and how it propagates -* Why reference cycles leak under RC -* How weak references break cycles safely - -PBS does not hide these concepts. - -If a program does not allocate, gates do not exist. -If it allocates, gates make responsibility explicit. - ---- - -### 16.10 Summary - -* Gates are explicit handles to storage objects -* Strong gates own and keep objects alive -* Weak gates observe without ownership -* Conversions between strong and weak are explicit -* Access to storage always requires a strong gate - -Gates are the foundation of the HIP world in PBS. - ---- - -# 17. Gate-backed Array - -PBS provides **gate-backed arrays** as fixed-size, homogeneous collections stored in **Storage (heap)**. - -Gate-backed arrays are the primary indexed collection type in the HIP world. They deliberately trade safety for power and explicit control. - ---- - -## 17.1 Array Type - -The array type is written as: - -```pbs -array[N] -``` - -Where: - -* `T` is the element type -* `N` is a compile-time constant size (`bounded`) - -Arrays are **homogeneous** and **fixed-size**. - ---- - -## 17.2 Allocation - -Arrays are created explicitly using `alloc`: - -```pbs -let a: array[4b] = alloc array[4b]; -``` - -Allocation: - -* Creates a storage object -* Registers it in the Gate Pool -* Returns a **strong gate** - -Arrays cannot exist on the stack. - ---- - -## 17.3 Properties - -Gate-backed arrays have the following properties: - -* Stored in Storage (heap) -* Accessed via gates -* Assignment copies the gate (aliasing) -* Elements are laid out contiguously -* Size is fixed for the lifetime of the array - -Arrays are **not value types**. - ---- - -## 17.4 Aliasing Semantics - -Assigning an array copies the gate, not the data: - -```pbs -let a = alloc array[2b]; -let b = a; // alias -``` - -Mutations through one alias are visible through all aliases. - ---- - -## 17.5 Element Access - -Direct element access using `a[i]` is **not allowed**. - -All element access is explicit and controlled. - -### Reading Elements (`peek`) - -```pbs -let v: int = peek a[1b]; -``` - -* Returns a value copy -* SAFE operation -* Never exposes references - -### Writing Elements (`mutate`) - -```pbs -mutate a as aa -{ - aa[1b] = 42; -} -``` - -* Requires mutable access -* Mutation is shared across aliases - ---- - -## 17.6 Iteration - -Arrays may be iterated by value: - -```pbs -for v in a -{ - // v is a value copy -} -``` - -Iteration copies each element from storage into the stack. - -To iterate indices explicitly: - -```pbs -for i in a.indices() -{ - let v = peek a[i]; -} -``` - ---- - -## 17.7 Passing Arrays to Functions - -Arrays are passed by gate (handle), not by value: - -```pbs -fn setFirst(a: array[2b], v: int): void -{ - mutate a as aa - { - aa[0b] = v; - } -} -``` - -Calling this function mutates the caller's array. - ---- - -## 17.8 Copying Arrays - -To duplicate an array's contents, use `copy`: - -```pbs -let b = copy(a); -``` - -`copy`: - -* Allocates a new array -* Copies all elements -* Returns a new strong gate - ---- - -## 17.9 Bounds and Safety - -Array indices are statically bounded by `N`. - -* Out-of-bounds access is a runtime error -* Index values are of type `bounded` - -These checks preserve safety without hiding cost. - ---- - -## 17.10 Didactic Intent - -Gate-backed arrays exist to teach: - -* Heap allocation -* Aliasing through handles -* Explicit mutation -* Copy versus alias -* Cost of indexed collections - -Arrays are powerful but intentionally not implicit or convenient. - ---- - -## 17.11 Summary - -* Arrays are gate-backed, fixed-size collections -* Arrays live in Storage and require `alloc` -* Assignment aliases; `copy` duplicates -* Element access is explicit (`peek`, `mutate`) -* Arrays belong to the HIP world - -Arrays are the foundation for structured data in PBS. - ---- - -## 18. Gate-backed List - -PBS provides **gate-backed lists** as dynamic, growable collections stored in **Storage (heap)**. - -Lists complement gate-backed arrays by supporting variable size and incremental construction, at the cost of additional runtime overhead and indirection. - ---- - -### 18.1 List Type - -The list type is written as: - -```pbs -list -``` - -Where `T` is the element type. - -Lists are **homogeneous** and **dynamically sized**. - ---- - -### 18.2 Allocation - -Lists are created explicitly using `alloc`: - -```pbs -let xs: list = alloc list; -``` - -Allocation: - -* Creates an empty list in Storage -* Registers it in the Gate Pool -* Returns a **strong gate** - -Lists cannot exist on the stack. - ---- - -### 18.3 Properties - -Gate-backed lists have the following properties: - -* Stored in Storage (heap) -* Accessed via gates -* Assignment copies the gate (aliasing) -* Dynamic length -* Elements may be reallocated internally - -Lists are **not value types**. - ---- - -### 18.4 Aliasing Semantics - -Assigning a list copies the gate, not the data: - -```pbs -let a = alloc list; -let b = a; // alias -``` - -Mutations through one alias are visible through all aliases. - ---- - -### 18.5 Core Operations - -### Length - -```pbs -let n: bounded = borrow xs as xsr { xsr.len() }; -``` - -### Appending Elements - -Elements are appended using mutation: - -```pbs -mutate xs as xsr -{ - xsr.push(10); - xsr.push(20); -} -``` - -For single operations, `take` may be used as syntactic sugar: - -```pbs -take xs.push(30); -``` - ---- - -### 18.6 Element Access - -Direct element access using `xs[i]` is **not allowed**. - -### Reading Elements (`peek`) - -```pbs -let v: int = peek xs[i]; -``` - -* Returns a value copy -* SAFE operation - -### Writing Elements (`mutate`) - -```pbs -mutate xs as xsr -{ - xsr[i] = 42; -} -``` - ---- - -### 18.7 Iteration - -Lists may be iterated by value: - -```pbs -for v in xs -{ - // v is a value copy -} -``` - -To iterate indices explicitly: - -```pbs -for i in xs.indices() -{ - let v = peek xs[i]; -} -``` - ---- - -### 18.8 Passing Lists to Functions - -Lists are passed by gate (handle), not by value: - -```pbs -fn append(xs: list, v: int): void -{ - take xs.push(v); -} -``` - -Calling this function mutates the caller's list. - ---- - -### 18.9 Copying Lists - -To duplicate a list and its contents, use `copy`: - -```pbs -let ys = copy(xs); -``` - -`copy`: - -* Allocates a new list -* Copies all elements -* Returns a new strong gate - ---- - -### 18.10 Performance Notes - -* Appending may trigger internal reallocation -* Element access is O(1) -* Iteration copies elements to the stack - -Lists trade predictability for flexibility. - ---- - -### 18.11 Didactic Intent - -Gate-backed lists exist to teach: - -* Dynamic allocation -* Growth and reallocation cost -* Aliasing through handles -* Explicit mutation patterns - -Lists are powerful but require discipline. - ---- - -### 18.12 Summary - -* Lists are gate-backed, dynamic collections -* Lists live in Storage and require `alloc` -* Assignment aliases; `copy` duplicates -* Mutation is explicit (`mutate`, `take`) -* Lists belong to the HIP world - -Lists provide flexibility while preserving explicit control. - ---- - -# 19. Gate-backed Set - -PBS provides **gate-backed sets** as unordered collections of unique values stored in **Storage (heap)**. - -Sets are designed for membership tests, uniqueness constraints, and dynamic collections where ordering is irrelevant. - -Like all gate-backed collections, sets belong to the **HIP world** and make allocation, aliasing, and mutation explicit. - ---- - -## 19.1 Set Type - -The set type is written as: - -```pbs -set -``` - -Where `T` is the element type. - -Elements of a set must be **hashable and comparable**. - ---- - -## 19.2 Allocation - -Sets are created explicitly using `alloc`: - -```pbs -let s: set = alloc set; -``` - -Allocation: - -* Creates an empty set in Storage -* Registers it in the Gate Pool -* Returns a **strong gate** - -Sets cannot exist on the stack. - ---- - -## 19.3 Properties - -Gate-backed sets have the following properties: - -* Stored in Storage (heap) -* Accessed via gates -* Assignment copies the gate (aliasing) -* Dynamic size -* No duplicate elements - -Sets are **not value types**. - ---- - -## 19.4 Aliasing Semantics - -Assigning a set copies the gate, not the data: - -```pbs -let a = alloc set; -let b = a; // alias -``` - -Mutations through one alias are visible through all aliases. - ---- - -## 19.5 Core Operations - -### Insertion - -Elements are inserted explicitly: - -```pbs -mutate s as ss -{ - ss.add(10); - ss.add(20); -} -``` - -For single operations, `take` may be used: - -```pbs -take s.add(30); -``` - -Adding an element already present has no effect. - ---- - -### Membership Test - -Membership checks do not mutate the set: - -```pbs -let exists: bool = borrow s as ss -{ - ss.contains(10); -}; -``` - ---- - -### Removal - -Elements are removed explicitly: - -```pbs -mutate s as ss -{ - ss.remove(20); -} -``` - ---- - -## 19.6 Iteration - -Sets may be iterated by value: - -```pbs -for v in s -{ - // v is a value copy -} -``` - -Iteration order is implementation-defined. - ---- - -## 19.7 Passing Sets to Functions - -Sets are passed by gate (handle), not by value: - -```pbs -fn addIfMissing(s: set, v: int): void -{ - let exists = borrow s as ss { ss.contains(v) }; - if (!exists) - { - take s.add(v); - } -} -``` - -Calling this function mutates the caller's set. - ---- - -## 19.8 Copying Sets - -To duplicate a set and its contents, use `copy`: - -```pbs -let s2 = copy(s); -``` - -`copy`: - -* Allocates a new set -* Copies all elements -* Returns a new strong gate - ---- - -## 19.9 Performance Notes - -* Membership tests are expected to be O(1) average -* Insertions and removals may rehash internally -* Iteration copies elements into the stack - -Sets trade ordering guarantees for fast membership checks. - ---- - -## 19.10 Didactic Intent - -Gate-backed sets exist to teach: - -* Unordered collections -* Uniqueness constraints -* Membership-oriented logic -* Explicit mutation and aliasing - -Sets are ideal for flags, visited markers, and dynamic uniqueness tracking. - ---- - -## 19.11 Summary - -* Sets are gate-backed, unordered collections -* Sets live in Storage and require `alloc` -* Assignment aliases; `copy` duplicates -* Mutation is explicit (`mutate`, `take`) -* Iteration order is undefined - -Sets complete the family of associative gate-backed collections. - ---- - -## 20. Gate-backed Text - -PBS provides **gate-backed text** as the primary dynamic string type. - -`text` exists to support **runtime string construction, mutation, and concatenation** without weakening the guarantees of the SAFE world. String literals and compile-time strings remain immutable and live in the Constant Pool. - ---- - -### 20.1 Text Type - -The text type is written as: - -```pbs -text -``` - -`text` is a **gate-backed**, dynamically sized sequence of characters stored in **Storage (heap)**. - ---- - -### 20.2 Text vs String - -PBS distinguishes clearly between `string` and `text`: - -| string | text | -| ------------- | -------------- | -| Immutable | Mutable | -| Constant Pool | Storage (heap) | -| Compile-time | Runtime | -| SAFE world | HIP world | - -`string` values can never be mutated or concatenated. All dynamic string operations require `text`. - ---- - -### 20.3 Allocation - -Text objects are created explicitly using `alloc`: - -```pbs -let t: text = alloc text; -``` - -Allocation: - -* Creates an empty text object in Storage -* Registers it in the Gate Pool -* Returns a **strong gate** - ---- - -### 20.4 Properties - -Gate-backed text has the following properties: - -* Stored in Storage (heap) -* Accessed via gates -* Assignment copies the gate (aliasing) -* Dynamic length -* Internally growable - -`text` is **not** a value type. - ---- - -### 20.5 Appending and Mutation - -Text is mutated explicitly using `mutate` or `take`. - -### Appending String Literals - -```pbs -let t = alloc text; - -take t.append("Hello"); -take t.append(", world"); -``` - -### Appending Other Text - -```pbs -let a = alloc text; -let b = alloc text; - -take a.append("Hello"); -take b.append("World"); - -take a.appendText(b); -``` - -All mutations affect all aliases. - ---- - -### 20.6 Reading Text - -Text contents are read by copying into the SAFE world. - -### Convert to String - -```pbs -let s: string = borrow t as tt { tt.toString() }; -``` - -The returned `string` is immutable snapshot copied from text into a SAFE string value runtime-owned. - ---- - -### 20.7 Aliasing Semantics - -Assigning a text value copies the gate: - -```pbs -let a = alloc text; -let b = a; // alias - -take a.append("!"); -``` - -Both `a` and `b` observe the same content. - ---- - -### 20.8 Passing Text to Functions - -Text values are passed by gate (handle): - -```pbs -fn appendExclamation(t: text): void -{ - take t.append("!"); -} -``` - -Calling this function mutates the caller's text. - ---- - -### 20.9 Copying Text - -To duplicate a text value, use `copy`: - -```pbs -let t2 = copy(t); -``` - -`copy`: - -* Allocates a new text object -* Copies all characters -* Returns a new strong gate - ---- - -### 20.10 Performance Notes - -* Appending may trigger internal reallocation -* Converting to `string` copies data -* Text mutation is O(n) in the worst case - -Text trades immutability for flexibility. - ---- - -### 20.11 Didactic Intent - -Gate-backed text exists to teach: - -* The cost of string mutation -* The difference between immutable and mutable strings -* Explicit heap allocation for dynamic text -* Aliasing through handles - -PBS does not allow silent string mutation. - ---- - -### 20.12 Summary - -* `text` is the dynamic string type in PBS -* `string` remains immutable and SAFE -* Text lives in Storage and requires `alloc` -* Mutation is explicit (`mutate`, `take`) -* Assignment aliases; `copy` duplicates - -Text completes the set of fundamental gate-backed collections. - ---- - -## 21. Gate-backed Map - -PBS provides **gate-backed maps** as associative collections stored in **Storage (heap)**. - -Maps associate keys to values and are designed for lookup-oriented data structures where indexed access is not sufficient. - -Like all gate-backed collections, maps belong to the **HIP world** and make aliasing, mutation, and cost explicit. - ---- - -### 21.1 Map Type - -The map type is written as: - -```pbs -map -``` - -Where: - -* `K` is the key type -* `V` is the value type - -Both `K` and `V` must be **value-safe types** or gate-backed types. - ---- - -### 21.2 Allocation - -Maps are created explicitly using `alloc`: - -```pbs -let m: map = alloc map; -``` - -Allocation: - -* Creates an empty map in Storage -* Registers it in the Gate Pool -* Returns a **strong gate** - -Maps cannot exist on the stack. - ---- - -### 21.3 Properties - -Gate-backed maps have the following properties: - -* Stored in Storage (heap) -* Accessed via gates -* Assignment copies the gate (aliasing) -* Dynamic size -* Key-based lookup - -Maps are **not value types**. - ---- - -### 21.4 Aliasing Semantics - -Assigning a map copies the gate, not the data: - -```pbs -let a = alloc map; -let b = a; // alias -``` - -Mutations through one alias are visible through all aliases. - ---- - -### 21.5 Core Operations - -### Insertion and Update - -Entries are inserted or updated via mutation: - -```pbs -mutate m as mm -{ - mm.put("health", 100); - mm.put("mana", 50); -} -``` - -For single operations, `take` may be used: - -```pbs -take m.put("score", 1000); -``` - ---- - -### Lookup - -Lookup operations do not mutate the map and return copied values: - -```pbs -let hp: optional = borrow m as mm -{ - mm.get("health"); -}; -``` - -* Returns `some(value)` if the key exists -* Returns `none` otherwise - ---- - -### Removal - -Entries are removed explicitly: - -```pbs -mutate m as mm -{ - mm.remove("mana"); -} -``` - ---- - -### 21.6 Iteration - -Maps may be iterated by key-value pairs copied into the SAFE world: - -```pbs -for pair in m -{ - let (k, v) = pair; // Tuple(K, V) -} -``` - -Iteration order is implementation-defined. - -To iterate keys or values explicitly: - -```pbs -for k in m.keys() { } -for v in m.values() { } -``` - ---- - -### 21.7 Passing Maps to Functions - -Maps are passed by gate (handle): - -```pbs -fn incrementScore(m: map): void -{ - take m.put("score", (peek m.get("score") else 0) + 1); -} -``` - -Calling this function mutates the caller's map. - ---- - -### 21.8 Copying Maps - -To duplicate a map and its contents, use `copy`: - -```pbs -let m2 = copy(m); -``` - -`copy`: - -* Allocates a new map -* Copies all entries -* Returns a new strong gate - ---- - -### 21.9 Performance Notes - -* Lookup is expected to be O(1) average -* Insertion and removal may rehash internally -* Iteration copies key-value pairs into the stack - -Maps trade predictability for flexibility. - ---- - -### 21.10 Didactic Intent - -Gate-backed maps exist to teach: - -* Associative data structures -* Explicit mutation and lookup -* Cost of dynamic containers -* Aliasing through handles - -Maps are powerful but must be used deliberately. - ---- - -### 21.11 Summary - -* Maps are gate-backed, associative collections -* Maps live in Storage and require `alloc` -* Assignment aliases; `copy` duplicates -* Mutation is explicit (`mutate`, `take`) -* Lookup returns optional values - -Maps complete the core set of gate-backed collection types in PBS. - ---- - -# 22. Gate-backed Deque - -PBS provides **gate-backed deques** (double-ended queues) as flexible, dynamic collections stored in **Storage (heap)**. - -A `deque` supports efficient insertion and removal at both ends and serves as the **foundational structure** for queues and stacks in PBS. - -Like all gate-backed collections, deques belong to the **HIP world** and make allocation, aliasing, and mutation explicit. - ---- - -## 22.1 Deque Type - -The deque type is written as: - -```pbs -deque -``` - -Where `T` is the element type. - -Deques are **homogeneous** and **dynamically sized**. - ---- - -## 22.2 Allocation - -Deques are created explicitly using `alloc`: - -```pbs -let d: deque = alloc deque; -``` - -Allocation: - -* Creates an empty deque in Storage -* Registers it in the Gate Pool -* Returns a **strong gate** - -Deques cannot exist on the stack. - ---- - -## 22.3 Properties - -Gate-backed deques have the following properties: - -* Stored in Storage (heap) -* Accessed via gates -* Assignment copies the gate (aliasing) -* Dynamic size -* Efficient operations at both ends - -Deques are **not value types**. - ---- - -## 22.4 Aliasing Semantics - -Assigning a deque copies the gate, not the data: - -```pbs -let a = alloc deque; -let b = a; // alias -``` - -Mutations through one alias are visible through all aliases. - ---- - -## 22.5 Core Operations - -### Length - -```pbs -let n: bounded = borrow d as dd { dd.len() }; -``` - -### Push Operations - -```pbs -take d.pushFront(10); -take d.pushBack(20); -``` - -### Pop Operations - -```pbs -let a: optional = borrow d as dd { dd.popFront() }; -let b: optional = borrow d as dd { dd.popBack() }; -``` - -Pop operations return `optional` and do not mutate when the deque is empty. - ---- - -## 22.6 Element Access - -Direct indexed access is supported for convenience: - -```pbs -let v: int = peek d[i]; -``` - -Mutation by index requires `mutate`: - -```pbs -mutate d as dd -{ - dd[i] = 42; -} -``` - ---- - -## 22.7 Iteration - -Deques may be iterated by value: - -```pbs -for v in d -{ - // v is a value copy -} -``` - -Iteration order is front to back. - ---- - -## 22.8 Passing Deques to Functions - -Deques are passed by gate (handle), not by value: - -```pbs -fn pushPair(d: deque, a: int, b: int): void -{ - take d.pushBack(a); - take d.pushBack(b); -} -``` - -Calling this function mutates the caller's deque. - ---- - -## 22.9 Copying Deques - -To duplicate a deque and its contents, use `copy`: - -```pbs -let d2 = copy(d); -``` - -`copy`: - -* Allocates a new deque -* Copies all elements -* Returns a new strong gate - ---- - -## 22.10 Queue and Stack Views - -In PBS, **queues** and **stacks** are not separate data structures. Instead, they are **usage conventions** built on top of `deque`. - -### Queue (FIFO) - -A queue uses the following operations: - -* Enqueue: `pushBack` -* Dequeue: `popFront` - -Example: - -```pbs -let q: deque = alloc deque; - -take q.pushBack(evt1); -take q.pushBack(evt2); - -let e1 = borrow q as qq { qq.popFront() }; -``` - -### Stack (LIFO) - -A stack uses the following operations: - -* Push: `pushBack` -* Pop: `popBack` - -Example: - -```pbs -let s: deque = alloc deque; - -take s.pushBack(1); -take s.pushBack(2); - -let top = borrow s as ss { ss.popBack() }; -``` - -This approach reduces the number of built-in types while preserving expressiveness. - ---- - -## 22.11 Performance Notes - -* Push and pop operations are expected to be O(1) amortized -* Indexed access may be O(1) or O(n), implementation-defined -* Iteration copies elements into the stack - ---- - -## 22.12 Didactic Intent - -Gate-backed deques exist to teach: - -* Flexible data structures -* FIFO vs LIFO semantics -* Explicit mutation and aliasing -* How abstractions emerge from disciplined usage - -Queues and stacks are concepts, not primitives. - ---- - -## 22.13 Summary - -* `deque` is a gate-backed, double-ended queue -* Deques live in Storage and require `alloc` -* Assignment aliases; `copy` duplicates -* Queue and stack are usage patterns - -Deques complete the set of core dynamic collections in PBS. - ---- - -## 23. Gate-backed Blob - -PBS provides **gate-backed blobs** as raw, byte-addressable buffers stored in **Storage (heap)**. - -A `blob` is the fundamental building block for: - -* Save data and serialization -* Asset loading (sprites, maps, audio chunks) -* Networking payloads -* Bridging to host APIs - -Blobs are intentionally low-level, but they remain **explicit** and **teachable** in the HIP world. - ---- - -### 23.1 Blob Type - -The blob type is written as: - -```pbs -blob -``` - -A `blob` is a dynamic buffer of bytes. - ---- - -### 23.2 Allocation - -Blobs are created explicitly using `alloc` with a size: - -```pbs -let b: blob = alloc blob(256b); -``` - -Allocation: - -* Creates a storage object containing `size` bytes -* Registers it in the Gate Pool -* Returns a **strong gate** - ---- - -### 23.3 Properties - -Gate-backed blobs have the following properties: - -* Stored in Storage (heap) -* Accessed via gates -* Assignment copies the gate (aliasing) -* Byte-addressable -* Size is fixed after allocation (v0) - -Blobs are **not value types**. - ---- - -### 23.4 Aliasing Semantics - -Assigning a blob copies the gate, not the data: - -```pbs -let a = alloc blob(16b); -let c = a; // alias - -take a.writeU8(0b, 255); -let x = borrow c as cc { cc.readU8(0b) }; // x == 255 -``` - -Mutations through one alias are visible through all aliases. - ---- - -### 23.5 Core Operations - -The following operations are required for `blob`. - -### Size - -```pbs -let n: bounded = borrow b as bb { bb.size() }; -``` - -### Read/Write Bytes - -```pbs -take b.writeU8(0b, 42); -let v: bounded = borrow b as bb { bb.readU8(0b) }; -``` - -Indices are `bounded` and refer to byte offsets. - ---- - -### 23.6 Typed Reads and Writes - -For convenience and portability, blobs may support typed operations. - -The endianness of typed operations is **explicit**. - -Examples (LE shown): - -```pbs -take b.writeU16LE(0b, 0xBEEF); -let x: bounded = borrow b as bb { bb.readU16LE(0b) }; - -take b.writeI32LE(2b, -10); -let y: int = borrow b as bb { bb.readI32LE(2b) }; -``` - -Implementations may provide a minimal subset initially (e.g. only U8/U16/U32). - ---- - -### 23.7 Slicing and Views (Deliberately Deferred) - -`blob` does not expose raw pointers or escaping views in v0. - -If slicing is introduced later, it must preserve PBS rules: - -* No escaping references -* Explicit cost -* Explicit lifetime - ---- - -### 23.8 Bounds and Safety - -* Out-of-bounds reads and writes are runtime errors -* Typed reads/writes must also bounds-check their byte width - -These checks keep the model teachable without hiding risk. - ---- - -### 23.9 Passing Blobs to Functions - -Blobs are passed by gate (handle), not by value: - -```pbs -fn fill(b: blob, value: bounded): void -{ - let n = borrow b as bb { bb.size() }; - let i = 0b; - while (i < n) - { - take b.writeU8(i, value); - i = i + 1b; - } -} -``` - -Calling this function mutates the caller's blob. - ---- - -### 23.10 Copying Blobs - -To duplicate a blob and its contents, use `copy`: - -```pbs -let b2 = copy(b); -``` - -`copy`: - -* Allocates a new blob with the same size -* Copies all bytes -* Returns a new strong gate - ---- - -### 23.11 Didactic Intent - -Gate-backed blobs exist to teach: - -* Raw memory as bytes -* Bounds checking -* Serialization patterns -* Explicit mutation and aliasing - -Blobs provide low-level power without introducing implicit pointers. - ---- - -### 23.12 Summary - -* `blob` is a gate-backed byte buffer -* Blobs live in Storage and require `alloc blob(size)` -* Assignment aliases; `copy` duplicates -* Mutation is explicit (`mutate`, `take`) -* Bounds are checked at runtime - -Blobs are the foundation for binary data handling in PBS. - ---- - -# 24. Custom Storage API - -PBS allows users to define **custom gate-backed storage types** using the `declare storage struct` construct. - -A `storage struct` follows the same declaration model as a regular `struct`, but lives in **Storage (heap)** and obeys **HIP-world semantics**. Value-only features are intentionally not available. - -This chapter defines the rules, capabilities, and limitations of custom storage types. - ---- - -## 24.1 Storage Struct Declaration - -A storage struct is declared using: - -```pbs -declare storage struct S(x: int, b: bool) -{ - fn f(self: mut this): void - { - // ... - } -} -``` - -The declaration consists of: - -* A **name** (`S`) -* A **field list** declared in the header -* A **method body** block - -Fields declared in the header are the **only fields** of the storage struct. - ---- - -## 24.2 Allocation - -Storage structs are created explicitly using `alloc`: - -```pbs -let s: S = alloc S; -``` - -Allocation: - -* Creates a storage object -* Registers it in the Gate Pool -* Returns a **strong gate** - -Storage structs cannot exist on the stack and cannot be created by value construction. - ---- - -## 24.3 Properties - -Storage structs have the following properties: - -* Stored in Storage (heap) -* Accessed via gates -* Assignment copies the gate (aliasing) -* Lifetime managed via reference counting -* Fields may contain: - - * value types - * gate-backed types - * `weak` and `optional` - -Storage structs are **not value types**. - ---- - -## 24.4 Field Access Rules - -Fields of a storage struct may only be accessed inside `borrow` or `mutate` blocks. - -### Reading Fields - -```pbs -let hp: int = borrow s as ss -{ - ss.x; -}; -``` - -### Writing Fields - -```pbs -mutate s as ss -{ - ss.b = true; -} -``` - -Direct field access outside these blocks is not allowed. - ---- - -## 24.5 Methods on Storage Structs - -Storage structs may define methods using the same syntax as regular structs. - -```pbs -fn f(self: mut this): void -{ - self.x = self.x + 1; -} -``` - -Rules: - -* `self: this` provides read-only access -* `self: mut this` provides mutable access -* Mutating methods require a mutable context to be called - ---- - -## 24.6 Mutation Sugar (`take`) - -For single mutating method calls, `take` may be used: - -```pbs -take s.f(); -``` - -Equivalent to: - -```pbs -mutate s as ss -{ - ss.f(); -} -``` - ---- - -## 24.7 Strong and Weak Fields - -Fields of a storage struct may be declared as strong or weak gates. - -```pbs -declare storage struct Node(value: int, parent: optional>) -{ - fn attach(self: mut this, p: Node): void - { - self.parent = some(p as weak); - } -} -``` - -Rules: - -* Strong fields increment reference counts -* Weak fields do not keep objects alive -* Weak fields must be promoted before use - -This enables cyclic graphs without leaks. - ---- - -## 24.8 Copying Storage Structs - -To duplicate a storage struct and its contents, use `copy`: - -```pbs -let s2 = copy(s); -``` - -`copy`: - -* Allocates a new storage object -* Performs a deep copy of all fields -* Returns a new strong gate - ---- - -## 24.9 Differences from `struct` - -While `storage struct` shares syntax with `struct`, several features are intentionally missing. - -### Not Available in `storage struct` - -* Constructors (`[]`) -* Static blocks (`[[]]`) -* Value initialization syntax (`S(1, true)`) -* Stack allocation - -Initialization of storage structs must be performed explicitly after allocation. - -```pbs -let s = alloc S; -mutate s as ss -{ - ss.x = 10; - ss.b = false; -} -``` - -These restrictions preserve the explicit nature of heap allocation. - ---- - -## 24.10 Interaction with Built-in Collections - -Storage structs may freely contain and manipulate gate-backed collections. - -```pbs -declare storage struct Inventory(items: list) -{ - fn add(self: mut this, it: Item): void - { - take self.items.push(it); - } -} -``` - -All collection semantics (aliasing, mutation, copying) apply recursively. - ---- - -## 24.11 Didactic Intent - -The Custom Storage API exists to teach: - -* Heap allocation without implicit safety nets -* Ownership and aliasing in user-defined types -* Explicit initialization and mutation -* How complex data structures are built from primitives - -Storage structs intentionally expose power without hiding cost. - ---- - -## 24.12 Summary - -* `declare storage struct` defines custom gate-backed types -* Storage structs live in Storage and require `alloc` -* Fields are declared in the header -* No constructors, static blocks, or value initialization -* Access is controlled via `borrow`, `mutate`, and `take` -* Strong and weak fields control lifetime - -Custom storage structs complete the PBS HIP world. - ---- - -# 25. Box and Unbox (Built-in Heap Values) - -PBS is a **value-first language**: primitive types and `struct` values live on the **stack** and follow SAFE-world semantics. - -In some situations, however, a value must acquire **identity**: - -* It must be shared between multiple owners -* It must be mutated through aliases -* It must outlive a stack frame - -For these cases, PBS provides the built-in **Box** abstraction. - -`Box` is a **gate-backed built-in type**, and `box` / `unbox` are **built-in operations**. - ---- - -## 25.1 Box Type - -```pbs -Box -``` - -A `Box`: - -* Stores exactly one value of type `T` in **Storage (heap)** -* Is gate-backed (strong gate) -* Participates in aliasing and reference counting - -`Box` is not a value type. - ---- - -## 25.2 Boxing a Value (`box`) - -The `box` builtin allocates a `Box` in Storage and initializes it with a **copy** of a SAFE value. - -```pbs -let v: Vector = Vector.ZERO; // SAFE (stack) -let b: Box = box(v); // HIP (heap) -``` - -Rules: - -* `box(x)` performs a copy of `x` -* The original value remains unchanged on the stack -* The result is a strong gate - ---- - -## 25.3 Unboxing a Value (`unbox`) - -The `unbox` builtin copies the value stored in a box back into the SAFE world. - -```pbs -let vv: Vector = unbox(b); -``` - -Rules: - -* `unbox(b)` always returns a **copy** -* The returned value has no identity -* Mutating the returned value does not affect the box - ---- - -## 25.4 Generic Form - -The `box` and `unbox` builtins are fully generic: - -```pbs -let b: Box = box(t); -let t2: T = unbox(b); -``` - -This works for any SAFE type `T`. - ---- - -## 25.5 Aliasing and Mutation - -Boxes follow standard gate semantics. - -```pbs -let a = box(10); -let b = a; // alias - -take a.set(20); -let x = unbox(b); // x == 20 -``` - -Assignment copies the gate, not the boxed value. - ---- - -## 25.6 Mutating a Box - -A boxed value may be mutated via methods on `Box`. - -```pbs -take b.set(newValue); -``` - -Reading without mutation uses `unbox`. - -Direct access to internal storage is not exposed. - ---- - -## 25.7 Relationship to SAFE and HIP Worlds - -* SAFE world: values without identity (stack) -* HIP world: values with identity (heap) - -`box` is the explicit transition from SAFE to HIP. -`unbox` is the explicit transition from HIP to SAFE. - ---- - -## 25.8 Informative: Possible Lowering Using `storage struct` - -This section is **informative**, not normative. - -A `Box` can be implemented internally using a storage struct: - -```pbs -declare storage struct _BoxImpl(value: T) -{ - fn get(self: this): T { self.value } - fn set(self: mut this, v: T): void { self.value = v } -} -``` - -With lowering rules: - -* `box(x)` → `alloc _BoxImpl` + store copy of `x` -* `unbox(b)` → `borrow b as bb { bb.get() }` - -This implementation detail is hidden from the user. - ---- - -## 25.9 Didactic Intent - -The Box abstraction exists to teach: - -* How values acquire identity -* Why heap allocation must be explicit -* The boundary between SAFE and HIP worlds -* Controlled sharing and mutation of values - -Boxing is explicit by design. - ---- - -## 25.10 Summary - -* `Box` is a built-in gate-backed type -* `box` and `unbox` are built-in operations -* Boxing copies a SAFE value into the heap -* Unboxing copies the value back to the stack -* `Box` participates fully in aliasing and RC - -Box and unbox complete the bridge between value semantics and identity semantics in PBS. \ No newline at end of file diff --git a/docs/specs/pbs/Prometeu VM Memory model.md b/docs/specs/pbs/Prometeu VM Memory model.md deleted file mode 100644 index 5fdf5a9d..00000000 --- a/docs/specs/pbs/Prometeu VM Memory model.md +++ /dev/null @@ -1,359 +0,0 @@ -# Prometeu VM Memory Model v0 - -> **Status:** v0 (normative, implementer-facing) -> -> **Purpose:** define the runtime memory model required to execute PBS programs with stable bytecode. -> -> This specification describes the four memory regions and their interactions: -> -> 1. **Constant Pool** (read-only) -> 2. **Stack** (SAFE) -> 3. **Heap** (HIP storage bytes/slots) -> 4. **Gate Pool** (HIP handles, RC, metadata) - ---- - -## 1. Design Goals - -1. **Bytecode stability**: instruction meanings and data formats must remain stable across versions. -2. **Deterministic behavior**: no tracing GC; reclamation is defined by reference counts and safe points. -3. **Explicit costs**: HIP allocation and aliasing are explicit via gates. -4. **PBS alignment**: SAFE vs HIP semantics match PBS model. - ---- - -## 2. Memory Regions Overview - -### 2.1 Constant Pool (RO) - -A program-wide immutable pool containing: - -* integers, floats, bounded ints -* strings -* (optional in future) constant composite literals - -Properties: - -* read-only during execution -* indexed by `ConstId` -* VM bytecode uses `PUSH_CONST(ConstId)` - ---- - -### 2.2 Stack (SAFE) - -The **stack** contains: - -* local variables (by slot) -* operand stack values for instruction evaluation - -SAFE properties: - -* values are copied by value -* no aliasing across variables unless the value is a gate handle -* stack values are reclaimed automatically when frames unwind - ---- - -### 2.3 Heap (HIP storage) - -The **heap** is a contiguous array of machine slots (e.g., `Value` slots), used only as **storage backing** for HIP objects. - -Heap properties: - -* heap cells are not directly addressable by bytecode -* heap is accessed only via **Gate Pool resolution** - -The heap may implement: - -* bump allocation (v0) -* free list (optional) -* compaction is **not** required in v0 - ---- - -### 2.4 Gate Pool (HIP handles) - -The **gate pool** is the authoritative table mapping a small integer handle (`GateId`) to a storage object. - -Gate Pool entry (conceptual): - -```text -GateEntry { - alive: bool, - base: HeapIndex, - slots: u32, - - strong_rc: u32, - weak_rc: u32, // optional in v0; may be reserved - - type_id: TypeId, // required for layout + debug - flags: GateFlags, -} -``` - -Properties: - -* `GateId` is stable during the lifetime of an entry -* `GateId` values may be reused only after an entry is fully reclaimed (v0 may choose to never reuse) -* any invalid `GateId` access is a **runtime trap** (deterministic) - ---- - -## 3. SAFE vs HIP - -### 3.1 SAFE - -SAFE is stack-only execution: - -* primitives -* structs / arrays as **value copies** -* temporaries - -SAFE values are always reclaimed by frame unwinding. - -### 3.2 HIP - -HIP is heap-backed storage: - -* storage objects allocated with `alloc` -* accessed through **gates** -* aliasing occurs by copying a gate handle - -HIP values are reclaimed by **reference counting**. - ---- - -## 4. Value Representation - -### 4.1 Stack Values - -A VM `Value` type must minimally support: - -* `Int(i64)` -* `Float(f64)` -* `Bounded(u32)` -* `Bool(bool)` -* `String(ConstId)` or `StringRef(ConstId)` (strings live in const pool) -* `Gate(GateId)` ← **this is the only HIP pointer form in v0** -* `Unit` - -**Rule:** any former `Ref` pointer type must be reinterpreted as `GateId`. - ---- - -## 5. Allocation (`alloc`) and Gate Creation - -### 5.1 Concept - -PBS `alloc` creates: - -1. heap backing storage (N slots) -2. a gate pool entry -3. returns a gate handle onto the stack - -### 5.2 Required inputs - -Allocation must be shape-explicit: - -* `TypeId` describing the allocated storage type -* `slots` describing the storage size - -### 5.3 Runtime steps (normative) - -On `ALLOC(type_id, slots)`: - -1. allocate `slots` contiguous heap cells -2. create gate entry: - - * `base = heap_index` - * `slots = slots` - * `strong_rc = 1` - * `type_id = type_id` -3. push `Gate(gate_id)` to stack - -### 5.4 Example - -PBS: - -```pbs -let v: Box = box(Vector.ZERO); -``` - -Lowering conceptually: - -* compute value `Vector.ZERO` (SAFE) -* `ALLOC(TypeId(Vector), slots=2)` → returns `Gate(g0)` -* store the two fields into heap via `STORE_GATE_FIELD` - ---- - -## 6. Gate Access (Read/Write) - -### 6.1 Access Principle - -Heap is never accessed directly. -All reads/writes go through: - -1. gate validation -2. gate → (base, slots) -3. bounds check -4. heap read/write - -### 6.2 Read / Peek - -`peek` copies from HIP storage to SAFE value. - -* no RC changes -* no aliasing is created - -### 6.3 Borrow (read-only view) - -Borrow provides temporary read-only access. - -* runtime may enforce with a borrow stack (debug) -* v0 may treat borrow as a checked read scope - -### 6.4 Mutate (mutable view) - -Mutate provides temporary mutable access. - -* v0 may treat mutate as: - - * read into scratch (SAFE) - * write back on `EndMutate` - -Or (preferred later): - -* direct heap writes within a guarded scope - ---- - -## 7. Reference Counting (RC) - -### 7.1 Strong RC - -Strong RC counts how many **live gate handles** exist. - -A `GateId` is considered live if it exists in: - -* a stack slot -* a global slot -* a heap storage cell (HIP) (future refinement) - -### 7.2 RC operations - -When copying a gate handle into a new location: - -* increment `strong_rc` - -When a gate handle is removed/overwritten: - -* decrement `strong_rc` - -**Rule:** RC updates are required for any VM instruction that: - -* assigns locals/globals -* stores into heap cells -* pops stack values - -### 7.3 Release and Reclamation - -When `strong_rc` reaches 0: - -* gate entry becomes **eligible for reclamation** -* actual reclamation occurs at a **safe point** - -Safe points (v0): - -* end of frame -* explicit `FRAME_SYNC` (if present) - -Reclamation: - -1. mark gate entry `alive = false` -2. optionally add heap region to a free list -3. gate id may be recycled (optional) - -**No tracing GC** is performed. - ---- - -## 8. Weak Gates (Reserved / Optional) - -v0 may reserve the field `weak_rc` but does not require full weak semantics. - -If implemented: - -* weak handles do not keep storage alive -* upgrading weak → strong requires a runtime check - ---- - -## 9. Runtime Traps (Deterministic) - -The VM must trap deterministically on: - -* invalid `GateId` -* accessing a dead gate -* out-of-bounds offset -* type mismatch in a typed store/load (if enforced) - -Traps must include: - -* opcode -* span (if debug info present) -* message - ---- - -## 10. Examples - -### 10.1 Aliasing via gates - -```pbs -let a: Box = box(Vector.ZERO); -let b: Box = a; // copy handle, RC++ - -mutate b { - it.x += 10; -} - -let v: Vector = unbox(a); // observes mutation -``` - -Explanation: - -* `a` and `b` are `GateId` copies -* mutation writes to the same heap storage -* `unbox(a)` peeks/copies storage into SAFE value - -### 10.2 No HIP for strings - -```pbs -let s: string = "hello"; -``` - -* string literal lives in constant pool -* `s` is a SAFE value referencing `ConstId` - ---- - -## 11. Conformance Checklist - -A VM is conformant with this spec if: - -* it implements the four memory regions -* `GateId` is the only HIP pointer form -* `ALLOC(type_id, slots)` returns `GateId` -* heap access is only via gate resolution -* RC increments/decrements occur on gate copies/drops -* reclamation happens only at safe points - ---- - -## Appendix A — Implementation Notes (Non-normative) - -* Start with bump-alloc heap + never-reuse GateIds (simplest v0) -* Add free list later -* Add borrow/mutate enforcement later as debug-only checks diff --git a/scripts/run-local.sh b/scripts/run-local.sh new file mode 100755 index 00000000..d924be47 --- /dev/null +++ b/scripts/run-local.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -e + +cargo run -p pbxgen-stress +cargo run -p prometeu-host-desktop-winit --release -- --run test-cartridges/stress-console \ No newline at end of file