add PBS docs

This commit is contained in:
bQUARKz 2026-01-27 10:42:49 +00:00
parent 62e68bf7ff
commit c795eeb3dc
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
24 changed files with 6386 additions and 649 deletions

View File

@ -6,7 +6,7 @@ This directory contains the technical documentation and specifications for the P
### 📜 [Specifications (Specs)](./specs) ### 📜 [Specifications (Specs)](./specs)
Detailed documentation on system architecture, cartridge format, VM instruction set, and more. Detailed documentation on system architecture, cartridge format, VM instruction set, and more.
- [Topic Index](./specs/topics/table-of-contents.md) - [Topic Index](specs/hardware/topics/table-of-contents.md)
### 🐞 [Debugger](./debugger) ### 🐞 [Debugger](./debugger)
Documentation on debugging tools and how to integrate new tools into the ecosystem. Documentation on debugging tools and how to integrate new tools into the ecosystem.

View File

@ -0,0 +1,359 @@
# 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.

View File

@ -0,0 +1,357 @@
# PBS Compiler — Junie PR Plan
> **Purpose:** this document defines a sequence of small, focused Pull Requests to be implemented by *Junie*, one at a time.
>
> **Audience:** compiler implementer (AI or human).
>
> **Scope:** PBS-first compiler architecture. TS and Lua frontends are assumed **removed**.
>
> **Hard rules:**
>
> * Each PR must compile and pass tests.
> * Each PR must include tests.
> * No speculative features.
> * Follow the `Prometeu Base Script (PBS) - Implementation Spec`.
---
## Global Architectural Direction (Non-negotiable)
* PBS is the **primary language**.
* Frontend is implemented **before** runtime integration.
* Architecture uses **two IR layers**:
* **Core IR** (PBS-semantic, typed, resolved)
* **VM IR** (stack-based, backend-friendly)
* VM IR remains simple and stable.
* Lowering is explicit and testable.
---
# PR-01 — ProjectConfig and Frontend Selection
### Goal
Introduce a project-level configuration that selects the frontend and entry file explicitly.
### Motivation
The compiler must not hardcode entry points or languages. PBS will be the first frontend, others may return later.
### Scope
* Add `ProjectConfig` (serde-deserializable) loaded from `prometeu.json`
* Fields (v0):
* `script_fe: "pbs"`
* `entry: "main.pbs"`
* Refactor compiler entry point to:
* load config
* select frontend by `script_fe`
* resolve entry path relative to project root
### Files Likely Touched
* `compiler/mod.rs`
* `compiler/driver.rs`
* `common/config.rs` (new)
### Tests (mandatory)
* unit test: load valid `prometeu.json`
* unit test: invalid frontend → diagnostic
* integration test: project root + entry resolution
### Notes to Junie
Do **not** add PBS parsing yet. This PR is infrastructure only.
---
# PR-02 — Core IR Skeleton (PBS-first)
### Goal
Introduce a **Core IR** layer independent from the VM IR.
### Motivation
PBS semantics must be represented before lowering to VM instructions.
### Scope
* Add new module: `ir_core`
* Define minimal structures:
* `Program`
* `Module`
* `Function`
* `Block`
* `Instr`
* `Terminator`
* IDs only (no string-based calls):
* `FunctionId`
* `ConstId`
* `TypeId`
### Constraints
* Core IR must NOT reference VM opcodes
* No lowering yet
### Tests
* construct Core IR manually in tests
* snapshot test (JSON) for deterministic shape
---
# PR-03 — Constant Pool and IDs
### Goal
Introduce a stable constant pool shared by Core IR and VM IR.
### Scope
* Add `ConstPool`:
* strings
* numbers
* Replace inline literals in VM IR with `ConstId`
* Update existing VM IR to accept `PushConst(ConstId)`
### Tests
* const pool deduplication
* deterministic ConstId assignment
* IR snapshot stability
---
# PR-04 — VM IR Cleanup (Stabilization)
### Goal
Stabilize VM IR as a **lowering target**, not a language IR.
### Scope
* Replace string-based calls with `FunctionId`
* Ensure locals are accessed via slots
* Remove or internalize `PushScope` / `PopScope`
### Tests
* golden VM IR tests
* lowering smoke test (Core IR → VM IR)
---
# PR-05 — Core IR → VM IR Lowering Pass
### Goal
Implement the lowering pass from Core IR to VM IR.
### Scope
* New module: `lowering/core_to_vm.rs`
* Lowering rules:
* Core blocks → labels
* Core calls → VM calls
* Host calls preserved
* No PBS frontend yet
### Tests
* lowering correctness
* instruction ordering
* label resolution
---
# PR-06 — PBS Frontend: Lexer
### Goal
Implement PBS lexer according to the spec.
### Scope
* Token kinds
* Keyword table
* Span tracking
### Tests
* tokenization tests
* keyword vs identifier tests
* bounded literals
---
# PR-07 — PBS Frontend: Parser (Raw AST)
### Goal
Parse PBS source into a raw AST.
### Scope
* Imports
* Top-level declarations
* Blocks
* Expressions (calls, literals, control flow)
### Tests
* valid programs
* syntax error recovery
---
# PR-08 — PBS Frontend: Symbol Collection and Resolver
### Goal
Resolve names, modules, and visibility.
### Scope
* Type namespace vs value namespace
* Visibility rules
* Import resolution
### Tests
* duplicate symbols
* invalid imports
* visibility errors
---
# PR-09 — PBS Frontend: Type Checking
### Goal
Validate PBS semantics.
### Scope
* Primitive types
* Structs
* `optional<T>` and `result<T, E>`
* Mutability rules
* Return path validation
### Tests
* type mismatch
* mutability violations
* implicit `none` behavior
---
# PR-10 — PBS Frontend: Semantic Lowering to Core IR
### Goal
Lower typed PBS AST into Core IR.
### Scope
* ID-based calls
* ConstPool usage
* Control flow lowering
* SAFE vs HIP effects represented explicitly
### Tests
* PBS → Core IR snapshots
* semantic correctness
---
# PR-11 — Host-bound Contracts and Syscall Mapping
### Goal
Connect PBS host-bound contracts to runtime syscalls (without executing them).
### Scope
* Contract registry
* Mapping: contract.method → syscall id
* Core IR host call nodes
### Tests
* invalid contract calls
* correct syscall mapping
---
# PR-12 — Diagnostics Canonicalization
### Goal
Standardize diagnostics output.
### Scope
* Error codes (`E_*`, `W_*`)
* Stable messages
* Span accuracy
### Tests
* golden diagnostics
---
# PR-13 — Backend Integration (VM IR → Bytecode)
### Goal
Reconnect the pipeline to the Prometeu runtime backend.
### Scope
* VM IR → bytecode emission
* No PBS semantics here
### Tests
* bytecode emission smoke test
---
# PR-14 — End-to-End PBS Compile Test
### Goal
Prove the full pipeline works.
### Scope
* Sample PBS project
* Compile → bytecode
* Diagnostics only (no execution)
### Tests
* golden bytecode snapshot
---
## Final Note to Junie
Do **not** skip PRs.
Do **not** merge multiple PRs together.
If the spec is unclear, create a failing test and document the ambiguity.
This plan is the authoritative roadmap for PBS frontend implementation.

View File

@ -0,0 +1,446 @@
# 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 NonGoals
### 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 NonGoals
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
Runtimeready 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 `"`
* UTF8 encoded
* immutable
---
### 2.4 Comments
* line comment: `// until end of line`
* block comments are **not supported** in v0
---
## 3. Grammar (EBNFstyle)
> 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 ::= 'fn' Identifier ParamList ReturnType? ElseFallback? Block
```
Toplevel `fn` are always fileprivate.
---
### 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 **modulelevel 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 compiletime 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. fileprivate declarations
3. module symbols
4. imported symbols
---
### 6.2 Visibility Rules (Normative)
* fileprivate: 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<T>`
* `result<T, E>`
* gatebacked types (opaque at frontend level)
---
### 7.2 Mutability Rules
* mutability belongs to bindings, not types
* `mut` is part of binding metadata
* mutability violations are compiletime errors
---
### 7.3 Function Checking
Rules:
* all paths must return a value unless `else` fallback exists
* `optional<T>` may implicitly return `none`
* `result<T,E>` 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<T>`
Lowered AST must contain **no syntactic sugar**.
---
## 9. Diagnostics Model
Diagnostics are firstclass 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:
* hostbound 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 PBSv0conformant 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 (NonNormative)
Future versions may add:
* pattern matching
* richer type inference
* macros
No v0 frontend is required to support these.
---
## End of Spec

View File

@ -0,0 +1,341 @@
# Prometeu Base Script (PBS)
> **A didactic scripting language for game development with explicit cost, explicit memory, and predictable runtime.**
---
## 0. What PBS Is (and What It Is Not)
PBS (Prometeu Base Script) is a **small, explicit scripting language designed for game engines and realtime runtimes**.
Its core goals are:
* **Didactic clarity** — the language teaches how memory, data, and APIs really work.
* **Gamefriendly execution** — predictable runtime, no hidden allocation, no tracing GC.
* **Explicit cost model** — you always know *when* you allocate, *where* data lives, and *who* can mutate it.
PBS is **not**:
* a generalpurpose application language
* a productivity scripting language like Python or Lua
* a language that hides runtime cost
PBS is intentionally opinionated. It is built for developers who want **control, predictability, and understanding**, especially in **game and engine contexts**.
---
## 1. The Core Philosophy
PBS is built around one rule:
> **If you do not allocate, you are safe. If you allocate, you are responsible.**
From this rule, everything else follows.
### 1.1 Two Worlds
PBS has **two explicit memory worlds**:
| World | Purpose | Properties |
| ----- | -------------- | ---------------------------------------------- |
| SAFE | Stack / values | No aliasing, no leaks, no shared mutation |
| HIP | Storage / heap | Explicit aliasing, shared mutation, refcounted |
You never cross between these worlds implicitly.
---
## 2. First Contact: SAFEOnly PBS
A PBS program that never allocates lives entirely in the SAFE world.
SAFE code:
* uses value semantics
* copies data on assignment (conceptually)
* cannot leak memory
* cannot accidentally share mutable state
This makes SAFE PBS ideal for:
* gameplay logic
* math and simulation
* AI logic
* scripting without fear
Example:
```pbs
let a = Vector(1, 2);
let b = a; // conceptual copy
b.scale(2);
// a is unchanged
```
No pointers. No references. No surprises.
---
## 3. Values, Not Objects
PBS does not have objects with identity by default.
### 3.1 Value Types
All basic types are **values**:
* numbers
* bool
* string (immutable)
* tuples
* userdefined `struct`
A value:
* has no identity
* has no lifetime beyond its scope
* is safe to copy
This matches how most gameplay data *should* behave.
---
## 4. Structs (Data First)
Structs are **pure data models**.
```pbs
declare struct Vector(x: float, y: float)
{
pub fn len(self: this): float { ... }
pub fn scale(self: mut this, s: float): void { ... }
}
```
Rules:
* fields are private
* mutation is explicit (`mut this`)
* no inheritance
* no identity
Structs are designed to be:
* cachefriendly
* predictable
* easy to reason about
---
## 5. Mutability Is a Property of Bindings
In PBS, **types are never mutable**. Bindings are.
```pbs
let v = Vector.ZERO;
v.scale(); // ERROR
let w = mut Vector.ZERO;
w.scale(); // OK
```
This single rule eliminates entire classes of bugs.
---
## 6. When You Need Power: Allocation (HIP World)
Allocation is **explicit**.
```pbs
let enemies = alloc list<Enemy>();
```
Once you allocate:
* data lives in Storage (heap)
* access happens through **gates** (handles)
* aliasing is real and visible
This is intentional and explicit.
---
## 7. Gates (Handles, Not Pointers)
A gate is a small value that refers to heap storage.
Properties:
* cheap to copy
* may alias shared data
* managed by reference counting
### 7.1 Strong vs Weak Gates
* **Strong gate** — keeps data alive
* **Weak gate** — observes without ownership
```pbs
let a = alloc Node;
let w: weak<Node> = a as weak;
let maybeA = w as strong;
```
This model mirrors real engine constraints without hiding them.
---
## 8. Controlled Access to Storage
Heap data is never accessed directly.
You must choose *how*:
* `peek` — copy to SAFE
* `borrow` — temporary readonly access
* `mutate` — temporary mutable access
```pbs
mutate enemies as e
{
e.push(newEnemy);
}
```
This keeps mutation visible and scoped.
---
## 9. Errors Are Values
PBS has no exceptions.
### 9.1 `optional<T>`
Used when absence is normal.
```pbs
let x = maybeValue else 0;
```
### 9.2 `result<T, E>`
Used when failure matters.
```pbs
let v = loadTexture(path)?;
```
Error propagation is explicit and typed.
---
## 10. Control Flow Without Surprises
PBS favors explicit flow:
* `if` — control only
* `when` — expression
* `for` — bounded loops only
```pbs
for i in [0b..count] {
update(i);
}
```
No unbounded iteration by accident.
---
## 11. Services: Explicit API Boundaries
A `service` is how behavior crosses module boundaries.
```pbs
pub service Audio
{
fn play(sound: Sound): void;
}
```
Services are:
* explicit
* statically checked
* singletonlike
They map naturally to engine subsystems.
---
## 12. Contracts: Static Guarantees
Contracts define **what exists**, not how.
```pbs
pub declare contract Gfx host
{
fn drawText(x: int, y: int, msg: string): void;
}
```
Contracts:
* have no runtime cost
* are validated at compile time
* define engine ↔ script boundaries
---
## 13. Modules (Simple and Predictable)
* one directory = one module
* only `pub` symbols cross modules
* no side effects on import
This keeps build and runtime deterministic.
---
## 14. Why PBS Works for Games
PBS is designed around real engine needs:
* framebased execution
* explicit allocation
* deterministic cleanup
* predictable performance
It teaches developers **why engines are written the way they are**, instead of hiding reality.
---
## 15. Who PBS Is For
PBS is for:
* game developers who want control
* engine developers
* students learning systems concepts
* educators teaching memory and runtime models
PBS is *not* for:
* rapid prototyping without constraints
* general app scripting
* hiding complexity
---
## 16. Design Promise
PBS makes one promise:
> **Nothing happens behind your back.**
If you understand PBS, you understand your runtime.
That is the product.

File diff suppressed because it is too large Load Diff

View File

@ -1,648 +0,0 @@
# Prometeu Base Script (PBS)
**Status:** v0 (frontend-freeze)
**Goal:** stack-only language, no GC, explicit semantics, predictable runtime
---
## 0. Philosophy (anchor section)
PBS is designed as:
* **Value-first** (no implicit references, no aliasing of mutable state)
* **Stack-only** (no heap, no GC)
* **Explicit mutability** (bindings, not types)
* **Didactic** (rules visible in syntax)
* **Runtime-cheap** (frontend-heavy, backend-simple)
Everything in the language flows from these constraints.
---
## 1. Project, Files & Modules
### 1.1 Files
* Extension: `.pbs`
* Project has a **root directory**
* `@path` is resolved relative to project root
### 1.2 Module Model
* **One directory = one module**.
* A module boundary is **real**: parent/child/sibling directories do **not** get automatic visibility.
* Code in `@project:modA` does **not** automatically see declarations in `@project:modA/sub`.
* Code in `@project:modA/sub` does **not** automatically see declarations in `@project:modA`.
* Sibling modules (e.g., `@project:modA` and `@project:modB`) are completely isolated unless you `import`.
* **Cross-module access always requires `import`**, and only `pub` symbols may cross module boundaries.
### 1.3 Automatic Module Index
* No mandatory barrel file
* Compiler builds an index from:
* all `pub` symbols
* in `.pbs` files **directly inside the directory**
* Subdirectories are excluded
### 1.4 Visibility Modifiers
PBS uses explicit visibility modifiers to control symbol exposure. Visibility is the **only** mechanism that decides whether another file/module can see a symbol.
* **default (no modifier)** — **file-private**
* Visible **only** within the declaring `.pbs` file.
* Never visible from any other file, 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 for other files in the same module to refer to it.
* **`pub`** — **public API**
* Exported as part of the modules public surface.
* May be imported by other modules.
* Within the same module, `pub` behaves like `mod` (visible across files without import).
Important:
* A symbol is either **file-private**, **module-visible**, or **public**. There is no other visibility level.
* Visibility is checked independently in the **type** and **value** namespaces.
Visibility applies uniformly to:
* `declare` type declarations
* `service`
* value-level symbols
---
## 2. Namespaces & Visibility
### 2.1 Global Namespaces
Existem **dois namespaces globais**:
**Type namespace**
Tipos são introduzidos **exclusivamente** por declarações `declare`.
O que vem após `declare` define a categoria do tipo.
Formas válidas:
```pbs
declare struct Name { ... }
declare error Name { ... }
declare contract Name { ... }
```
**Value namespace**
Introduzido por declarações executáveis:
* `fn`
* `service`
* `let`
---
## 3. Top-level Declarations
A `.pbs` file may contain:
* `import`
* **type declarations** via `declare`:
* `declare struct`
* `declare error`
* `declare contract`
* `service`
* `fn` (always file-private)
Order is free.
---
## 4. Resolver Rules
This section defines how the compiler resolves modules, imports, and symbol names into concrete declarations.
### 4.1 Units of compilation
* The compilation unit is a **project** (root + all referenced modules/files).
* The root of the project is associated with the src/main/modules
* A **module** is exactly one directory.
* A **file unit** is one `.pbs` file.
Examples, lets say we have a project called: `project`:
```
project
|- prometeu.json
|- prometeu-cache
| |-cache.lock
|- src
| |- main
| | |- modules
| | | |- module-1
| | | | |- file-1.pbs
| | | | |- file-2.pbs
| | | | | |- fearture A
| | | | |- file-3.pbs
| | | | |- module-2
| | | | | |- file-1.pbs
| | | | | |- file-2.pbs
| | | | | | |- fearture B
| | | | | |- file-3.pbs
| |- resources
| | |- resource-1.txt
| |- test
| | |- modules
| | | |- module-1
| | | | |- file-1.pbs
| | | | |- file-2.pbs
| | | | |- file-3.pbs
| | |- resources
```
For a project `project/src/main/` is the root, and it will be presented in the import as (when looking for feature A and B):
```
import { featureA } from "@project:module-1"
import { featureB } from "@project:module-1/module-2"
```
The project name is a configuration inside `prometeu.json`. A project always defines a unit compilation. Other compilation
units could be linked to the project as a dependency. When building a project, all dependencies will be copied into a
directory called `prometeu-cache` (read-only) inside the project root.
A `project.json` could be look like.
```json
{
"name": "project",
"dependencies":{
"project-A": {
"version": "1.0.0",
"source": "path:../project-A"
},
"project-B": {
"version": "1.5.3",
"source": "git:https://github.com/project-B.git"
}
}
}
```
When compiling, all deps and current project will be flattened, and Project names should be unique otherwise it will
cause conflicts and compilation errors on the current project.
### 4.2 Namespaces
Resolution is performed in **two independent namespaces**:
* **Type namespace**: declarations introduced by `declare` (`struct`, `error`, `contract`).
* **Value namespace**: declarations introduced by `service`, `fn`, and `let`.
A name may exist in both namespaces, but this is **discouraged**; toolchains may report this as an error or warning.
### 4.3 Visibility gates
For any candidate symbol `S`, visibility is checked explicitly:
* **file-private (default)** — visible only within the same file
* **`mod`** — visible to any file in the same module
* **`pub`** — visible to other modules via `import`
No implicit visibility exists beyond these rules.
### 4.4 Module index construction
For each module directory `M`:
* The compiler scans all `.pbs` files **directly inside** `M`.
* The modules **public index** contains **only** `pub` symbols from those files.
* Subdirectories never contribute to the parent modules index.
Duplicate rules:
* Any `structs, services, contracts, errors` duplicate `pub` names in the same namespace cause a compile error:
* `duplicate public symbol in module`.
### 4.5 Import resolution
Imports are the **only** mechanism for cross-module access.
```pbs
import { X, Y as Z } from "@project:module";
```
Rules:
* Only `pub` symbols may be imported.
* Import-by-module resolves against the module public index.
* Import-by-file is not allowed at all.
### 4.6 Local resolution order
Within a file, name lookup follows this order **within each namespace**:
1. Local bindings (parameters, local `let`, innermost scope first) but with warnings
- when `import { X as Y } from "@project:module"` and `let Y: int = 0;` `let Y` wins but could generate a warning
2. File-level declarations (file-private, `mod`, and `pub` in the same file)
3. Imported bindings
4. When `import { X } from "@project:module"` (or when using `as`) and we already have a `mod X ...` for this module, compilation error.
Shadowing rules:
* Local `let` shadowing is allowed.
* Shadowing of `fn` and `service` names is not allowed.
### 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.
**Across modules:**
* only `pub` symbols are visible
* access always requires an explicit `import`.
### 4.8 Cycles
* Import cycles are allowed only if name resolution can be completed.
* PBS has no top-level execution, so cycles are resolved purely at the symbol level.
* Any cycle that prevents construction of a complete symbol table is a compile error.
### 4.9 Contracts and services
* `declare contract C` introduces `C` in the **type namespace**.
* `service S: C` resolves `C` as a type and validates that `S` implements all declared signatures.
* they can be implemented by the Prometeu runtime / host environment.
---
## 4. Services
A `service` represents an **explicit API boundary** between PBS code and either:
* other PBS modules, or
* it is definitely a singleton by design
* can implement a contract, or not
- when implementing a contract, signatures must match exactly
* every `fn` inside in a `service` will follow the same visibility as the service it belongs (`pub` or `mod`).
- once being able to see the service, it can see all its methods.
A service is never an implementation detail.
### 4.1 Declaration and visibility
A service must always be declared with an explicit visibility modifier:
* `pub service` — part of the modules public API
* `mod service` — internal API, visible only inside the module
There is **no such thing** as a private service.
### 4.2 Service methods
* All methods declared inside a service are **public within that service**.
* There are no private or helper methods inside a service.
* A service method may call:
* top-level `fn` in the same file
* other services it can legally see
### 4.3 Implementation rule
If logic is not conceptually part of the API, it **must not** live inside a service.
Such logic must be implemented as file-private `fn`.
---
## 6. Types
This section describes all value types available in PBS and how they are declared.
### 6.1 Primitive types
PBS provides a small, fixed set of primitive types:
* `void`
* `int` (32-bit signed)
* `long` (64-bit signed)
* `float` (32-bit IEEE-754)
* `double` (64-bit IEEE-754)
* `bool`
* `char` (Unicode code point, 32-bit)
* `string`
Important notes:
* `string` values are **immutable**.
* Strings exist only as literals stored in a constant pool.
* There is no dynamic string allocation at runtime.
### 6.2 Struct types
User-defined value types are declared using `declare struct`.
A struct:
* is a **pure value type**
* has no identity
* follow the rules of **mutability and borrowing" described below
* has no inheritance or subtyping
* it is only copied when mutated, otherwise it is **viewed as immutable** and passed as ref by default.
All behavior related to the type must be defined inside the struct body.
### 6.3 `this`
Inside a struct body, the special type `this` refers to the struct itself.
Rules:
* `this` may only appear inside a `declare struct` body.
* Outside a struct, `this` is illegal.
---
## 7. Structs & Constructors
```pbs
declare struct Vector(x: float, y: float)
[
(): (0,0) as default { }
(a: float): (a,a) as square { }
(): (1,1) as normalize { }
]
[[
ZERO: default()
ONE: square(1.0)
]]
{
pub fn len(self: this): float { ... }
pub fn scale(self: mut this): void { ... }
pub fn normalize(self: mut this): void { ... }
}
```
Key rules:
* fields private by default
* constructor aliases live in type namespace but can never be imported, in the exemplo only `Vector` can be imported.
* no static methods
* static block values are compile-time constants
There will never be a conflict between an alias and a method of the same name.
A constructor will be called as `let v = Vector.normalize()` and methods will be called as `v.normalize()`.
In fact, they call different things, one is static, and the other is an instance method (there are no static methods on structs).
The static block `[[ ]]` should be static (and generate static on constant pool) and can use only constructors and static values, as helpers.
---
## 8. Mutability & Borrow Model (Core Rule)
This is a foundational rule of PBS.
Mutability is **never** a property of a type.
Mutability belongs **only** to bindings (variables and parameters).
### 8.1 Immutable by default
All bindings are immutable unless explicitly marked `mut`.
```pbs
let v = Vector.ZERO; // immutable binding
let mut w = Vector.ZERO; // mutable binding (local copy)
```
Some examples of mutable bindings:
```pbs
let v = Vector.ZERO;
v.scale(); // ERROR: cannot mutate a read-only binding
let mut w = v;
w.scale(); // OK: when mut it copies the value, and could be changed
fn f(v: Vector): Vector
{
let mut w = v;
return w; // when it happens compiler will generate a copy otherwise w will be lost in the stack
}
fn g(v: Vector): void
{
let vro = f(Vector.ZERO); // OK! and it will be a readonly copy
let vrw = mut f(Vector.ZERO); // OK! and it will be a mutable copy of the copy
}
```
### 8.2 Consequences of binding-based mutability
* Mutating a value never affects any other binding.
* Static values can never be mutated.
* There is no aliasing of mutable state.
### 8.3 Parameters and copying
Function parameters follow these rules:
* `T` parameters are passed as **read-only borrows**.
* `mut T` parameters create a **local mutable copy**.
The caller is never affected by mutations performed inside the function.
### 8.4 Method receivers
Methods on structs declare mutability explicitly on the receiver:
```pbs
fn len(self: this): float // read-only
fn scale(self: mut this): void // mutating
```
A mutating method:
* requires a **mutable lvalue** at the call site
* cannot be called on temporaries or static values
---
## 9. Expressions & Control Flow
* `if / else`
* `for` with ranges
* `when` expression
* `return`
`when`:
```pbs
let x = when a > b then 1 else 2;
```
---
## 10. Return Fallback (`else`)
```pbs
fn v(): Vector else Vector.ZERO
{
if cond return Vector.ONE;
}
```
Used to guarantee total functions without boilerplate.
---
## 11. Numeric Rules
* implicit widen: `int → long → float → double`
* no implicit narrowing
* cast syntax: `expr as Type`
---
## 12. `bounded`
`bounded` is a dedicated scalar type for **indices and sizes**.
### 12.1 Purpose
The goal of `bounded` is to make indexing and counting:
* explicit
* safe
* visually distinct from general arithmetic
### 12.2 Representation
* Internally represented as an unsigned 16-bit integer (`u16`).
* Valid range: `0 .. 65535`.
### 12.3 Usage rules
`bounded` is used for:
* array indices
* array lengths
* default `for` loop counters
Operations are intentionally limited:
* allowed: comparison, checked `+` and `-`
* disallowed: multiplication, division, bitwise operations
---
## 13. Fixed Arrays
```pbs
Array<T>[Nb]
```
* `Nb` is compile-time `bounded` literal
* immutable bindings = view
* mutable bindings = owned storage
Supports:
* indexing (`bounded` only)
* slicing `[a..b[` (half-open)
Return escape model prevents dangling views.
---
## 14. `optional<T>`
`optional<T>` represents the explicit presence or absence of a value.
### 14.1 Design intent
* Absence is a **normal, explicit state**.
* No traps or implicit unwraps exist.
* No heap allocation is involved.
### 14.2 Extraction rule
The **only** supported way to extract a value from an optional is with `else`:
```pbs
let x: int = opt else 0;
```
This makes fallback behavior explicit at the call site.
---
## 15. `result<T,E>`
* typed errors
* no exceptions
* no implicit extraction
```pbs
let v = f()?;
```
### Error handling with `handle`
`handle` is the *only* construct that allows value extraction from a `result` while mapping errors.
```pbs
let x: int = handle r
{
ErrorA.not_found => ErrorB.io,
ErrorA.denied => ErrorB.permission,
_ => ErrorB.unknown,
};
```
Semantics:
* If `r` is `ok(v)`, the expression evaluates to `v`
* If `r` is `err(e)`, the first matching arm is selected and an early `return err(mapped)` is executed
Rules:
* Arms must be exhaustive (explicit or via `_`)
* Right-hand side must be a label of the destination error type
* No traps are permitted
* **The only possible way to extract a value is by handling the error**
---
## 16. What is *deliberately missing* (v0)
* heap allocation
* GC
* references
* inheritance
* traits / interfaces
* async
* closures
* generics (beyond containers)
---
## 17. Roadmap (v1+ ideas)
This is a non-binding list of future directions. None of these are part of v0.
* generics for `struct`
* slice views with runtime bounds
* iterator sugar
* `defer`
* pattern matching on `result`
* ABI boundary spec (VM ↔ PBS)
* contract versioning
---
**This document is now the canonical organized v0 spec.****