added first steps on pbs pipeline

This commit is contained in:
bQUARKz 2026-02-26 19:10:29 +00:00
parent 176a3f7587
commit 801109b993
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
41 changed files with 13804 additions and 6 deletions

View File

@ -0,0 +1,94 @@
### Prometeu Bytecode — Core ISA (Minimal, BytecodeOnly)
This document defines the minimal, stable Core ISA surface for the Prometeu Virtual Machine at the bytecode level. It specifies instruction encoding, the stack evaluation model, and the instruction set currently available. Higherlevel constructs (closures, coroutines) are intentionally out of scope at this stage.
#### Encoding Rules
- Endianness: Littleendian.
- Instruction layout: `[opcode: u16][immediate: spec.imm_bytes]`.
- Opcodes are defined in `prometeu_bytecode::isa::core::CoreOpCode`.
- Immediate sizes and stack effects are defined by `CoreOpCode::spec()` returning `CoreOpcodeSpec`.
- All jump immediates are absolute u32 byte offsets from the start of the current function.
#### Stack Machine Model
- The VM is stackbased. Unless noted, operands are taken from the top of the operand stack and results are pushed back.
- Types at the bytecode level are represented by the `Value` enum; the VM may perform numeric promotion where appropriate (e.g., `Int32 + Float -> Float`).
- Stack underflow is a trap (TRAP_STACK_UNDERFLOW).
- Some operations may trap for other reasons (e.g., division by zero, invalid indices, type mismatches).
#### Instruction Set (Core)
- Execution control:
- `NOP` — no effect.
- `HALT` — terminates execution (block terminator).
- `JMP u32` — unconditional absolute jump (block terminator).
- `JMP_IF_FALSE u32` — pops `[bool]`, jumps if false.
- `JMP_IF_TRUE u32` — pops `[bool]`, jumps if true.
- `TRAP` — software trap/breakpoint (block terminator).
- Stack manipulation:
- `PUSH_CONST u32` — load constant by index → _pushes `[value]`.
- `PUSH_I64 i64`, `PUSH_F64 f64`, `PUSH_BOOL u8`, `PUSH_I32 i32`, `PUSH_BOUNDED u32(<=0xFFFF)` — push literals.
- `POP` — pops 1.
- `POP_N u32` — pops N.
- `DUP``[x] -> [x, x]`.
- `SWAP``[a, b] -> [b, a]`.
- Arithmetic:
- `ADD`, `SUB`, `MUL`, `DIV`, `MOD` — binary numeric ops.
- `NEG` — unary numeric negation.
- `BOUND_TO_INT``[bounded] -> [int64]`.
- `INT_TO_BOUND_CHECKED``[int] -> [bounded]` (traps on overflow 0..65535).
- Comparison and logic:
- `EQ`, `NEQ`, `LT`, `LTE`, `GT`, `GTE` — comparisons → `[bool]`.
- `AND`, `OR`, `NOT` — boolean logic.
- `BIT_AND`, `BIT_OR`, `BIT_XOR`, `SHL`, `SHR` — integer bit operations.
- Variables:
- `GET_GLOBAL u32`, `SET_GLOBAL u32` — access global slots.
- `GET_LOCAL u32`, `SET_LOCAL u32` — access local slots (current frame).
- Functions and scopes:
- `CALL u32` — call by function index; argument/result arity per function metadata.
- `RET` — return from current function (block terminator).
- `PUSH_SCOPE`, `POP_SCOPE` — begin/end lexical scope.
- System/Timing:
- `SYSCALL u32` — platform call; arity/types are verified by the VM/firmware layer.
- `FRAME_SYNC` — yield until the next frame boundary (e.g., vblank); explicit safepoint.
For exact immediates and stack effects, see `CoreOpCode::spec()` which is the single source of truth used by the decoder, disassembler, and (later) verifier.
#### Canonical Decoder Contract
- The canonical decoder is `prometeu_bytecode::decode_next(pc, bytes)`.
- It uses the Core ISA spec to determine immediate size and the canonical `next_pc`.
- Unknown or legacy opcodes must produce a deterministic `UnknownOpcode` error.
#### Module Boundary
- Core ISA lives under `prometeu_bytecode::isa::core` and reexports:
- `CoreOpCode` — the opcode enum of the core profile.
- `CoreOpcodeSpec` and `CoreOpCodeSpecExt` — spec with `imm_bytes`, stack effects, and flags.
- Consumers (encoder/decoder/disasm/verifier) should import from this module to avoid depending on internal layout.
#### FRAME_SYNC — Semantics and Placement (Bytecode Level)
- Semantics:
- `FRAME_SYNC` is a zero-operand instruction and does not modify the operand stack.
- It marks a VM safepoint for GC and the cooperative scheduler. In `CoreOpcodeSpec` this is exposed as `spec.is_safepoint == true`.
- On execution, the VM may suspend the current fiber/coroutine until the next frame boundary (e.g., vsync) and/or perform GC. After resuming, execution continues at the next instruction.
- Placement rules (representable and checkable):
- `FRAME_SYNC` may appear anywhere inside a function body where normal instructions can appear. It is NOT a block terminator (`spec.is_terminator == false`).
- Instruction boundaries are canonical: encoders/emitters must only place `FRAME_SYNC` at valid instruction PCs. The verifier already enforces “jump-to-boundary” and end-exclusive `[start, end)` function ranges using the canonical layout routine.
- Entrypoints that represent a render/update loop SHOULD ensure at least one reachable `FRAME_SYNC` along every long-running path to provide deterministic safepoints for GC/scheduling. This policy is semantic and may be enforced by higher-level tooling; at the bytecode level it is representable via `spec.is_safepoint` and can be counted by static analyzers.
- Disassembly:
- Disassemblers must print the mnemonic `FRAME_SYNC` verbatim for this opcode.
- Tools MAY optionally annotate it as a safepoint in comments, e.g., `FRAME_SYNC ; safepoint`.
- Verification notes:
- The bytecode verifier treats `FRAME_SYNC` as a normal instruction with no stack effect and no control-flow targets. It is permitted before `RET`, between basic blocks, and as the last instruction of a function. Jumps targeting the function end (`pc == end`) remain valid under the end-exclusive rule.

View File

@ -0,0 +1,183 @@
PR-1.1 — Bytecode Legacy Inventory & Deletion Map
Goal
- Provide an explicit, reviewable inventory of all RC/HIP-era artifacts (opcodes, traps, types, helpers, docs, tests) and a deletion map for the ISA reset.
- No code changes in this PR — documentation only. Follow-up PRs will execute the deletions/edits.
Repository root
- runtime (this file lives at docs/bytecode/RESET_LEGACY_MAP.md)
Quick grep guide (reviewers)
- Use these commands from the project root. They avoid target/ and other generated dirs.
```
# Core RC/HIP signal words
grep -RIn --exclude-dir target --exclude-dir dist-staging --exclude-dir dist-workspace \
-e '\bHIP\b' -e '\bgate\b' -e 'GATE_' -e '\bretain\b' -e '\brelease\b' -e '\bscope\b' .
# Legacy opcodes (bytecode + VM handlers)
grep -RIn --exclude-dir target \
-e 'GateLoad' -e 'GateStore' -e 'GateBeginPeek' -e 'GateEndPeek' \
-e 'GateBeginBorrow' -e 'GateEndBorrow' -e 'GateBeginMutate' -e 'GateEndMutate' \
-e 'GateRetain' -e 'GateRelease' -e 'Alloc' \
crates/console/prometeu-bytecode crates/console/prometeu-vm
# Scope-related (inventory only; not necessarily to delete)
grep -RIn --exclude-dir target -e 'PushScope' -e 'PopScope' crates/console
# Trap codes inventory (post-reset, check for any lingering gate-specific traps)
grep -RIn --exclude-dir target \
-e '\bTRAP_\w\+' \
crates/console/prometeu-bytecode crates/console/prometeu-vm | grep -Ei 'gate|heap|hip' -n || true
# Value::Gate usages
grep -RIn --exclude-dir target -e 'Value::Gate' crates/console
# Docs that describe HIP/RC model
grep -RIn --exclude-dir target --include '*.md' -e '\bHIP\b' docs files
```
Legend for the deletion map
- Remove: file or symbol slated for deletion in follow-up PRs.
- Edit: file will survive but needs edits to remove/rename legacy parts.
- Keep: file or symbol remains under the GC stack+heap model (not part of RC/HIP removal) — listed here when helpful for context.
1) Legacy opcodes (RC/HIP)
Remove — OpCode variants (definitions and all references)
- prometeu-bytecode
- crates/console/prometeu-bytecode/src/opcode.rs
- OpCode::Alloc
- OpCode::GateLoad
- OpCode::GateStore
- OpCode::GateBeginPeek, OpCode::GateEndPeek
- OpCode::GateBeginBorrow, OpCode::GateEndBorrow
- OpCode::GateBeginMutate, OpCode::GateEndMutate
- OpCode::GateRetain, OpCode::GateRelease
- Notes: also appears in TryFrom<u16> mapping and in cycles() table inside same file.
- crates/console/prometeu-bytecode/src/opcode_spec.rs
- Spec entries for each of the above (names: GATE_LOAD, GATE_STORE, GATE_BEGIN_PEEK, ... GATE_RELEASE; ALLOC).
- crates/console/prometeu-bytecode/src/decoder.rs
- Keep file. No direct legacy handling beyond general decoding; remains after removing legacy opcodes.
- crates/console/prometeu-bytecode/src/model.rs
- Keep file. No opcode definitions here; only payload helpers like imm_u32x2 are generic. No deletion but re-check comments if they reference ALLOC semantics.
- crates/console/prometeu-bytecode/src/value.rs
- Edit: enum Value includes Gate(usize). This is HIP-only; plan to remove variant and downstream usages when ISA reset lands.
- crates/console/prometeu-bytecode/src/program_image.rs
- Edit: maps Value to ConstantPoolEntry; has a Value::Gate arm that turns into Null. Will be adjusted once Value::Gate is removed.
- prometeu-vm (execution semantics for legacy opcodes)
- crates/console/prometeu-vm/src/virtual_machine.rs
- Match arms for: Alloc, GateLoad/Store, GateBegin*/End* (Peek/Borrow/Mutate), GateRetain/Release.
- Gate pool and heap structures (GateEntry, GateId), resolve_gate() helper, and related fields in VM state.
- Unit tests tied to HIP/RC model, e.g.:
- fn test_hip_traps_oob()
- fn test_hip_traps_type()
- fn test_invalid_gate_traps()
- fn test_gate_ids_distinct_and_round_trip()
- crates/console/prometeu-vm/src/scope_frame.rs
- Inventory only (scope handling). Scope is not inherently HIP, keep or revise later under GC if needed.
- prometeu-hal / prometeu-system (touch points)
- crates/console/prometeu-hal/src/host_return.rs
- Pushes Value::Gate in some host return paths. Will require edits once Value::Gate is removed.
- crates/console/prometeu-system/*
- No direct HIP opcode handling expected, but grep for Value::Gate and GATE_ in formatting/printing if any.
2) Trap codes (RC/HIP)
Remove — gate-specific traps (delete constants and all usages)
- crates/console/prometeu-bytecode/src/abi.rs
- Remove identifiers tied to HIP/gate semantics
Keep — still meaningful under GC model (names may be revised later, but remain functionally)
- crates/console/prometeu-bytecode/src/abi.rs
- TRAP_OOB (0x03) — out-of-bounds (generic)
- TRAP_INVALID_SYSCALL (0x0000_0007)
- TRAP_STACK_UNDERFLOW (0x0000_0008)
- TRAP_INVALID_LOCAL (0x0000_0009)
- TRAP_DIV_ZERO (0x0000_000A)
- TRAP_INVALID_FUNC (0x0000_000B)
- TRAP_BAD_RET_SLOTS (0x0000_000C)
- TRAP_TYPE — retained; semantics generalized to non-HIP type mismatches
Usages to edit alongside removal
- crates/console/prometeu-vm/src/virtual_machine.rs
- Imports: remove any gate-specific trap imports
- resolve_gate() and gate handlers construct TrapInfo with those codes
- Tests asserting these trap codes
3) Legacy terminology occurrences (inventory)
Gate/GATE_
- Files containing gate-centric logic or names:
- crates/console/prometeu-bytecode/src/opcode.rs — Gate* opcodes
- crates/console/prometeu-bytecode/src/opcode_spec.rs — GATE_* names
- crates/console/prometeu-bytecode/src/value.rs — Value::Gate
- crates/console/prometeu-vm/src/virtual_machine.rs — GateEntry, GateId, handlers, tests
- docs/specs/pbs/Prometeu VM Memory model.md — extensive HIP/gate sections
- docs/specs/pbs/Prometeu Scripting - Prometeu Bytecode Script (PBS).md — HIP/gate model and APIs
Retain/Release (RC semantics)
- Opcode names: GateRetain, GateRelease — bytecode + VM
- Docs: reference counting sections in PBS and VM Memory model docs
Scope
- Opcode names: PushScope, PopScope — present in opcode.rs/opcode_spec.rs and executed in VM
- VM scope_frame.rs and scope_stack usage — not inherently HIP, but inventoried here per task wording
HIP (Heap Interface Protocol)
- Labelled sections/comments:
- crates/console/prometeu-bytecode/src/opcode.rs — comment header “HIP (Heap Interface Protocol)” above Gate* opcodes
- docs/specs/pbs/Prometeu VM Memory model.md — HIP-specific architecture
- docs/specs/pbs/Prometeu Scripting - Prometeu Bytecode Script (PBS).md — HIP world, borrow/mutate/peek, RC
- files/Borrow Mutate - Compiler GC.md — discusses removing RC/HIP in favor of GC; keep as design context but will need text updates after reset
4) Deletion map (by module)
Will be removed outright (symbols or blocks)
- prometeu-bytecode
- opcode.rs: all Gate* variants and Alloc variant; TryFrom/encoding IDs and cycles for those
- opcode_spec.rs: corresponding spec arms (GATE_*, ALLOC)
- abi.rs: gate-specific trap constants
- prometeu-vm
- virtual_machine.rs: handlers for Alloc and all Gate* opcodes; GateEntry/GateId data structures; resolve_gate(); HIP-focused unit tests listed above
Will be edited (kept but changed)
- prometeu-bytecode
- value.rs: remove Value::Gate and update Display/eq code paths
- program_image.rs: remove/adjust Value::Gate mapping in constant pool materialization
- decoder.rs: no semantic change; ensure UnknownOpcode remains deterministic (already the case)
- prometeu-vm
- virtual_machine.rs: remove trap imports for legacy codes; update match to exclude removed opcodes; prune gate/heap fields
- scope_frame.rs: keep unless later ISA changes require scope model changes (not part of HIP removal)
- prometeu-hal
- host_return.rs: stop producing Value::Gate once Value enum loses Gate
Docs to remove or rewrite (architecture reset)
- docs/specs/pbs/Prometeu VM Memory model.md — replace HIP/RC model with GC stack+heap model
- docs/specs/pbs/Prometeu Scripting - Prometeu Bytecode Script (PBS).md — remove HIP world/RC sections; update storage semantics
- Any disasm examples or golden outputs referring to GATE_* or ALLOC — update after opcode removal
Tests/fixtures affected
- Unit tests in prometeu-vm referencing HIP traps or Gate behavior:
- test_hip_traps_oob, test_hip_traps_type, test_invalid_gate_traps, test_gate_ids_distinct_and_round_trip
- Golden disassemblies under test-cartridges/*/build/program.disasm.txt may include GATE_* lines — grep and update in follow-up PRs
5) Reviewer checklist for follow-up PRs
- Bytecode enum/spec cleaned: no Gate*/Alloc variants remain
- VM execution has no references to Gate/GateEntry/resolve_gate
- Value enum no longer has Gate; all Display/eq/serde paths fixed
- Trap ABI contains only GC-era traps (OOB, stack underflow, invalid func/local/syscall, div by zero, bad ret slots, etc.)
- Disassembler/printers do not print legacy names (GATE_*, ALLOC)
- Docs no longer mention HIP/RC; updated to GC model
- All tests green (`cargo test`) after deletions and necessary test updates
6) Notes
- This document inventories occurrences proactively, but exact refactors in the reset may slightly shift responsibilities (e.g., how storage allocation appears under GC). Use the grep guide to double-check any stragglers during code deletion PRs.

View File

@ -0,0 +1,299 @@
# 📜 Prometeu — The Fire and the Promise
**PROMETEU** is an imaginary console.
But not in the weak sense of "imagining".
PROMETEU was **deliberately designed** to exist as a **complete computational model**: simple enough to be understood, and powerful enough to create real games.
The name PROMETEU carries two intentional meanings:
- **Prometheus, the titan**, who stole fire from the gods and gave it to humans
- **“Prometeu” as a verb**, a promise made (in Portuguese)
In this project:
- the *fire* is the knowledge of how computers really work
- the *promise* is that nothing is magic, nothing is hidden, and everything can be understood
This manual is that written promise.
---
## 🎯 What is PROMETEU
PROMETEU is a **Virtual Microconsole** — a fictitious computer, with clear rules, limited resources, and deterministic behavior.
It was designed to:
- teach real computing fundamentals
- expose time, memory, and execution
- allow full 2D games (platformers, metroidvanias, arcade)
- serve as a bridge between:
- microcontrollers
- classic consoles
- virtual machines
- modern engines
PROMETEU **is not**:
- a generic engine
- a substitute for Unity or Godot
- a real commercial console
PROMETEU **is**:
- a laboratory
- a didactic instrument
- a serious toy
---
## 🧠 Fundamental Mental Model
> PROMETEU is a microcontroller that draws pixels, plays sound, and handles player input.
>
Everything in the system derives from this.
### Conceptual equivalence
| Real microcontroller | PROMETEU |
| --- | --- |
| Clock | VM Tick |
| Flash | PROMETEU Cartridge |
| RAM | Heap + Stack |
| GPIO | Input |
| DMA | Graphics transfer |
| ISR | System events |
| Peripherals | GFX, AUDIO, INPUT, FS |
If you understand an MCU, you understand PROMETEU.
---
## 🧩 Design Philosophy
### 1. Visible Simplicity
- Every operation has a cost
- Every frame has a budget
- Every error has an observable cause
Nothing happens "just because".
### 2. Determinism
- Same cartridge
- Same input
- Same result
Regardless of the platform.
### 3. Limitation as a pedagogical tool
PROMETEU imposes **intentional** limits:
- finite memory
- time per frame
- limited graphics resources
Limits teach design.
### 4. Progressive depth
PROMETEU can be explored in layers:
1. **Game Layer**`update()` and `draw()`
2. **System Layer** — bytecode, stack, heap
3. **Machine Layer** — cycles, events, peripherals
The student decides how deep to go.
## 🏗️ General Architecture
```
+---------------------------+
| CARTRIDGE |
| (bytecodeX + assets) |
+-------------+-------------+
|
+-------------v-------------+
| PROMETEU VM |
| (stack-based, |
| deterministic) |
+-------------+-------------+
|
+-------------v-------------+
| VIRTUAL PERIPHERALS |
| GFX | AUDIO |INPUT | FS| |
+-------------+-------------+
|
+-------------v-------------+
| PLATFORM LAYER |
| PC | SteamOS | Android |
| iOS | Consoles (future) |
+---------------------------+
```
This manual describes **the PROMETEU machine**, not external tools.
---
## 📦 The PROMETEU Cartridge
A **PROMETEU cartridge** is an immutable package, equivalent to firmware.
Typical content:
- `program.pbx` — PROMETEU bytecode (bytecodeX)
- `assets/` — graphics, audio, maps
- `manifest.json` — metadata and configuration
The cartridge:
- can always be executed
- can always be distributed
- is never blocked by the system
---
## 🧪 Programming Languages
PROMETEU **does not execute high-level languages directly**.
The languages are **sources**, compiled to PROMETEU bytecode.
### Planned languages
- **Java (subset)** — architecture, deep teaching
- **TypeScript (subset)** — accessibility and market
- **Lua (subset)** — classic and lightweight scripting
All converge to:
> The same bytecode. The same VM. The same behavior.
>
---
## 🔄 Execution Model
PROMETEU executes in a fixed loop:
```
BOOT
LOAD CARTRIDGE
INIT
LOOP (60 Hz):
├─INPUT
├─UPDATE
├─ DRAW
├─ AUDIO
└─ SYNC
```
- Default frequency: **60 Hz**
- Each iteration corresponds to **one frame**
- Execution time is **measurable**
---
## ⏱️ CAP — Execution Cap
### Definition
The **CAP** defines an execution budget (time, memory, and resources) **in a specific context**, such as a Game Jam or evaluation.
> CAP never blocks execution.
> CAP never blocks packaging.
> CAP never prevents the game from being played.
>
---
### When the CAP is used
- PROMETEU Game Jams
- Academic evaluations
- Technical challenges
- Comparisons between solutions
Outside of these contexts, PROMETEU operates in **free mode**.
---
### The role of the CAP
The CAP serves to:
- guide technical decisions
- provide measurable feedback
- generate **technical certifications**
- create objective evaluation criteria
PROMETEU **helps during development**:
- visual indicators
- cost alerts
- per-frame profiling
---
## 📄 PROMETEU Certification
### What it is
The **PROMETEU Certification** is a technical report generated from the execution of the game under a defined CAP.
It:
- **does not prevent** the game from running
- **does not invalidate** the delivery
- **records technical evidence**
---
### Report Example
```
PROMETEUCERTIFICATIONREPORT
-----------------------------
Context:PROMETEUJAM#03
Target Profile:PROMETEU-LITE
Execution:
Avg cycles/frame:4,820
Peak cycles/frame:5,612❌
Memory:
Heap peak:34KB❌(limit:32KB)
Status:
❌NOTCOMPLIANT
Notes:
-OverruncausedbyenemyAIupdateloop
-Heappressurecausedbyper-frameallocations
```
The report **accompanies the game**.
---
## 🎓 Educational Use
Evaluation may consider:
- Game quality (creativity, design)
- Technical quality (certification)
- Justified architectural decisions
- Evolution between versions
PROMETEU evaluates **process**, not just result.
< [Summary](topics/table-of-contents.md) >

View File

@ -0,0 +1,254 @@
< [Summary](table-of-contents.md) | [Next](chapter-2.md) >
# ⏱️ **Time Model and Cycles**
# 1. Overview
PROMETEU is a **time-oriented** system.
Nothing happens "instantly".
Every action consumes **measurable time**, expressed in **execution cycles**.
This chapter defines:
- the system's **base clock**
- the concept of a **PROMETEU cycle**
- how time is distributed across frames
- how the **CAP (Execution Cap)** relates to this model
---
## 2. System Base Clock
PROMETEU operates with a **fixed clock of 60 Hz**.
This means:
- 60 iterations of the main loop per second
- each iteration corresponds to **one frame**
- the clock is **deterministic and stable**, regardless of the platform
> The clock defines the machine's rhythm,
not the amount of mandatory work per frame.
>
---
## 3. The PROMETEU Frame
A **PROMETEU frame** represents a complete unit of execution.
Each frame conceptually executes the following stages:
```
FRAME N
──────────────
INPUT
UPDATE
DRAW
AUDIO
SYNC
──────────────
```
The system guarantees:
- fixed order
- predictable repetition
- absence of "ghost" frames
---
## 4. PROMETEU Cycles
### 4.1 Definition
A **PROMETEU cycle** is the smallest abstract unit of time in the system.
- each VM instruction consumes a fixed number of cycles
- peripheral calls also have a cost
- the cost is **documented and stable**
Cycles are:
- **countable**
- **comparable**
- **independent of real hardware**
---
### 4.2 Why cycles, and not milliseconds?
PROMETEU does not measure time in milliseconds because:
- milliseconds vary between platforms
- real clocks differ
- jitter and latency hide the real cost
Cycles allow stating:
> "This program is more expensive than that one,
>
>
> regardless of where it runs."
>
---
## 5. Per-Frame Budget
Each frame has a **maximum cycle budget**.
This budget:
- is reset every frame
- does not accumulate unused cycles
- represents the capacity of the "virtual CPU"
### Conceptual example
```
Frame Budget:10,000cycles
Used:
-Update logic:4,200
-Draw calls:3,100
-Audio:900
Remaining:
1,800cycles
```
---
## 6. Separation between Clock and Work
PROMETEU **does not require** all logic to run every frame.
The programmer is **explicitly encouraged** to distribute work over time.
### Common examples
- enemy logic every 2 frames (30 Hz)
- pathfinding every 4 frames (15 Hz)
- animations independent of AI
- timers based on frame count
This reflects real-world practices from:
- classic consoles
- embedded systems
- microcontroller firmware
> Not everything needs to happen now.
>
---
## 7. Temporal Distribution as Architecture
PROMETEU treats **work distribution over time** as an architectural decision.
This means that:
- code that runs every frame is more expensive
- distributed code is more efficient
- optimization is, first and foremost, **temporal organization**
PROMETEU teaches:
> performance doesn't just come from "less code",
>
>
> but from **when** the code runs.
>
---
## 8. Execution CAP (Contextual Budget)
### 8.1 What is the CAP
The **CAP** defines a set of technical limits associated with a **specific context**, such as:
- Game Jams
- academic evaluations
- technical challenges
The CAP can define:
- maximum cycle budget per frame
- memory limit
- peripheral call limits
---
### 8.2 CAP does not block execution
Fundamental rules:
- the game **always runs**
- the game **can always be packaged**
- the game **can always be played**
The CAP **never prevents** execution.
---
### 8.3 CAP generates evidence, not punishment
When a CAP is active, PROMETEU:
- measures execution
- records peaks
- identifies bottlenecks
- generates a **certification report**
This report:
- does not block the game
- does not invalidate the delivery
- **documents compliance or non-compliance**
---
## 9. Time-Based Certification
PROMETEU certification analyzes:
- average cycle usage per frame
- maximum peaks
- problematic frames
- cost distribution
Example:
```
Target CAP:PROMETEU-LITE
Frame Budget:5,000cycles
Frame 18231:
Used:5,612cycles❌
Primary cause:
enemy.updateAI():1,012cycles
```
This certification accompanies the game as a **technical artifact**.
---
## 10. Pedagogical Implications
The time and cycles model allows teaching:
- execution planning
- time-oriented architecture
- technical trade-offs
- reading real profiles
< [Summary](table-of-contents.md) | [Next](chapter-2.md) >

View File

@ -0,0 +1,350 @@
< [Back](chapter-9.md) | [Summary](table-of-contents.md) | [Next](chapter-11.md) >
# 🛠️ **Debug, Inspection, and Profiling**
## 1. Overview
PROMETEU was designed to **be observed**.
Debug, inspection, and profiling **are not optional external tools**
they are **integral parts of the machine**.
Nothing happens without leaving traces.
Nothing consumes resources without being measured.
Nothing fails without explanation.
> PROMETEU does not hide state.
> PROMETEU exposes behavior.
>
---
## 2. Debug Philosophy in PROMETEU
PROMETEU follows three fundamental debug principles:
1. **State before abstraction**
The programmer sees the machine before seeing “features”.
2. **Time as first class**
Every action is analyzed in the context of the frame and cycles.
3. **Observation does not alter execution**
Debug never changes the behavior of the system.
---
## 3. Execution Modes
PROMETEU operates in three main modes:
### 3.1 Normal Mode
- continuous execution
- no detailed inspection
- focus on game and experience
---
### 3.2 Debug Mode
- controlled execution
- access to internal state
- pauses and stepping
This mode is used for:
- learning
- investigation
- error correction
---
### 3.3 Certification Mode
- deterministic execution
- collected metrics
- report generation
No mode alters the logical result of the program.
---
## 4. Execution Debug
### 4.1 Pause and Resume
The system can be paused at safe points:
- frame start
- before UPDATE
- after DRAW
- before SYNC
During pause:
- state is frozen
- buffers are not swapped
- logical time does not advance
---
### 4.2 Step-by-Step
PROMETEU allows stepping at different levels:
- **by frame**
- **by function**
- **by VM instruction**
Stepping by instruction reveals:
- Program Counter (PC)
- current instruction
- operand stack
- call stack
---
## 5. State Inspection
### 5.1 Stacks
PROMETEU allows inspecting:
- **Operand Stack**
- **Call Stack**
For each frame:
- content
- depth
- growth and cleanup
Stack overflow and underflow are immediately visible.
---
### 5.2 Heap
The heap can be inspected in real time:
- total size
- current usage
- peak usage
- live objects
The programmer can observe:
- allocation patterns
- fragmentation
- GC pressure
---
### 5.3 Global Space
Global variables:
- current values
- references
- initialization
Globals are visible as **static RAM**.
---
## 6. Graphics Debug
PROMETEU allows inspecting the graphics system:
- front buffer
- back buffer
- palette state
- active sprites
It is possible to:
- freeze the image
- observe buffers separately
- identify excessive redraw
---
## 7. Time Profiling (Cycles)
### 7.1 Per-Frame Measurement
For each frame, PROMETEU records:
- total cycles used
- cycles per subsystem
- execution peaks
Conceptual example:
```
Frame 18231:
Total:9,842/10,000cycles
UPDATE:4,210
DRAW:3,180
AUDIO:920
SYSTEM:612
```
---
### 7.2 Per-Function Profiling
PROMETEU can associate cycles with:
- functions
- methods
- logical blocks
This allows answering:
> “where is the time being spent?”
>
---
### 7.3 Per-Instruction Profiling
At the lowest level, the system can display:
- executed instructions
- individual cost
- frequency
This level is especially useful for:
- VM teaching
- deep optimization
- bytecode analysis
---
## 8. Memory Profiling
PROMETEU records:
- average heap usage
- heap peak
- allocations per frame
- GC frequency
Example:
```
Heap:
Avg:24KB
Peak:34KB❌
Limit:32KB
```
These data directly feed the certification.
---
## 9. Breakpoints and Watchpoints
### 9.1 Breakpoints
PROMETEU supports breakpoints in:
- specific frames
- functions
- VM instructions
Breakpoints:
- pause execution
- preserve state
- do not change behavior
---
### 9.2 Watchpoints
Watchpoints monitor:
- variables
- heap addresses
- specific values
Execution can pause when:
- a value changes
- a limit is exceeded
---
## 10. Event and Interrupt Debugging
PROMETEU allows observing:
- event queue
- active timers
- occurred interrupts
Each event has:
- origin
- frame
- cost
- consequence
Nothing happens “silently”.
---
## 11. Integration with CAP and Certification
All debug and profiling data:
- feed the certification report
- are collected deterministically
- do not depend on external tools
The final report is:
- reproducible
- auditable
- explainable
---
## 12. Pedagogical Use
This system allows teaching:
- how to debug real systems
- how to read metrics
- how to correlate time and memory
- how to justify technical decisions
The student learns:
> debug is not trial and error,
> it is informed observation.
>
---
## 13. Summary
- debug is part of the system
- inspection is complete
- profiling is deterministic
- time and memory are visible
- certification is evidence-based
< [Back](chapter-9.md) | [Summary](table-of-contents.md) | [Next](chapter-11.md) >

View File

@ -0,0 +1,258 @@
< [Back](chapter-10.md) | [Summary](table-of-contents.md) | [Next](chapter-12.md) >
# 🌍 **Portability Guarantees and Cross-Platform Execution**
## 1. Overview
PROMETEU was designed from the beginning to be **portable by construction**, not by later adaptation.
A PROMETEU cartridge:
- executes the same way on any supported platform
- produces the same results for the same inputs
- maintains predictable technical behavior
> PROMETEU does not depend on the platform.
> The platform depends on PROMETEU.
>
This chapter defines the system's **portability contract**.
---
## 2. Fundamental Principle of Portability
Portability in PROMETEU is based on a simple rule:
> Only the PROMETEU VM defines the program's behavior.
>
This means:
- source languages do not matter after compilation
- real hardware does not influence logic
- the operating system does not change semantics
Everything that does not belong to the VM is **adaptation**, not execution.
---
## 3. Separation of Responsibilities
PROMETEU strictly separates three layers:
```
+----------------------------+
| CARTRIDGE |
| (bytecode + assets) |
+-------------+--------------+
|
+-------------v--------------+
| PROMETEU VM |
| (logic, time, memory) |
+-------------+--------------+
|
+-------------v--------------+
| PLATFORM LAYER |
| window, audio, input, FS |
+----------------------------+
```
### What the VM controls
- bytecode execution
- logical time (frames, cycles)
- memory (stack, heap)
- determinism
- costs and metrics
### What the platform provides
- pixel display
- audio output
- physical input collection
- access to the sandbox file system
The platform **never decides behavior**.
---
## 4. Determinism as the Basis of Portability
PROMETEU guarantees determinism through:
- fixed logical clock (60 Hz)
- abstract cycles, not real time
- input sampled per frame
- absence of implicit concurrency
- absence of system-dependent operations
This allows stating:
> “If the cartridge is the same, the game is the same.”
>
---
## 5. Independence from Real Hardware
PROMETEU **does not use**:
- operating system timers
- high-resolution clocks
- CPU-specific instructions
- uncontrolled graphics acceleration
All performance is measured in **PROMETEU cycles**, not in milliseconds.
The real hardware:
- only executes the VM
- never interferes with semantics
---
## 6. Floating Point Precision
To guarantee numerical consistency:
- PROMETEU defines explicit mathematical operations
- avoids dependence on specific FPUs
- standardizes `number` behavior
This avoids:
- divergences between architectures
- unpredictable cumulative errors
- subtle differences between platforms
---
## 7. Cross-Platform Input
PROMETEU abstracts physical input into **logical state**.
- keyboard, gamepad, and touch are mapped externally
- the cartridge only sees logical buttons and axes
- the reading moment is fixed per frame
The same cartridge:
- reacts the same way on PC, mobile, or console
- does not contain device-dependent logic
---
## 8. Cross-Platform Audio
The audio system:
- defines channels and mixing logically
- uses native APIs only as output
- maintains per-frame synchronization
Hardware differences:
- do not change logical timing
- do not change sound sequence
- do not affect certification
---
## 9. Cross-Platform Graphics
The graphics system:
- operates on a logical framebuffer
- uses an indexed palette
- does not depend on a specific GPU
The platform layer:
- only displays the framebuffer
- does not reinterpret graphics commands
PROMETEU **does not delegate graphics decisions to the hardware**.
---
## 10. File System and Persistence
PROMETEU defines a **sandbox logical filesystem**:
- virtual paths
- size limits
- deterministic behavior
The platform maps this filesystem to:
- disk
- mobile storage
- persistent memory
Without changing semantics.
---
## 11. Certification and Portability
The **PROMETEU Certification** is valid for all platforms.
If a cartridge:
- passes on one platform
- with the same inputs
It:
- will pass on all
- will produce the same reports
This is possible because:
> the certification validates the VM, not the environment.
>
---
## 12. What PROMETEU DOES NOT guarantee
PROMETEU **does not promise**:
- identical absolute performance (real FPS)
- identical physical latency
- equivalent energy consumption
PROMETEU promises:
- **identical logical behavior**
- **reproducible technical decisions**
---
## 13. Pedagogical Implications
This model allows teaching:
- the difference between logic and presentation
- why modern engines break determinism
- how to isolate systems
- how to design portable software
The student learns:
> portability is not luck — it is architecture.
>
---
## 14. Summary
- PROMETEU defines behavior in the VM
- platforms only execute
- time is logical, not physical
- input, audio, and graphics are abstracted
- certification is universal
- portability is guaranteed by design
< [Back](chapter-10.md) | [Summary](table-of-contents.md) | [Next](chapter-12.md) >

View File

@ -0,0 +1,236 @@
< [Back](chapter-11.md) | [Summary](table-of-contents.md) | [Next](chapter-13.md) >
# 🧠 Firmware — PrometeuOS (POS) + PrometeuHub
## 1. Overview
The **PROMETEU Firmware** is composed of two layers:
- **PrometeuOS (POS)**: the system firmware/base. It is the maximum authority for boot, peripherals, PVM execution, and fault handling.
- **PrometeuHub**: the system launcher and UI environment, embedded in the firmware, executed **over** the POS and using the **PROMETEU Window System**.
> **Every cartridge is an App**.
> The difference is the **App mode** (Game or System), specified in the cartridge header.
---
## 2. Terms and Definitions
- **Host**: real platform (desktop, mobile, DIY console, etc.) that calls the core at 60Hz and displays the framebuffer.
- **POS (PrometeuOS)**: firmware (system core). Controls boot, PVM, budget, input latch, peripherals, and crash.
- **PrometeuHub**: system UI / launcher running over POS.
- **PVM (PROMETEU Virtual Machine)**: VM that executes the App's bytecode.
- **App / Cartridge**: PROMETEU executable package (e.g., `.pbc`).
- **AppMode**: App mode in the cartridge header:
- `GAME`: assumes full screen, own UI
- `SYSTEM`: app integrated into the system, should run "in a window" in the Hub
- **Logical frame**: App update unit, terminated by `FRAME_SYNC`.
- **Host tick**: call from the host (real 60Hz).
---
## 3. Responsibilities of the POS (PrometeuOS)
The POS must guarantee:
### 3.1 Boot and System States
- Deterministic RESET (known state)
- Optional SPLASH (short)
- initialization of the PrometeuHub and the Window System
- return to the Hub after app exit/crash
### 3.2 PVM Control
- load cartridge into the PVM
- reset PC/stack/heap upon app start
- execute budget per logical frame
- respect `FRAME_SYNC` as logical frame boundary
- maintain input latched per logical frame
### 3.3 Peripheral Control
- initialization and reset of:
- GFX / buffers
- AUDIO (channels, command queue)
- INPUT / TOUCH
- "safe mode" policy (ignore Hub/config if requested)
### 3.4 Failures and Recovery
- capture fatal PVM faults
- present **POS_CRASH_SCREEN** (outside the PVM)
- allow:
- app restart
- return to Hub
- system reset
---
## 4. Responsibilities of the PrometeuHub
The PrometeuHub must:
- list available Apps in storage
- read metadata from the cartridge header (including `AppMode`)
- allow selection and launch
- apply system theme/UI via Window System
- decide the "execution behavior" based on the `AppMode`
### 4.1 Decision by `AppMode`
When selecting an App:
- If `AppMode = GAME`:
- the Hub delegates to the POS: **execute as a game** (full screen)
- If `AppMode = SYSTEM`:
- the Hub delegates to the POS: **load the App**
- and the Hub **opens a window** and runs the App "integrated into the system"
> Note: the Hub does not execute bytecode directly; it always delegates to the POS.
---
## 5. PROMETEU Window System (System UI)
The Window System is part of the firmware (POS + Hub) and offers:
- global theme (palette, fonts, UI sounds)
- window system (at least: one active window + dialog stack)
- input routing:
- focus, navigation, confirmation/cancellation
- touch as "tap" (single pointer)
### 5.1 System App Integration
System Apps are executed in "window" mode and must:
- respect the viewport area provided by the window
- cooperate with the Window System's focus/inputs
- accept being suspended/closed by the Hub
The Hub can offer window "chrome":
- title
- back/close button
- overlays (toast, dialogs)
---
## 6. Cartridge Header (minimum requirement for the firmware)
The firmware requires every cartridge to provide at least the following in the header:
- `magic` / `version`
- `app_id` (short string or hash)
- `title` (string)
- `entrypoint` (PC address)
- `app_mode`: `GAME` or `SYSTEM`
- (optional) `icon_id` / `cover_id`
- (optional) `requested_cap` / VM version
The POS/HUB must use `app_mode` to decide on execution.
---
## 7. Official Boot States (Firmware)
### 7.1 POS_RESET
- initializes peripherals
- prepares PVM
- loads config
- detects safe mode
Transition: `POS_SPLASH` or `POS_LAUNCH_HUB`
### 7.2 POS_SPLASH (optional)
- displays logo/version
- fixed time or "skip"
Transition: `POS_LAUNCH_HUB`
### 7.3 POS_LAUNCH_HUB
- starts Window System
- enters the Hub loop
Transition: `HUB_HOME`
### 7.4 HUB_HOME
- lists Apps
- allows selection:
- `GAME``POS_RUN_GAME(app)`
- `SYSTEM``HUB_OPEN_WINDOW(app)` (which delegates `POS_RUN_SYSTEM(app)`)
### 7.5 POS_RUN_GAME(app)
- loads cartridge into the PVM
- executes logical frame (budget + `FRAME_SYNC`)
- presents frames
- maintains latch per logical frame
Exits:
- `APP_EXIT``POS_LAUNCH_HUB`
- `APP_FAULT``POS_CRASH_SCREEN`
### 7.6 POS_RUN_SYSTEM(app)
- loads cartridge into the PVM (or separate instance, if supported in the future)
- executes under the Hub/Window System cycle
- presents in the window area (viewport)
Exits:
- `APP_EXIT` → returns to the Hub
- `APP_FAULT``POS_CRASH_SCREEN` (or crash window + fallback)
### 7.7 POS_CRASH_SCREEN
- displays error (type, PC, stack trace)
- actions: restart app / hub / reset
Transition: as chosen.
---
## 8. Execution Model (Budget, Latch, and `FRAME_SYNC`)
The POS is responsible for guaranteeing:
- **Input latched per logical frame**
- execution in slices (host ticks) until the app reaches `FRAME_SYNC`
- only at `FRAME_SYNC`:
- `present()` (or compose+present)
- advances `logical_frame_index`
- releases input latch
- resets the budget for the next logical frame
If the budget runs out before `FRAME_SYNC`:
- does not present
- maintains latch
- execution continues in the next host tick within the same logical frame
---
## 9. Rules for Returning to the Hub
The firmware must offer a "return to system" mechanism:
- button shortcut (e.g., START+SELECT for X ticks)
- or controlled syscall (System Apps only)
Upon returning to the Hub:
- the POS terminates or suspends the current App
- returns to the Window System with a consistent state
---
## 10. Pedagogical Objectives
This firmware allows teaching:
- boot stages and firmware as an authority
- separation between system and application
- deterministic execution with budget and logical frame
- difference between apps (Game vs System)
- fault tolerance and realistic crash handling
---
## 11. Summary
- POS is the base layer and controls the PVM, budget, latch, peripherals, and crash.
- PrometeuHub is the embedded firmware launcher/UI over the POS.
- Every cartridge is an App; the header defines the `app_mode` (GAME/SYSTEM).
- `GAME` runs in full screen; `SYSTEM` runs integrated into the Hub in a window.
- `FRAME_SYNC` is the logical frame boundary.
< [Back](chapter-11.md) | [Summary](table-of-contents.md) | [Next](chapter-13.md) >

View File

@ -0,0 +1,110 @@
< [Back](chapter-12.md) | [Summary](table-of-contents.md) | [Next](chapter-14.md) >
# Cartridges
**Version:** 1.0 (stable baseline)
**Status:** Proposal
---
## 1. Objective
Define a minimum and stable contract for Prometeu cartridges, allowing:
* App identification
* Mode selection (Game/System)
* Entrypoint resolution
* Predictable loading by the runtime
---
## 2. Concept
A cartridge is the distributable unit of Prometeu. It can exist as:
* **Directory (dev)** — ideal for development and hot-reload
* **Packaged file (.pmc)** — ideal for distribution
Both share the same logical layout.
---
## 3. Logical Layout
```
<cartridge>/
├── manifest.json
├── program.pbc
└── assets/
└── ...
```
Required fields:
* `manifest.json`
* `program.pbc` (Prometeu bytecode)
---
## 4. manifest.json (Contract v1)
```json
{
"magic": "PMTU",
"cartridge_version": 1,
"app_id": 1234,
"title": "My Game",
"app_version": "1.0.0",
"app_mode": "Game",
"entrypoint": "main"
}
```
### Fields
* `magic`: fixed string `PMTU`
* `cartridge_version`: format version
* `app_id`: unique numerical identifier
* `title`: name of the app
* `app_version`: app version
* `app_mode`: `Game` or `System`
* `entrypoint`: symbol or index recognized by the VM
---
## 5. Runtime Rules
* Validate `magic` and `cartridge_version`
* Read `app_mode` to decide execution flow
* Resolve `entrypoint` in `program.pbc`
* Ignore `assets/` if not supported yet
---
## 6. Usage Modes
### Directory (development)
```
prometeu --run ./mycart/
```
### Packaged file
```
prometeu --run mygame.pmc
```
Both must behave identically in the runtime.
---
## 7. Contract Stability
From v1 onwards:
* `manifest.json` is the source of truth
* Fields can only be added in a backward-compatible manner
* Incompatible changes require a new `cartridge_version`
< [Back](chapter-12.md) | [Summary](table-of-contents.md) | [Next](chapter-14.md) >

View File

@ -0,0 +1,135 @@
< [Back](chapter-13.md) | [Summary](table-of-contents.md) | [Next](chapter-15.md) >
# Boot Profiles
**Version:** 1.0
**Status:** Proposal
---
## 1. Objective
Define how Prometeu decides what to execute at startup:
* Hub
* Automatic cartridge
* Debug mode
---
## 2. BootTarget Concept
At the beginning of boot, the POS resolves a target:
```rust
enum BootTarget {
Hub,
Cartridge { path: String, debug: bool },
}
```
---
## 3. General Rules
### If BootTarget == Hub
* Firmware enters `HubHome`
* No cartridge is automatically loaded
### If BootTarget == Cartridge
1. Load cartridge
2. Read `app_mode` in the manifest
3. Apply rules:
* `Game`:
* Enter `RunningGame`
* `System`:
* Stay in `HubHome`
* Open the app as a window/system tool
---
## 4. Host CLI
### Default boot
```
prometeu
```
Result: enters the Hub
### Run cartridge
```
prometeu run <cartridge>
```
Result:
* Game → enters directly into the game
* System → opens as a tool in the Hub
### Run with debugger
```
prometeu debug <cartridge>
```
Result:
* Same flow as `run`
* Runtime starts in debug mode
* Waits for connection from the Java Debugger
---
## 5. Firmware States
Firmware maintains only:
* `Boot`
* `HubHome`
* `RunningGame`
* `AppCrashed`
System apps never change the firmware state.
---
## 6. Behavior on Real Hardware (future)
* If miniSD/physical cartridge is present at boot:
* POS can:
* always go to the Hub, or
* auto-execute according to user configuration
---
## 7. Integration with Debugger
When `debug == true`:
* Runtime:
* Initializes
* Opens DevTools socket
* Waits for `start` command
* Only after that does it start cartridge execution
---
## 8. Stability
* BootTarget is an internal POS contract
* Host CLI must respect these rules
* New boot modes must be compatible extensions
< [Back](chapter-13.md) | [Summary](table-of-contents.md) | [Next](chapter-15.md) >

View File

@ -0,0 +1,331 @@
< [Back](chapter-14.md) | [Summary](table-of-contents.md) | [Next](chapter-16.md) >
# Asset Management
## Bank-Centric Hardware Asset Model
**Scope:** Runtime / Hardware Asset Management (SDK-agnostic)
---
## 1. Fundamental Principles
1. **Every asset in Prometeu resides in a Bank**
2. **A Bank is a hardware memory management system**
3. **Assets are cold binaries stored in the cartridge**
4. **Asset memory belongs to the console, not to the VM**
5. **Loading, residency, and eviction are explicit**
6. **Hardware does not interpret gameplay semantics**
7. **The SDK orchestrates policies; hardware executes contracts**
> In Prometeu, you do not load “data”. You load **residency**.
---
## 2. Asset Origin (Cold Storage)
* All assets initially reside in **cold storage** inside the cartridge.
* Typically, each asset corresponds to a binary file.
* The runtime **never scans the cartridge directly**.
* All access goes through the **Asset Table**.
---
## 3. Asset Table
The Asset Table is an index loaded at cartridge boot.
It describes **content**, not residency.
### Location
* The Asset Table **must be embedded as JSON inside `manifest.json`**.
* Tooling may compile this JSON into a binary table for runtime use, but the source of truth is the manifest.
### Required Fields (conceptual)
* `asset_id` (integer, internal identifier)
* `asset_name` (string, user-facing identifier)
* `asset_type` (TILEBANK, SOUNDBANK, BLOB, TILEMAP, ...)
* `bank_kind` (mandatory, single)
* `offset` (byte offset in cartridge)
* `size` (cold size)
* `decoded_size` (resident size)
* `codec` (RAW, LZ4, ZSTD, ...)
* `asset_metadata` (type-specific)
> `bank_kind` defines **where** an asset may reside in hardware.
---
## 4. Bank — Definition
> **A Bank is the residency and swapping mechanism of the Prometeu hardware.**
A Bank:
* owns **numbered slots**
* enforces **resident memory budgets**
* enforces **staging / inflight budgets**
* accepts only compatible assets
* supports **atomic swap via commit**
* exposes memory and residency metrics
Banks are hardware infrastructure, not assets.
---
## 5. BankKind
Each Bank belongs to a **BankKind**, defining its pipeline and constraints.
### Example BankKinds
* `GFX_TILEBANK`
* `AUDIO_SOUNDBANK`
* `DATA_BLOBBANK`
* `MAP_TILEMAPBANK`
Each BankKind defines:
* slot count
* maximum resident memory
* inflight / staging memory budget
* decode and validation pipeline
* hardware consumer subsystem (renderer, audio mixer, etc.)
---
## 6. Slots and Slot References
* Each BankKind owns a fixed set of **slots**.
* A slot:
* references a resident asset (or is empty)
* never stores data directly
* may expose a **generation counter** (debug)
### Slot Reference
Slots are always referenced with explicit BankKind context:
* `gfxSlot(3)`
* `audioSlot(1)`
* `blobSlot(7)`
This prevents cross-bank ambiguity.
---
## 7. Bank Memory Model
* Bank memory is **console-owned memory**.
* It does not belong to the VM heap.
* It does not participate in GC.
* It can be fully released when the cartridge shuts down.
Each Bank manages:
* total memory
* used memory
* free memory
* inflight (staging) memory
Conceptually, each Bank is a **specialized allocator**.
---
## 8. Unified Loader
### Conceptual API
```text
handle = asset.load(asset_name, slotRef, flags)
```
Load flow:
1. Resolve `asset_name` via Asset Table to get its `asset_id`
2. Read `bank_kind` from asset entry
3. Validate compatibility with `slotRef`
4. Enqueue load request
5. Perform IO + decode on worker
6. Produce materialized asset in staging
### Handle States
* `PENDING` — enqueued
* `LOADING` — IO/decode in progress
* `READY` — staging completed
* `COMMITTED` — installed into slot
* `CANCELED`
* `ERROR`
---
## 9. Commit
```text
asset.commit(handle)
```
* Commit is **explicit** and **atomic**
* Executed at a safe frame boundary
* Performs **pointer swap** in the target slot
* Previous asset is released if no longer referenced
The hardware **never swaps slots automatically**.
---
## 10. Asset Deduplication
* A decoded asset exists **at most once per BankKind**.
* Multiple slots may reference the same resident asset.
* Redundant loads become **install-only** operations.
Memory duplication is forbidden by contract.
---
## 11. Bank Specializations
### 11.1 GFX_TILEBANK
* AssetType: TILEBANK
* Immutable graphical tile + palette structure
* Consumed by the graphics subsystem
### 11.2 AUDIO_SOUNDBANK
* AssetType: SOUNDBANK
* Resident audio samples or streams
* Consumed by the audio mixer
### 11.3 DATA_BLOBBANK
* AssetType: BLOB
* Read-only byte buffers
* Hardware does not interpret contents
Used by the SDK via:
* **Views** (zero-copy, read-only)
* **Decode** (materialization into VM heap)
---
## 12. Views and Decode (SDK Contract)
* **View**
* read-only
* zero-copy
* valid only while the blob remains resident
* **Decode**
* allocates VM-owned entities
* independent from the Bank after creation
Hardware is unaware of Views and Decode semantics.
---
## 13. Manifest and Initial Load
* The cartridge may include a `manifest.json`.
* The manifest may declare:
* assets to preload
* target slot references
These loads occur **before the first frame**.
---
## 14. Shutdown and Eviction
* On cartridge shutdown:
* all Banks are cleared
* all resident memory is released
No implicit persistence exists.
---
## 15. Debugger and Telemetry
Each Bank must expose:
* total memory
* used memory
* free memory
* inflight memory
* occupied slots
* `asset_id` per slot
* `asset_name` per slot
* generation per slot
This enables debuggers to visualize:
* memory stacks per Bank
* memory pressure
* streaming strategies
---
## 16. Minimal Syscall API (Derived)
The following syscalls form the minimal hardware contract for asset management:
```text
asset.load(asset_name, slotRef, flags) -> handle
asset.status(handle) -> LoadStatus
asset.commit(handle)
asset.cancel(handle)
bank.info(bank_kind) -> BankStats
bank.slot_info(slotRef) -> SlotStats
```
Where:
* `LoadStatus` ∈ { PENDING, LOADING, READY, COMMITTED, CANCELED, ERROR }
* `BankStats` exposes memory usage and limits
* `SlotStats` exposes current asset_id, asset_name and generation
---
## 17. Separation of Responsibilities
### Hardware (Prometeu)
* manages memory
* loads bytes
* decodes assets
* swaps pointers
* reports usage
### SDK
* defines packs, scenes, and policies
* interprets blobs
* creates VM entities
### VM
* executes logic
* manages its own heap
* never owns hardware assets
---
## 18. Golden Rule
> **Banks are the foundation of the hardware.**
> **Asset Types describe content.**
> **The SDK orchestrates; the hardware executes.**
< [Back](chapter-14.md) | [Summary](table-of-contents.md) | [Next](chapter-16.md) >

View File

@ -0,0 +1,372 @@
< [Back](chapter-15.md) | [Summary](table-of-contents.md) >
# **Host ABI and Syscalls**
This chapter defines the Application Binary Interface (ABI) between the Prometeu Virtual Machine (PVM) and the host environment. It specifies how syscalls are identified, resolved, invoked, verified, and accounted for.
Syscalls provide controlled access to host-managed subsystems such as graphics, audio, input, asset banks, and persistent storage.
This chapter defines the **contract**. Individual subsystems (GFX, AUDIO, MEMCARD, ASSETS, etc.) define their own syscall tables that conform to this ABI.
---
## 1 Design Principles
The syscall system follows these rules:
1. **Deterministic**: Syscalls must behave deterministically for the same inputs and frame state.
2. **Synchronous**: Syscalls execute to completion within the current VM slice.
3. **Non-blocking**: Long operations must be modeled as request + status polling.
4. **Capability-gated**: Each syscall requires a declared capability.
5. **Stack-based ABI**: Arguments and return values are passed via VM slots.
6. **Not first-class**: Syscalls are callable but cannot be stored as values.
7. **Language-agnostic identity**: Syscalls are identified by canonical names, not language syntax.
---
## 2 Canonical Syscall Identity
Syscalls are identified by a **canonical triple**:
```
(module, name, version)
```
Example:
```
("gfx", "present", 1)
("input", "state", 1)
("audio", "play", 2)
```
This identity is:
* Independent of language syntax.
* Stable across languages and toolchains.
* Used for capability checks and linking.
### Language independence
Different languages may reference the same syscall using different syntax:
| Language style | Reference form |
| -------------- | --------------- |
| Dot-based | `gfx.present` |
| Namespace | `gfx::present` |
| Object-style | `Gfx.present()` |
| Functional | `present(gfx)` |
All of these map to the same canonical identity:
```
("gfx", "present", 1)
```
The compiler is responsible for this mapping.
---
## 3 Syscall Resolution
The host maintains a **Syscall Registry**:
```
(module, name, version) -> syscall_id
```
Example:
```
("gfx", "present", 1) -> 0x0101
("gfx", "submit", 1) -> 0x0102
("audio", "play", 1) -> 0x0201
```
### Resolution process
At cartridge load time:
1. The cartridge declares required syscalls using canonical identities.
2. The host verifies capabilities.
3. The host resolves each canonical identity to a numeric `syscall_id`.
4. The VM stores the resolved table for fast execution.
At runtime, the VM executes:
```
SYSCALL <id>
```
Only numeric IDs are used during execution.
---
## 4 Syscall Instruction Semantics
The VM provides a single instruction:
```
SYSCALL <id>
```
Where:
* `<id>` is a 32-bit integer identifying the syscall.
Execution steps:
1. The VM looks up syscall metadata using `<id>`.
2. The VM verifies that enough arguments exist on the stack.
3. The VM checks capability requirements.
4. The syscall executes in the host environment.
5. The syscall leaves exactly `ret_slots` values on the stack.
If any contract rule is violated, the VM traps.
---
## 5 Syscall Metadata Table
Each syscall is defined by a metadata entry.
### SyscallMeta structure
```
SyscallMeta {
id: u32
module: string
name: string
version: u16
arg_slots: u8
ret_slots: u8
capability: CapabilityId
may_allocate: bool
cost_hint: u32
}
```
Fields:
| Field | Description |
| -------------- | ------------------------------------------------ |
| `id` | Unique numeric syscall identifier |
| `module` | Canonical module name |
| `name` | Canonical syscall name |
| `version` | ABI version of the syscall |
| `arg_slots` | Number of input stack slots |
| `ret_slots` | Number of return stack slots |
| `capability` | Required capability |
| `may_allocate` | Whether the syscall may allocate VM heap objects |
| `cost_hint` | Expected cycle cost |
The verifier uses this table to validate stack effects.
---
## 6 Arguments and Return Values
Syscalls use the same slot-based ABI as functions.
### Argument passing
Arguments are pushed onto the stack before the syscall.
Example:
```
push a
push b
SYSCALL X // expects 2 arguments
```
### Return values
After execution, the syscall leaves exactly `ret_slots` values on the stack.
Example:
```
// before: []
SYSCALL input_state
// after: [held, pressed, released]
```
Composite return values are represented as multiple slots (stack tuples).
---
## 7 Syscalls as Callable Entities (Not First-Class)
Syscalls behave like functions in terms of arguments and return values, but they are **not first-class values**.
This means:
* Syscalls can be invoked.
* Syscalls cannot be stored in variables.
* Syscalls cannot be passed as arguments.
* Syscalls cannot be returned from functions.
Only user-defined functions and closures are first-class.
### Conceptual declaration
```
host fn input_state() -> (int, int, int)
```
This represents a syscall with three return values, but it cannot be treated as a function value.
---
## 8 Error Model: Traps vs Status Codes
Syscalls use a hybrid error model.
### Trap conditions (contract violations)
The VM traps when:
* The syscall id is invalid.
* The canonical identity cannot be resolved.
* The required capability is missing.
* The stack does not contain enough arguments.
* A handle is invalid or dead.
These are fatal contract violations.
### Status returns (domain conditions)
Normal operational states are returned as values.
Examples:
* asset not yet loaded
* audio voice unavailable
* memcard full
These are represented by status codes in return slots.
---
## 9 Capability System
Each syscall requires a capability.
Capabilities are declared by the cartridge manifest.
Example capability groups:
* `gfx`
* `audio`
* `input`
* `asset`
* `memcard`
If a syscall is invoked without the required capability:
* The VM traps.
---
## 10 Interaction with the Garbage Collector
The VM heap is managed by the GC. Host-managed memory is separate.
### Heap vs host memory
| Memory | Managed by | GC scanned |
| --------------- | ---------- | ---------- |
| VM heap objects | VM GC | Yes |
| Asset banks | Host | No |
| Audio buffers | Host | No |
| Framebuffers | Host | No |
Assets are addressed by identifiers, not VM heap handles.
### Host root rule
If a syscall stores a handle to a VM heap object beyond the duration of the call, it must register that handle as a **host root**.
This prevents the GC from collecting objects still in use by the host.
This rule applies only to VM heap objects (such as closures or user objects), not to asset identifiers or primitive values.
---
## 11 Determinism Rules
Syscalls must obey deterministic execution rules.
Forbidden behaviors:
* reading real-time clocks
* accessing non-deterministic OS APIs
* performing blocking I/O
Allowed patterns:
* frame-based timers
* request + poll status models
* event delivery at frame boundaries
---
## 12 Cost Model and Budgeting
Each syscall contributes to frame cost.
The VM tracks:
* cycles spent in syscalls
* syscall counts
* allocation cost (if any)
Example telemetry:
```
Frame 10231:
Syscalls: 12
Cycles (syscalls): 380
Allocations via syscalls: 2
```
Nothing is free.
---
## 13 Blocking and Long Operations
Syscalls must not block.
Long operations must use a two-phase model:
1. Request
2. Status polling or event notification
Example pattern:
```
asset.load(id)
...
status, progress = asset.status(id)
```
---
## 14 Summary
* Syscalls are deterministic, synchronous, and non-blocking.
* They use the same slot-based ABI as functions.
* They are callable but not first-class.
* They are identified canonically by `(module, name, version)`.
* Language syntax does not affect the ABI.
* The host resolves canonical identities to numeric syscall IDs.
* Capabilities control access to host subsystems.
* GC only manages VM heap objects.
* Host-held heap objects must be registered as roots.
* All syscall costs are tracked per frame.
< [Back](chapter-15.md) | [Summary](table-of-contents.md) >

View File

@ -0,0 +1,427 @@
< [Back](chapter-1.md) | [Summary](table-of-contents.md) | [Next](chapter-3.md) >
# **Prometeu Virtual Machine (PVM)**
This chapter defines the execution model, value system, calling convention, memory model, and host interface of the Prometeu Virtual Machine.
The PVM is a **deterministic, stack-based VM** designed for a 2D fantasy console environment. Its primary goal is to provide predictable performance, safe memory access, and a stable execution contract suitable for real-time games running at a fixed frame rate. fileciteturn2file0
---
## 1 Core Design Principles
The PVM is designed around the following constraints:
1. **Deterministic execution**: no hidden threads or asynchronous callbacks.
2. **Frame-based timing**: execution is bounded by frame time.
3. **Safe memory model**: all heap objects are accessed through handles.
4. **Simple compilation target**: stack-based bytecode with verified control flow.
5. **Stable ABI**: multi-value returns with fixed slot semantics.
6. **First-class functions**: functions can be passed, stored, and returned.
---
## 2 Execution Model
The PVM executes bytecode in a **frame loop**. Each frame:
1. The firmware enters the VM.
2. The VM runs until:
* the frame budget is consumed, or
* a `FRAME_SYNC` instruction is reached.
3. At `FRAME_SYNC`:
* events are delivered
* input is sampled
* optional GC may run
4. Control returns to the firmware.
`FRAME_SYNC` is the **primary safepoint** in the system.
---
## 3 Value Types
All runtime values are stored in VM slots as a `Value`.
### Primitive value types (stack values)
| Type | Description |
| ------- | --------------------- |
| `int` | 64-bit signed integer |
| `bool` | Boolean value |
| `float` | 64-bit floating point |
### Built-in vector and graphics types (stack values)
These are treated as VM primitives with dedicated opcodes:
| Type | Description |
| ------- | --------------------------------- |
| `vec2` | 2D vector (x, y) |
| `color` | Packed color value |
| `pixel` | Combination of position and color |
These types:
* live entirely on the stack
* are copied by value
* never allocate on the heap
### Heap values
All user-defined objects live on the heap and are accessed via **handles**.
| Type | Description |
| -------- | -------------------------- |
| `handle` | Reference to a heap object |
| `null` | Null handle |
Handles may refer to:
* user objects
* arrays
* strings
* closures
---
## 4 Handles and Gate Table
Heap objects are accessed through **handles**. A handle is a pair:
```
handle = { index, generation }
```
The VM maintains a **gate table**:
```
GateEntry {
alive: bool
generation: u32
base: usize
slots: u32
type_id: u32
}
```
When an object is freed:
* its gate entry is marked dead
* its generation is incremented
If a handles generation does not match the gate entry, the VM traps.
This prevents use-after-free bugs.
---
## 5 Heap Model
* All user objects live in the heap.
* Objects are fixed-layout blocks of slots.
* No inheritance at the memory level.
* Traits/interfaces are resolved by the compiler or via vtables.
Built-in types remain stack-only.
Heap objects include:
* user structs/classes
* strings
* arrays
* closures
---
## 6 Tuples and Multi-Return ABI
The PVM supports **multi-value returns**.
### Tuple rules
* Tuples are **stack-only**.
* Maximum tuple arity is **N = 6**.
* Tuples are not heap objects by default.
* To persist a tuple, it must be explicitly boxed.
### Call convention
Each function declares a fixed `ret_slots` value.
At call time:
1. Caller prepares arguments.
2. `CALL` transfers control.
3. Callee executes.
4. `RET` leaves exactly `ret_slots` values on the stack.
The verifier ensures that:
* all control paths produce the same `ret_slots`
* stack depth is consistent.
---
## 7 Call Stack and Frames
The VM uses a **call stack**.
Each frame contains:
```
Frame {
return_pc
base_pointer
ret_slots
}
```
Execution uses only the following call instructions:
| Opcode | Description |
| ------ | ---------------------- |
| `CALL` | Calls a function by id |
| `RET` | Returns from function |
There is no separate `PUSH_FRAME` or `POP_FRAME` instruction in the public ISA.
---
## 8 Closures and First-Class Functions
Closures are heap objects and represent **function values**.
The PVM treats functions as **first-class values**. This means:
* Functions can be stored in variables.
* Functions can be passed as arguments.
* Functions can be returned from other functions.
* All function values are represented as closures.
Even functions without captures are represented as closures with an empty capture set.
### Closure layout
```
Closure {
func_id
captures[]
}
```
Captures are stored as handles or value copies.
All closure environments are part of the GC root set.
### Direct and indirect calls
The PVM supports two forms of function invocation:
| Opcode | Description |
| -------------- | -------------------------------------- |
| `CALL` | Direct call by function id |
| `CALL_CLOSURE` | Indirect call through a closure handle |
For `CALL_CLOSURE`:
1. The closure handle is read from the stack.
2. The VM extracts the `func_id` from the closure.
3. The function is invoked using the closures captures as its environment.
The verifier ensures that:
* The closure handle is valid.
* The target functions arity matches the call site.
* The `ret_slots` contract is respected.
---
## 9 Coroutines (Deterministic)
The PVM supports **cooperative coroutines**.
Characteristics:
* Coroutines are scheduled deterministically.
* No preemption.
* No parallel execution.
* All scheduling happens at safepoints.
Each coroutine contains:
```
Coroutine {
call_stack
operand_stack
state
}
```
### Coroutine instructions
| Opcode | Description |
| ------- | -------------------------- |
| `SPAWN` | Creates a coroutine |
| `YIELD` | Suspends current coroutine |
| `SLEEP` | Suspends for N frames |
Scheduling is:
* round-robin
* deterministic
* bounded by frame budget
Coroutine stacks are part of the GC root set.
---
## 10 Garbage Collection
The PVM uses a **mark-sweep collector**.
### GC rules
* GC runs only at **safepoints**.
* The primary safepoint is `FRAME_SYNC`.
* GC is triggered by:
* heap thresholds, or
* allocation pressure.
### Root set
The GC marks from:
* operand stack
* call stack frames
* global variables
* coroutine stacks
* closure environments
* host-held handles
The collector:
* does not compact memory (v1)
* uses free lists for reuse
---
## 11 Event and Interrupt Model
The PVM does not allow asynchronous callbacks.
All events are:
* queued by the firmware
* delivered at `FRAME_SYNC`
This ensures:
* deterministic execution
* predictable frame timing
Coroutines are the only supported concurrency mechanism.
---
## 12 Host Interface (Syscalls)
All hardware access occurs through syscalls.
Syscalls are:
* synchronous
* deterministic
* capability-checked
They operate on the following subsystems:
### Graphics
* tilebanks
* layers
* sprites
* palette control
* fade registers
* frame present
### Audio
* voice allocation
* play/stop
* volume/pan/pitch
* steal policy
### Input
* sampled once per frame
* exposed as frame state
### Assets
Asset banks are **host-owned memory**.
The VM interacts through handles:
| Syscall | Description |
| -------------- | ---------------------------- |
| `asset.load` | Request asset load into slot |
| `asset.status` | Query load state |
| `asset.commit` | Activate loaded asset |
Asset memory:
* is not part of the VM heap
* is not scanned by GC
### Save Memory (MEMCARD)
| Syscall | Description |
| --------------- | --------------- |
| `mem.read_all` | Read save data |
| `mem.write_all` | Write save data |
| `mem.commit` | Persist save |
| `mem.size` | Query capacity |
---
## 13 Verifier Requirements
Before execution, bytecode must pass the verifier.
The verifier ensures:
1. Valid jump targets
2. Stack depth consistency
3. Correct `ret_slots` across all paths
4. Handle safety rules
5. Closure call safety
6. No invalid opcode sequences
Invalid bytecode is rejected.
---
## 14 Summary
The Prometeu VM is:
* stack-based
* deterministic
* frame-synchronized
* handle-based for heap access
* multi-return capable
* first-class function capable
* coroutine-driven for concurrency
This design balances:
* ease of compilation
* predictable performance
* safety and debuggability
* suitability for real-time 2D games.
< [Back](chapter-1.md) | [Summary](table-of-contents.md) | [Next](chapter-3.md) >

View File

@ -0,0 +1,353 @@
< [Back](chapter-2.md) | [Summary](table-of-contents.md) | [Next](chapter-4.md) >
# 🧠 **Memory Model**
This chapter defines the memory architecture of the Prometeu Virtual Machine (PVM). It describes the stack, heap, handles, object layout, garbage collection, and interaction with host-owned memory such as asset banks.
The memory model is designed to be:
* deterministic
* safe
* simple to verify
* suitable for real-time 2D games
---
## 1 Overview
The PVM uses a **split memory model**:
1. **Stack memory**
* used for temporary values
* function arguments
* multi-return tuples
2. **Heap memory**
* used for all user-defined objects
* accessed only through handles
3. **Host-owned memory**
* asset banks
* audio buffers
* framebuffers
* not part of the VM heap
---
## 2 Stack Memory
The stack is used for:
* primitive values
* built-in value types
* temporary results
* function arguments
* tuple returns
### Stack value types
| Type | Description |
| -------- | ------------------------ |
| `int` | 64-bit integer |
| `bool` | Boolean |
| `float` | 64-bit float |
| `vec2` | 2D vector |
| `color` | Packed color |
| `pixel` | Position + color |
| `handle` | Reference to heap object |
All stack values are:
* fixed-size
* copied by value
* never directly reference raw memory
### Stack properties
* Stack is bounded and verified.
* Stack depth must be consistent across all control paths.
* Stack never stores raw pointers.
---
## 3 Tuples (Stack-Only Aggregates)
Tuples are used for multi-value returns.
### Tuple rules
* Tuples exist only on the stack.
* Maximum tuple arity: **6 slots**.
* Tuples are not heap objects by default.
* To persist a tuple, it must be explicitly boxed into a heap object.
### Example
Function returning two values:
```
fn position(): (int, int)
```
At runtime:
```
stack top → [x, y]
```
---
## 4 Heap Memory
All user-defined objects live in the heap.
### Heap characteristics
* Linear slot-based storage.
* Objects are fixed-layout blocks.
* No raw pointer access.
* No inheritance at memory level.
Heap objects include:
* user structs/classes
* arrays
* strings
* closures
* boxed tuples (optional)
---
## 5 Handles and Gate Table
All heap objects are accessed via **handles**.
A handle is defined as:
```
handle = { index, generation }
```
The VM maintains a **gate table**:
```
GateEntry {
alive: bool
generation: u32
base: usize
slots: u32
type_id: u32
}
```
### Handle safety
When an object is freed:
* `alive` becomes false
* `generation` is incremented
When a handle is used:
* index must exist
* generation must match
Otherwise, the VM traps.
This prevents:
* use-after-free
* stale references
---
## 6 Object Layout
Heap objects have a simple, fixed layout:
```
Object {
type_id
field_0
field_1
...
}
```
Properties:
* Fields are stored in slot order.
* No hidden base classes.
* No pointer arithmetic.
Traits and method dispatch are resolved:
* statically by the compiler, or
* via vtable handles (if dynamic dispatch is used).
---
## 7 Closures
Closures are heap objects.
Layout:
```
Closure {
func_id
capture_count
captures[]
}
```
Captures may be:
* copied values
* handles to heap objects
Closure environments are part of the GC root set.
---
## 8 Coroutine Memory
Each coroutine owns its own stacks:
```
Coroutine {
call_stack
operand_stack
state
}
```
All coroutine stacks are included in the GC root set.
Coroutines do not share stacks or frames.
---
## 9 Garbage Collection
The PVM uses a **mark-sweep collector**.
### GC properties
* Non-moving (no compaction in v1).
* Runs only at **safepoints**.
* Primary safepoint: `FRAME_SYNC`.
### GC triggers
GC may run when:
* heap usage exceeds threshold
* allocation pressure is high
### Root set
The collector marks from:
* operand stack
* call stack frames
* global variables
* coroutine stacks
* closure environments
* host-held handles
---
## 10 Allocation and Deallocation
### Allocation
Heap allocation:
1. VM reserves a slot block.
2. A gate entry is created.
3. A handle is returned.
If allocation fails:
* VM may trigger GC.
* If still failing, a trap occurs.
### Deallocation
Objects are freed only by the GC.
When freed:
* gate is marked dead
* generation is incremented
* memory becomes available via free list
---
## 11 Host-Owned Memory (Asset Banks)
Asset memory is **not part of the VM heap**.
It is managed by the firmware.
Examples:
* tilebanks
* audio sample banks
* sprite sheets
### Properties
* VM cannot access asset memory directly.
* Access occurs only through syscalls.
* Asset memory is not scanned by GC.
---
## 12 Save Memory (MEMCARD)
Save memory is a host-managed persistent storage area.
Properties:
* fixed size
* accessed only via syscalls
* not part of the VM heap
* not scanned by GC
---
## 13 Memory Safety Rules
The VM enforces:
1. All heap access via handles.
2. Generation checks on every handle use.
3. Bounds checking on object fields.
4. No raw pointer arithmetic.
5. Verified stack discipline.
Any violation results in a trap.
---
## 14 Summary
The PVM memory model is based on:
* stack-only primitive and tuple values
* heap-only user objects
* generation-based handles
* deterministic GC at frame safepoints
* strict separation between VM heap and host memory
This design ensures:
* predictable performance
* memory safety
* simple verification
* suitability for real-time game workloads.
< [Back](chapter-2.md) | [Summary](table-of-contents.md) | [Next](chapter-4.md) >

View File

@ -0,0 +1,600 @@
< [Back](chapter-3.md) | [Summary](table-of-contents.md) | [Next](chapter-5.md) >
# 🎨 **GFX Peripheral (Graphics System)**
## 1. Overview
The **GFX** peripheral is responsible for generating images in PROMETEU.
It models **simple graphics hardware**, inspired by classic consoles
(SNES, CPS-2, Neo-Geo), prioritizing:
- determinism
- low computational cost
- didactics
- portability
The GFX **is not a modern GPU**.
It is an explicit device, based on:
- framebuffer
- tilemaps
- tile banks
- priority-based sprites
- composition by drawing order
---
## 2. Resolution and Framebuffer
### Base resolution
- **320 × 180 pixels**
- aspect ratio close to 16:9
- scalable by the host (nearest-neighbor)
### Pixel format
- **RGB565**
- 5 bits Red
- 6 bits Green
- 5 bits Blue
- no alpha channel
Transparency is handled via **color key**.
---
## 3. Double Buffering
The GFX maintains two buffers:
- **Back Buffer** — where the frame is built
- **Front Buffer** — where the frame is displayed
Per-frame flow:
1. The system draws to the back buffer
2. Calls `present()`
3. Buffers are swapped
4. The host displays the front buffer
This guarantees:
- no tearing
- clear per-frame synchronization
- deterministic behavior
---
## 4. PROMETEU Graphical Structure
The graphical world is composed of:
- Up to **16 Tile Banks**
- **4 Tile Layers** (scrollable)
- **1 HUD Layer** (fixed, always on top)
- Sprites with priority between layers
### 4.1 Tile Banks
- There are up to **16 banks**
- Each bank has a fixed tile size:
- 8×8, 16×16, or 32×32
- A bank is a graphics library:
- environment
- characters
- UI
- effects
### 4.2 Layers
- There are:
- 4 Tile Layers
- 1 HUD Layer
- Each layer points to **a single bank**
- Sprites can use **any bank**
- HUD:
- does not scroll
- maximum priority
- generally uses 8×8 tiles
---
## 5. Internal Model of a Tile Layer
A Tile Layer **is not a bitmap of pixels**.
It is composed of:
- A **logical Tilemap** (tile indices)
- A **Border Cache** (window of visible tiles)
- A **Scroll Offset**
### Structure:
- `bank_id`
- `tile_size`
- `tilemap` (large matrix)
- `scroll_x`, `scroll_y`
- `cache_origin_x`, `cache_origin_y`
- `cache_tiles[w][h]`
---
## 6. Logical Tilemap
The tilemap represents the world:
Each cell contains:
- `tile_id`
- `flip_x`
- `flip_y`
- `priority` (optional)
- `palette_id` (optional)
The tilemap can be much larger than the screen.
---
## 7. Border Cache (Tile Cache)
The cache is a window of tiles around the camera.
Example:
- Screen: 320×180
- 16×16 tiles → 20×12 visible
- Cache: 22×14 (1-tile margin)
It stores tiles already resolved from the tilemap.
---
## 8. Cache Update
Every frame:
1. Calculate:
- `tile_x = scroll_x / tile_size`
- `tile_y = scroll_y / tile_size`
- `offset_x = scroll_x % tile_size`
- `offset_y = scroll_y % tile_size`
2. If `tile_x` changed:
- Advance `cache_origin_x`
- Reload only the new column
3. If `tile_y` changed:
- Advance `cache_origin_y`
- Reload only the new line
Only **one row and/or column** is updated per frame.
---
## 9. Cache as Ring Buffer
The cache is circular:
- Does not physically move data
- Only moves logical indices
Access:
- `real_x = (cache_origin_x + logical_x) % cache_width`
- `real_y = (cache_origin_y + logical_y) % cache_height`
---
## 10. Projection to the Back Buffer
For each frame:
1. For each Tile Layer, in order:
- Rasterize visible tiles from the cache
- Apply scroll, flip, and transparency
- Write to the back buffer
2. Draw sprites:
- With priority between layers
- Drawing order defines depth
3. Draw HUD layer last
---
## 11. Drawing Order and Priority
- There is no Z-buffer
- There is no automatic sorting
- Whoever draws later is in front
Base order:
1. Tile Layer 0
2. Tile Layer 1
3. Tile Layer 2
4. Tile Layer 3
5. Sprites (by priority between layers)
6. HUD Layer
---
## 12. Transparency (Color Key)
- One RGB565 value is reserved as TRANSPARENT_KEY
- Pixels with this color are not drawn
```
if src == TRANSPARENT_KEY:
skip
else:
draw
```
---
## 13. Color Math (Discrete Blending)
Inspired by the SNES.
Official modes:
- `BLEND_NONE`
- `BLEND_HALF`
- `BLEND_HALF_PLUS`
- `BLEND_HALF_MINUS`
- `BLEND_FULL`
No continuous alpha.
No arbitrary blending.
Everything is:
- integer
- cheap
- deterministic
---
## 14. Where Blend is Applied
- Blending occurs during drawing
- The result goes directly to the back buffer
- There is no automatic post-composition
---
## 15. What the GFX DOES NOT support
By design:
- Continuous alpha
- RGBA framebuffer
- Shaders
- Modern GPU pipeline
- HDR
- Gamma correction
---
## 16. Performance Rule
- Layers:
- only update the border when crossing a tile
- never redraw the entire world
- Rasterization:
- always per frame, only the visible area
- Sprites:
- always redrawn per frame
---
## 17. Special PostFX — Fade (Scene and HUD)
PROMETEU supports **gradual fade** as a special PostFX, with two independent
controls:
- **Scene Fade**: affects the entire scene (Tile Layers 03 + Sprites)
- **HUD Fade**: affects only the HUD Layer (always composed last)
The fade is implemented without continuous per-pixel alpha and without floats.
It uses a **discrete integer level** (0..31), which in practice produces an
"almost continuous" visual result in 320×180 pixel art.
---
### 17.1 Fade Representation
Each fade is represented by:
- `fade_level: u8` in the range **[0..31]**
- `0` → fully replaced by the fade color
- `31` → fully visible (no fade)
- `fade_color: RGB565`
- color the image will be blended into
Registers:
- `SCENE_FADE_LEVEL` (0..31)
- `SCENE_FADE_COLOR` (RGB565)
- `HUD_FADE_LEVEL` (0..31)
- `HUD_FADE_COLOR` (RGB565)
Common cases:
- Fade-out: `fade_color = BLACK`
- Flash/teleport: `fade_color = WHITE`
- Special effects: any RGB565 color
---
### 17.2 Fade Operation (Blending with Arbitrary Color)
For each RGB565 pixel `src` and fade color `fc`, the final pixel `dst` is calculated per channel.
1) Extract components:
- `src_r5`, `src_g6`, `src_b5`
- `fc_r5`, `fc_g6`, `fc_b5`
2) Apply integer blending:
```
src_weight = fade_level // 0..31
fc_weight = 31 - fade_level
r5 = (src_r5 * src_weight + fc_r5 * fc_weight) / 31
g6 = (src_g6 * src_weight + fc_g6 * fc_weight) / 31
b5 = (src_b5 * src_weight + fc_b5 * fc_weight) / 31
```
- `src_r5`, `src_g6`, `src_b5`
- `fc_r5`, `fc_g6`, `fc_b5`
3) Repack:
```
dst = pack_rgb565(r5, g6, b5)
```
Notes:
- Deterministic operation
- Integers only
- Can be optimized via LUT
---
### 17.3 Order of Application in the Frame
The frame composition follows this order:
1. Rasterize **Tile Layers 03** → Back Buffer
2. Rasterize **Sprites** according to priority
3. (Optional) Extra pipeline (Emission/Light/Glow etc.)
4. Apply **Scene Fade** using:
- `SCENE_FADE_LEVEL`
- `SCENE_FADE_COLOR`
5. Rasterize **HUD Layer**
6. Apply **HUD Fade** using:
- `HUD_FADE_LEVEL`
- `HUD_FADE_COLOR`
7. `present()`
Rules:
- Scene Fade never affects the HUD
- HUD Fade never affects the scene
---
### 17.4 Use Cases
- HUD Switch:
- decrease `HUD_FADE_LEVEL` to 0
- switch HUD/tilemap
- increase `HUD_FADE_LEVEL` to 31
- Area Switch:
- decrease `SCENE_FADE_LEVEL` to 0
- switch scenery
- increase `SCENE_FADE_LEVEL` to 31
- Flash / damage / teleport:
- use `fade_color = WHITE` or another thematic color
---
## 18. Palette System
### 18.1. Overview
PROMETEU uses **exclusively** palette-indexed graphics.
There is no direct RGB-per-pixel mode.
Every graphical pixel is an **index** pointing to a real color in a palette.
Objectives:
- reduce RAM and storage usage
- allow color swapping without shaders
- maintain retro identity
- facilitate effects like variation, damage, day/night
---
### 18.2. Pixel Format
Each pixel of a tile or sprite is:
- **4 bits per pixel (4bpp)**
- values: `0..15`
Fixed rule:
- Index `0` = TRANSPARENT
- Indices `1..15` = valid palette colors
---
### 18.3. Palette Structure
Each **Tile Bank** contains:
- Up to **256 palettes**
- Each palette has:
- **16 colors**
- each color in **RGB565 (u16)**
Size:
- 1 palette = 16 × 2 bytes = **32 bytes**
- 256 palettes = **8 KB per bank**
- 16 banks = **128 KB maximum palettes**
---
### 18.4. Palette Association
#### Fundamental Rule
- Each **tile** uses **a single palette**
- Each **sprite** uses **a single palette**
- The palette must be provided **explicitly** in every draw
There is no palette swap within the same tile or sprite.
---
### 18.5. Where the Palette is Defined
#### Tilemap
Each tilemap cell contains:
- `tile_id`
- `palette_id (u8)`
- `flip_x`
- `flip_y`
#### Sprite
Each sprite draw contains:
- `bank_id`
- `tile_id`
- `palette_id (u8)`
- `x`, `y`
- `flip_x`, `flip_y`
- `priority`
---
### 18.6. Color Resolution
The pipeline works like this:
1. Read indexed pixel from tile (value 0..15)
2. If index == 0 → transparent pixel
3. Otherwise:
- real_color = palette[palette_id][index]
4. Apply:
- flip
- discrete blend
- writing to back buffer
In other words:
```
pixel_index = tile_pixel(x,y)
if pixel_index == 0:
skip
else:
color = bank.palettes[palette_id][pixel_index]
draw(color)
```
---
### 18.7. Organization of Tile Banks
Tile Banks are "strong assets":
- Tiles and palettes live together
- Export/import always carries:
- tiles + palettes
- The hardware does not impose semantic organization:
- grouping is the creator's decision
- Tooling and scripts can create conventions:
- e.g.: palettes 0..15 = enemies
- 16..31 = scenery
- etc.
---
### 18.8. Possible Effects with Palettes
Without shaders, it is possible to:
- Palette swap:
- enemies with color variation
- States:
- damage, ice, poison, power-up
- Day / night:
- swap palettes globally
- Biomes:
- same art, different climate
- UI themes
All this without changing tiles.
---
### 18.9. Artistic Limitations
- Each tile/sprite:
- maximum of 16 colors
- Smooth gradients require:
- dithering
- discrete blend
- glow/emission
This limitation is intentional and part of the PROMETEU identity.
---
### 18.10. Metrics for Certification (CAP)
The system can measure:
- `palettes_loaded_total`
- `palettes_referenced_this_frame`
- `tiles_drawn_by_palette_id`
- `sprites_drawn_by_palette_id`
This allows:
- analyzing artistic cost
- teaching the impact of excessive variety
- suggesting best practices for visual cohesion
---
## 19. Summary
PROMETEU's GFX is simple **by choice**, not by limitation.
- RGB565 Framebuffer with double buffer
- Color key for transparency
- SNES-style discrete blending
- Up to 16 tile banks
- 4 Tile Layers + 1 HUD
- Layer = tilemap + cache + scroll
- Rasterized projection per frame
- Depth defined by drawing order
< [Back](chapter-3.md) | [Summary](table-of-contents.md) | [Next](chapter-5.md) >

View File

@ -0,0 +1,331 @@
< [Back](chapter-4.md) | [Summary](table-of-contents.md) | [Next](chapter-6.md) >
# 🔊 AUDIO Peripheral (Audio System)
## 1. Overview
The **AUDIO Peripheral** is responsible for **sound generation and mixing** in PROMETEU.
Just like the other subsystems:
- it is not automatic
- it is not free
- it is not magic
Each sound is the result of **explicit commands**, executed under a **time and resource budget**.
> Sound consumes time.
> Sound consumes memory.
---
## 2. Philosophy
PROMETEU treats audio as:
- an **active peripheral**
- with **limited channels**
- **deterministic** behavior
- **explicit** control
Objective: architectural and didactic clarity, not absolute realism.
---
## 3. General Architecture
The audio system is composed of:
- **Voices (channels)** — independent players
- **Samples** — PCM data
- **Mixer** — summation of voices
- **Output** — PCM stereo buffer
Conceptual separation:
- Game sends **commands at 60Hz**
- Audio is **generated continuously** at 48kHz
---
## 4. Output Format
- Sample rate: **48,000 Hz**
- Format: **PCM16 stereo (signed i16)**
- Clipping: saturation/clamp
This format is compatible with:
- common I2S DACs
- HDMI audio
- USB audio
- DIY SBCs (Raspberry Pi, Orange Pi, etc.)
---
## 5. Voices (Channels)
### 5.1 Quantity
```
MAX_VOICES = 16
```
Each voice:
- plays **1 sample at a time**
- is independent
- is mixed into the final output
---
### 5.2 Voice State
Each voice maintains:
- `sample_id`
- `pos` (fractional position in the sample)
- `rate` (pitch)
- `volume` (0..255)
- `pan` (0..255, left→right)
- `loop_mode` (off / on)
- `loop_start`, `loop_end`
- `priority` (optional)
---
### 5.3 Voice Conflict
If all voices are occupied:
- an explicit policy is applied:
- `STEAL_OLDEST`
- `STEAL_QUIETEST`
- `STEAL_LOWEST_PRIORITY`
PROMETEU does not resolve this automatically without a defined rule.
---
## 6. Conceptual Model: “Audio CPU”
The PROMETEU AUDIO is conceived as an **independent peripheral**, just as classic consoles had:
- Main game CPU
- Dedicated sound CPU
In PROMETEU:
- The **logical core** assumes this conceptual separation
- The **host decides** how to implement it:
- same thread
- separate thread
- separate core
### 6.1 Hardware Metaphor
Conceptually:
```
[Game CPU] → sends 60Hz commands → [AUDIO Peripheral]
|
v
Voices + Mixer
|
v
PCM Output
```
### 6.2 Implementation is the Host's Role
The core:
- defines the **model**
- defines the **commands**
- defines the **limits**
The host:
- chooses:
- threads
- CPU affinity
- audio backend
- guarantees continuous delivery of buffers
Thus:
> PROMETEU models the hardware.
> The host decides how to physically realize it.
---
## 7. Samples
### 7.1 Format
PROMETEU samples:
- **PCM16 mono**
- own sample_rate (e.g., 22050, 44100, 48000)
- immutable data at runtime
Fields:
- `sample_rate`
- `frames_len`
- `loop_start`, `loop_end` (optional)
---
### 7.2 Usage
Example:
```
audio.play(sample_id, voice_id, volume, pan, pitch, priority)
```
Or:
```
audio.playAuto(sample_id, volume, pan, pitch, priority)
```
(uses stealing policy)
---
## 8. Pitch and Interpolation
- `rate = 1.0` → normal speed
- `rate > 1.0` → higher pitch
- `rate < 1.0` → lower pitch
As position becomes fractional:
- **linear interpolation** is used between two neighboring samples
---
## 9. Mixer
For each output frame (48kHz):
1. For each active voice:
- read sample at current position
- apply pitch
- apply volume
- apply pan → generates L/R
2. Sum all voices
3. Apply clamp
4. Write to the stereo buffer
Cost depends on:
- number of active voices
- use of interpolation
---
## 10. Synchronization with the Game
- Game runs at **60Hz**
- Audio generates data at **48kHz**
Every frame (60Hz):
- game sends commands:
- play
- stop
- set_volume
- set_pan
- set_pitch
The audio applies these commands and continues playing.
---
## 11. Basic Commands
Conceptual examples:
```
audio.play(sample, voice, volume, pan, pitch, priority)
audio.stop(voice)
audio.setVolume(voice, v)
audio.setPan(voice, p)
audio.setPitch(voice, p)
audio.isPlaying(voice)
```
---
## 12. Audio and CAP
Audio participates in the Execution CAP:
- mixing cost per frame
- cost per active voice
- command cost
Example:
```
Frame 1024:
voices_active: 9
mix_cycles: 410
audio_commands: 6
```
---
## 13. Best Practices
Recommended:
- reuse samples
- limit simultaneous voices
- treat sound as an event
- separate music and effects
Avoid:
- playing sound every frame
- abusing voices
- giant samples for simple effects
---
## 14. Historical Inspiration
The PROMETEU model is inspired by:
- NES: fixed channels
- SNES: sample playback + mixing
- CPS2: comfortable polyphony
- Neo Geo: heavy samples (not fully copied)
But abstracted for:
- clarity
- simplicity
- teaching
---
## 15. Summary
- Output: stereo PCM16 @ 48kHz
- 16 voices
- mono PCM16 samples
- Volume, pan, pitch
- Linear interpolation
- Explicit mixer
- "Audio CPU" concept
- Implementation is the host's role
< [Back](chapter-4.md) | [Summary](table-of-contents.md) | [Next](chapter-6.md) >

View File

@ -0,0 +1,260 @@
< [Back](chapter-5.md) | [Summary](table-of-contents.md) | [Next](chapter-7.md) >
# 🎮 **INPUT Peripheral (Input System)**
## 1. Overview
The **INPUT Peripheral** is responsible for **collecting and providing the state of controls** to the PROMETEU program.
In PROMETEU:
- input is **state**, not an implicit event
- input is **sampled over time**, not delivered asynchronously
- input has a **defined order, cost, and moment**
> Buttons do not “trigger events”.
> They change state.
>
---
## 2. Philosophy of the Input System
PROMETEU models input like classic hardware:
- the state of controls is read **once per frame**
- the program queries this state during `UPDATE`
- there are no asynchronous input callbacks
This model:
- is deterministic
- is simple to understand
- reflects microcontrollers and classic consoles
---
## 3. Input Devices
PROMETEU abstracts different devices into a common interface.
Typical devices:
- digital control (D-Pad + botões)
- keyboard (mapped to buttons)
- gamepad
- touch (mobile, mapped)
Regardless of the origin, PROMETEU exposes **the same logical model**.
---
## 4. State Model
### 4.1 Per-Frame State
For each frame, the system maintains:
- current state of buttons
- state from the previous frame
This allows querying:
- button pressed
- button released
- button held down
---
### 4.2 Query Types
Typical operations:
```
input.btn(id)// button is pressed
input.btnp(id)// button was pressed this frame
input.btnr(id)// button was released this frame
```
These functions:
- are purely advisory
- do not change state
- have an explicit cost
---
## 5. Sampling Moment
The input state is captured **at the beginning of each frame**, before the `UPDATE` phase.
Conceptual flow:
```
FRAME N:
SAMPLEINPUT
UPDATE
DRAW
AUDIO
SYNC
```
Throughout the entire frame:
- the input state is immutable
- repeated calls return the same value
> Input does not change in the middle of the frame.
>
---
## 6. Determinism and Reproducibility
The PROMETEU input model guarantees:
- same sequence of inputs
- same frames
- same results
This allows:
- execution playback
- deterministic replays
- reliable certification
Input can be:
- recorded
- played back
- artificially injected
---
## 7. Input and CAP
Input operations:
- consume few cycles
- participate in the per-frame budget
- appear in certification reports
Example:
```
Frame 18231:
input.btn():12cycles
```
Although cheap, input **is not free**.
---
## 8. Button Mapping
### 8.1 Logical Identifiers
PROMETEU defines logical button identifiers:
- `UP`, `DOWN`, `LEFT`, `RIGHT`
- `A`, `B`, `X`, `Y`
- `START`, `SELECT`
The physical mapping:
- depends on the platform
- is resolved outside the VM
- is transparent to the program
---
### 8.2 Portability
The same PROMETEU cartridge:
- runs on a keyboard
- runs on a gamepad
- runs on touch
Without any code changes.
---
## 9. Analog Input (Optional)
PROMETEU can expose analog axes explicitly:
```
input.axis(id)
```
Characteristics:
- normalized value (e.g.: -1.0 to 1.0)
- explicit cost
- per-frame update
Analog input:
- is not mandatory
- does not replace digital input
- must be used consciously
---
## 10. Input Best Practices
PROMETEU encourages:
- treating input as state
- querying input only in `UPDATE`
- separating input from heavy logic
- mapping actions, not keys
And discourages:
- logic dependent on excessive polling
- reading input in DRAW
- direct coupling to physical hardware
---
## 11. Relationship with Classic Consoles
The PROMETEU model reflects:
- reading input registers
- per-frame polling
- absence of asynchronous events
This model:
- simplifies reasoning
- increases predictability
- facilitates debugging
---
## 12. Pedagogical Implications
The INPUT Peripheral allows teaching:
- the difference between event and state
- temporal sampling
- determinism in interactive systems
- synchronization between input and logic
With clear and reproducible feedback.
---
## 13. Summary
- input is state, not an event
- sampled once per frame
- immutable during the frame
- queries have an explicit cost
- input participates in the CAP
- model is deterministic
< [Back](chapter-5.md) | [Summary](table-of-contents.md) | [Next](chapter-7.md) >

View File

@ -0,0 +1,262 @@
< [Back](chapter-6.md) | [Summary](table-of-contents.md) | [Next](chapter-8.md) >
# 🖐️ TOUCH Peripheral (Absolute Pointer Input System)
## 1. Overview
The **TOUCH** peripheral provides PROMETEU with an **absolute pointer**, based on screen coordinates, intended for:
- UI interaction
- direct element selection
- contextual actions in the scenery
- drag-based mechanics (drag, slash, trail)
The TOUCH is a **first-class peripheral**, as valid as D-Pad and buttons.
---
## 2. Design Principles
TOUCH in PROMETEU follows strict principles:
- ✅ **Universal single-touch**
- ✅ **Deterministic**
- ✅ **Per-frame state**
- ✅ **No gestures**
- ✅ **No acceleration**
- ✅ **No heuristics**
- ✅ **Same behavior on all platforms**
> If a behavior cannot be guaranteed on all platforms, it does not exist in PROMETEU.
>
---
## 3. Conceptual Model
PROMETEU exposes **only one active pointer at a time**, regardless of how many physical touches the hardware recognizes.
- Hardware may detect multitouch
- Runtime selects **only one active touch**
- The API **never exposes multitouch**
This model guarantees:
- total portability
- predictability
- absence of ambiguities
---
## 4. Coordinate Space
- TOUCH coordinates use **the same space as the framebuffer**
- Resolution: **320×180**
- Origin: top-left corner `(0,0)`
- Ranges:
- `x ∈ [0, 319]`
- `y ∈ [0, 179]`
TOUCH is **absolute**:
> (x, y) represents the exact position of contact, without dynamic transformation.
>
---
## 5. TOUCH Peripheral API
### 5.1 Exposed Structure
```
TOUCH:
present : bool
down : bool
pressed : bool
released : bool
x : int
y : int
```
---
### 5.2 Field Semantics
- **present**
- `true` if the TOUCH peripheral is available
- `false` if there is no physical touch (desktop, hardware without touch)
- **down**
- `true` while the active pointer is pressed
- **pressed**
- `true` only in the frame where the active pointer was captured
- **released**
- `true` only in the frame where the active pointer was released
- **x, y**
- current position of the active pointer
- valid only when `down == true`
---
## 6. Pointer Selection Policy
### *Single Pointer Capture Policy*
When multiple physical touches occur, PROMETEU applies the following policy:
---
### 6.1 Initial Capture
1. If **no pointer is active**
2. And a **new physical touch** occurs
3. The **first detected touch** is captured
4. This touch becomes the **active pointer**
This frame generates:
- `pressed = true`
- `down = true`
---
### 6.2 Capture Maintenance
While the active pointer is pressed:
- Only it is tracked
- All other physical touches are ignored
- `x, y` follow only the active pointer
---
### 6.3 Release
When the active pointer is released:
- `released = true`
- `down = false`
- The system enters a **no active pointer** state
---
### 6.4 Recapture (Important Rule)
After release:
- Touches that **were already present** are ignored
- A new pointer is only captured with a **new touch event**
> This avoids unexpected pointer jumps and accidental actions.
>
---
## 7. Deliberately NOT Supported Behaviors
The TOUCH peripheral **does not implement**:
❌ Multitouch
❌ Gestures (swipe, pinch, rotate, long-press)
❌ Acceleration or smoothing
❌ Dynamic sensitivity
❌ Implicit history
❌ Intent interpretation
If a game wants any of these behaviors, it must:
- implement them explicitly
- using only per-frame state
- without implicit hardware support
---
## 8. “No Gesture” — Formal Definition
PROMETEU **does not interpret temporal patterns**.
The TOUCH peripheral **does not classify actions** as:
- swipe
- drag
- long press
- double tap
It only reports:
- current position
- contact state
All semantics are the game's responsibility.
---
## 9. “No Acceleration” — Formal Definition
PROMETEU **does not modify** the TOUCH input.
- No sensitivity curves
- No speed-based amplification
- No smoothing
The relationship between the physical touch and `(x, y)` is **1:1** after normalization.
---
## 10. Integration with Other Input Forms
- Desktop:
- mouse can emulate TOUCH
- Mobile:
- direct physical touch
- Steam Deck:
- physical touchscreen
- PROMETEU Hardware:
- optional touch, but supported
From PROMETEU's point of view:
> TOUCH is always TOUCH.
>
---
## 11. Expected Uses
The TOUCH peripheral is suitable for:
- UI (menus, inventory, maps)
- drag-and-drop
- direct selection
- “click to investigate”
- pointing-based puzzles
- trail mechanics (e.g.: Fruit Ninja-like)
---
## 12. Portability Guarantees
Every PROMETEU game that uses TOUCH:
- behaves identically on all platforms
- does not depend on host-specific capabilities
- does not suffer semantic variation between desktop, mobile, and dedicated hardware
---
## 13. Summary
The TOUCH in PROMETEU is:
- simple
- explicit
- predictable
- universal
- deterministic
< [Back](chapter-6.md) | [Summary](table-of-contents.md) | [Next](chapter-8.md) >

View File

@ -0,0 +1,238 @@
< [Back](chapter-7.md) | [Summary](table-of-contents.md) | [Next](chapter-9.md) >
# 📀 MEMCARD Peripheral (Save/Load System)
## 1. Overview
The **MEMCARD** is the peripheral responsible for the **explicit persistence of game data** in PROMETEU.
It simulates the behavior of classic *memory cards* (PS1, GameCube), providing:
- limited storage
- explicit I/O cost
- full game control over when to save
- portability across platforms
The MEMCARD **is not a save state**.
It represents **data that the game itself decides to persist**.
---
## 2. Design Principles
The MEMCARD peripheral follows these principles:
- ✅ **Explicit persistence** (nothing automatic)
- ✅ **Limited and known size**
- ✅ **Mandatory commit**
- ✅ **Measurable time cost (cycles)**
- ✅ **Stable and documented format**
- ✅ **Platform independent**
- ❌ No complex file system (in v0.1)
- ❌ No multiple internal files (in v0.1)
---
## 3. Conceptual Model
Each PROMETEU cartridge can access **one or more MEMCARD slots**, the default model being:
- **Slot A** — main
- **Slot B** — optional (future)
Each slot corresponds to **a file on the host**:
```
MyGame_A.mem
MyGame_B.mem
```
The runtime mounts this file as a **persistent storage device**.
---
## 4. Capacity and CAP
The size of the MEMCARD is **fixed**, defined by the execution profile (CAP).
### Suggested sizes
| Profile | Size |
| --- | --- |
| JAM | 8 KB |
| STANDARD | 32 KB |
| ADVANCED | 128 KB |
The game **cannot exceed** this size.
Attempts to write above the limit result in an error.
---
## 5. Peripheral API (v0.1)
### 5.1 Logical Interface
The MEMCARD exposes a **simple single-blob API**:
```
mem.read_all() -> byte[]
mem.write_all(byte[])
mem.commit()
mem.clear()
mem.size() -> int
```
---
### 5.2 Operation Semantics
### `read_all()`
- Returns all persisted content
- If the card is empty, returns a zeroed buffer
- Cycle cost proportional to size
---
### `write_all(bytes)`
- Writes the buffer **to temporary memory**
- Does not persist immediately
- Fails if `bytes.length > mem.size()`
---
### `commit()`
- Persists data to the device
- **Mandatory** operation
- Simulates hardware flush
- May fail (e.g., I/O, simulated corruption)
---
### `clear()`
- Zeroes the card content
- Requires `commit()` to persist
---
### `size()`
- Returns total card capacity in bytes
---
## 6. Explicit Commit (Fundamental Rule)
PROMETEU **does not save automatically**.
Without `commit()`:
- data remains volatile
- can be lost upon exiting the game
- simulates abrupt hardware shutdown
👉 This teaches:
- data flushing
- atomicity
- risk of corruption
- real cost of persistence
---
## 7. Execution Cost (Cycles)
All MEMCARD operations have an explicit cost.
### Example (illustrative values)
| Operation | Cost |
| --- | --- |
| read_all | 1 cycle / 256 bytes |
| write_all | 1 cycle / 256 bytes |
| commit | fixed + proportional cost |
These costs appear:
- in the profiler
- in the frame timeline
- in the CAP report
---
## 8. `.mem` File Format
The MEMCARD file has a simple and robust format.
### 8.1 Header
| Field | Size |
| --- | --- |
| Magic (`PMEM`) | 4 bytes |
| Version | 1 byte |
| Cart ID | 8 bytes |
| Payload Size | 4 bytes |
| CRC32 | 4 bytes |
---
### 8.2 Payload
- Binary buffer defined by the game
- Fixed size
- Content interpreted only by the game
---
## 9. Integrity and Security
- CRC validates corruption
- Cart ID prevents using wrong save
- Version allows future format evolution
- Runtime can:
- warn of corruption
- allow card reset
---
## 10. Integration with the Editor / GUI
The main tool can provide a **Memory Card Manager**:
- create/reset card
- see size and usage
- import/export `.mem`
- visualize last commits
- associate cards with projects
None of these operations change the runtime.
---
## 11. Planned Evolutions (outside v0.1)
- Block API (`read_block`, `write_block`)
- multiple internal slots
- wear simulation
- save versioning
- optional encryption (educational)
---
## 12. Summary
The MEMCARD peripheral in PROMETEU:
- simulates real hardware
- forces design decisions
- teaches persistence correctly
- is simple to use
- is hard to abuse
- grows without breaking compatibility
< [Back](chapter-7.md) | [Summary](table-of-contents.md) | [Next](chapter-9.md) >

View File

@ -0,0 +1,308 @@
< [Back](chapter-8.md) | [Summary](table-of-contents.md) | [Next](chapter-10.md) >
# ⚡ **Events and Scheduling**
This chapter defines how the Prometeu Virtual Machine (PVM) handles events, frame synchronization, and cooperative concurrency. It replaces the older interrupt-oriented terminology with a simpler and more accurate model based on **frame boundaries**, **event queues**, and **coroutines**.
The goal is to preserve determinism while still allowing responsive and structured game logic.
---
## 1 Core Philosophy
Prometeu does **not use asynchronous callbacks** or preemptive interrupts for user code.
All external signals are:
* queued by the firmware
* delivered at deterministic points
* processed inside the main execution loop
Nothing executes “out of time”.
Nothing interrupts the program in the middle of an instruction.
Nothing occurs without a known cost.
This guarantees:
* deterministic behavior
* reproducible runs
* stable frame timing
---
## 2 Events
### 2.1 Definition
An **event** is a logical signal generated by the system or by internal runtime mechanisms.
Events:
* represent something that has occurred
* do not execute code automatically
* are processed explicitly during the frame
Examples:
* end of frame
* timer expiration
* asset load completion
* system state change
* execution error
---
### 2.2 Event Queue
The firmware maintains an **event queue**.
Properties:
* events are queued in order
* events are processed at frame boundaries
* event processing is deterministic
Events never:
* execute user code automatically
* interrupt instructions
* run outside the main loop
---
## 3 Frame Boundary (Sync Phase)
The primary synchronization point in Prometeu is the **frame boundary**, reached by the `FRAME_SYNC` instruction.
At this point:
1. Input state is sampled.
2. Events are delivered.
3. Coroutine scheduling occurs.
4. Optional garbage collection may run.
5. Control returns to the firmware.
This replaces the older notion of a "VBlank interrupt".
The frame boundary:
* has a fixed, measurable cost
* does not execute arbitrary user code
* is fully deterministic
---
## 4 System Events vs System Faults
Prometeu distinguishes between normal events and system faults.
### 4.1 Normal events
Examples:
* timer expired
* asset loaded
* frame boundary
These are delivered through the event queue.
### 4.2 System faults
Generated by exceptional conditions:
* invalid instruction
* memory violation
* handle misuse
* verifier failure
Result:
* VM execution stops
* state is preserved
* a diagnostic report is generated
System faults are not recoverable events.
---
## 5 Timers
Timers are modeled as **frame-based counters**.
Properties:
* measured in frames, not real time
* deterministic across runs
* generate events when they expire
Timers:
* do not execute code automatically
* produce queryable or queued events
Example:
```
if timer.expired(t1) {
// handle event
}
```
---
## 6 Coroutines and Cooperative Scheduling
Prometeu provides **coroutines** as the only form of concurrency.
Coroutines are:
* cooperative
* deterministic
* scheduled only at safe points
There is:
* no preemption
* no parallel execution
* no hidden threads
### 6.1 Coroutine lifecycle
Each coroutine can be in one of the following states:
* `Ready`
* `Running`
* `Sleeping`
* `Finished`
### 6.2 Scheduling
At each frame boundary:
1. The scheduler selects the next coroutine.
2. Coroutines run in a deterministic order.
3. Each coroutine executes within the frame budget.
The default scheduling policy is:
* round-robin
* deterministic
---
### 6.3 Coroutine operations
Typical coroutine instructions:
| Operation | Description |
| --------- | ----------------------------- |
| `spawn` | Create a coroutine |
| `yield` | Voluntarily suspend execution |
| `sleep` | Suspend for N frames |
`yield` and `sleep` only take effect at safe points.
---
## 7 Relationship Between Events, Coroutines, and the Frame Loop
The high-level frame structure is:
```
FRAME N
------------------------
Sample Input
Deliver Events
Schedule Coroutines
Run VM until:
- budget exhausted, or
- FRAME_SYNC reached
Sync Phase
------------------------
```
Important properties:
* events are processed at known points
* coroutine scheduling is deterministic
* no execution occurs outside the frame loop
---
## 8 Costs and Budget
All event processing and scheduling:
* consumes cycles
* contributes to the CAP (certification and analysis profile)
* appears in profiling reports
Example:
```
Frame 18231:
Event processing: 120 cycles
Coroutine scheduling: 40 cycles
Frame sync: 80 cycles
```
Nothing is free.
---
## 9 Determinism and Reproducibility
Prometeu guarantees:
* same sequence of inputs and events → same behavior
* frame-based timers
* deterministic coroutine scheduling
This allows:
* reliable replays
* precise debugging
* fair certification
---
## 10 Best Practices
Prometeu encourages:
* treating events as data
* querying events explicitly
* structuring logic around frame steps
* using coroutines for asynchronous flows
Prometeu discourages:
* simulating asynchronous callbacks
* relying on hidden timing
* using events as implicit control flow
---
## 11 Conceptual Comparison
| Traditional System | Prometeu |
| ------------------ | ----------------------- |
| Hardware interrupt | Frame boundary event |
| ISR | System sync phase |
| Main loop | VM frame loop |
| Timer interrupt | Frame-based timer event |
| Threads | Coroutines |
Prometeu teaches reactive system concepts without the unpredictability of real interrupts.
---
## 12 Summary
* Events inform; they do not execute code.
* The frame boundary is the only global synchronization point.
* System faults stop execution.
* Coroutines provide cooperative concurrency.
* All behavior is deterministic and measurable.
< [Back](chapter-8.md) | [Summary](table-of-contents.md) | [Next](chapter-10.md) >

View File

@ -0,0 +1,21 @@
# Table of Contents
- [Chapter 1: Time Model and Cycles](chapter-1.md)
- [Chapter 2: PROMETEU VM Instruction Set](chapter-2.md)
- [Chapter 3: Memory: Stack, Heap, and Allocation](chapter-3.md)
- [Chapter 4: GFX Peripheral (Graphics System)](chapter-4.md)
- [Chapter 5: AUDIO Peripheral (Audio System)](chapter-5.md)
- [Chapter 6: INPUT Peripheral (Input System)](chapter-6.md)
- [Chapter 7: TOUCH Peripheral (Absolute Pointer Input System)](chapter-7.md)
- [Chapter 8: MEMCARD Peripheral (Save/Load System)](chapter-8.md)
- [Chapter 9: Events and Interrupts](chapter-9.md)
- [Chapter 10: Debug, Inspection, and Profiling](chapter-10.md)
- [Chapter 11: Portability Guarantees and Cross-Platform Execution](chapter-11.md)
- [Chapter 12: Firmware — PrometeuOS (POS) + PrometeuHub](chapter-12.md)
- [Chapter 13: Cartridge](chapter-13.md)
- [Chapter 14: Boot Profiles](chapter-14.md)
- [Chapter 15: Asset Management](chapter-15.md)
- [Chapter 16: Host ABI and Syscalls](chapter-16.md)
---
[Back to README](../README.md)

View File

@ -0,0 +1,373 @@
# PBS - Language Syntax Specification v0
Status: Draft (Normative for FE rewrite)
Scope: PBS v0 Core syntax (lexer + parser contract)
Language: English only
## 1. Goals
This document defines the canonical PBS v0 Core syntax.
It is designed to:
- be deterministic to parse,
- be stable for tests and tooling,
- align with runtime authority,
- provide a clear baseline for frontend rewrite.
This document defines syntax only. Runtime behavior, bytecode encoding, scheduling, and GC internals are out of scope.
## 2. Authority and precedence
Normative precedence order:
1. Runtime authority (`docs/specs/hardware/topics/chapter-2.md`, `chapter-3.md`, `chapter-9.md`, `chapter-12.md`, `chapter-16.md`)
2. Bytecode authority (`docs/specs/bytecode/ISA_CORE.md`)
3. This syntax specification
4. Legacy references (`docs/specs/pbs_old/*`)
If a syntax rule from legacy material conflicts with runtime or bytecode authority, that rule is invalid.
## 3. Source model
- Source encoding: UTF-8.
- Line terminators: `\n` and `\r\n` are both valid.
- Whitespace is insignificant except as separator.
- A source file is a declaration unit; top-level executable statements are forbidden.
## 4. Lexical specification
### 4.1 Tokens
The lexer must produce at least:
- identifiers,
- keywords,
- numeric literals,
- string literals,
- punctuation,
- operators,
- comments,
- EOF.
Each token must carry a source span (byte offsets).
### 4.2 Comments
- Line comment: `// ...` until line end.
- Block comments are not part of v0 Core.
### 4.3 Identifiers
Identifier:
- starts with `_` or alphabetic character,
- continues with `_`, alphabetic, or digit characters.
Keywords cannot be used as identifiers.
### 4.4 Keywords
Active keywords in `.pbs` files (v0 Core):
- `import`, `from`, `as`
- `service`, `fn`
- `declare`, `struct`, `contract`, `error`
- `let`, `mut`
- `if`, `else`, `when`, `for`, `in`, `return`
- `true`, `false`
Barrel-only keywords:
- `pub`, `mod`
- `type`
Reserved (not active in `.pbs` v0 Core grammar, but reserved):
- `host`, `handle`
- `alloc`, `borrow`, `mutate`, `peek`, `take`, `weak`
- `spawn`, `yield`, `sleep`
### 4.5 Literals
Numeric literals:
- `IntLit`: decimal integer (`0`, `42`, `1000`)
- `FloatLit`: decimal float with dot (`3.14`, `0.5`)
- `BoundedLit`: decimal integer with `b` suffix (`0b`, `255b`)
String literals:
- delimited by `"`.
- support escapes: `\\`, `\"`, `\n`, `\r`, `\t`.
Booleans:
- `true`, `false`.
## 5. Module and barrel model
### 5.1 Required files
A module is valid only if it contains:
- one or more `.pbs` source files,
- exactly one `mod.barrel` file.
Missing `mod.barrel` is a compile-time error.
### 5.2 Barrel responsibility
`mod.barrel` is the only place where module visibility is defined.
Visibility levels:
- `mod`: visible across files in the same module.
- `pub`: visible to other modules through imports.
Any top-level declaration not listed in `mod.barrel` is file-private.
Using `mod` or `pub` as top-level declaration modifiers in `.pbs` files is a syntax error.
### 5.3 Barrel grammar
```ebnf
BarrelFile ::= BarrelItem* EOF
BarrelItem ::= BarrelVisibility BarrelKind Identifier ';'
BarrelVisibility ::= 'mod' | 'pub'
BarrelKind ::= 'fn' | 'type' | 'service'
```
Rules:
- `mod.barrel` cannot declare aliases.
- Barrel item order has no semantic meaning.
- The same symbol cannot appear more than once in `mod.barrel`.
- Each barrel item must resolve to an existing top-level declaration in the module.
- Alias syntax is allowed only in `import` declarations.
- Importing modules may only import symbols marked as `pub` in the target module barrel.
Examples:
```barrel
mod fn clamp;
pub fn sum;
pub type Vector;
mod service Audio;
```
## 6. File and declaration grammar
EBNF conventions used:
- `A?` optional
- `A*` zero or more
- `A+` one or more
- terminals in single quotes
### 6.1 File
```ebnf
File ::= ImportDecl* TopDecl* EOF
```
### 6.2 Imports
Imports must target modules, never files.
```ebnf
ImportDecl ::= 'import' ( ModuleRef | '{' ImportList '}' 'from' ModuleRef ) ';'
ImportList ::= ImportItem (',' ImportItem)*
ImportItem ::= Identifier ('as' Identifier)?
ModuleRef ::= '@' Identifier ':' ModulePath
ModulePath ::= Identifier ('/' Identifier)*
```
Examples:
```pbs
import @core:math;
import { Vector, Matrix as Mat } from @core:math;
```
### 6.3 Top-level declarations
```ebnf
TopDecl ::= TypeDecl | ServiceDecl | FunctionDecl
```
Top-level `let` and top-level statements are not allowed.
### 6.4 Type declarations
```ebnf
TypeDecl ::= 'declare' TypeKind Identifier TypeBody
TypeKind ::= 'struct' | 'contract' | 'error'
TypeBody ::= '{' TypeMember* '}'
TypeMember ::= FieldDecl | FnSigDecl
FieldDecl ::= Identifier ':' TypeRef ';'
FnSigDecl ::= 'fn' Identifier ParamList ReturnType? ';'
```
### 6.5 Services
```ebnf
ServiceDecl ::= 'service' Identifier (':' Identifier)? ServiceBody
ServiceBody ::= '{' ServiceMember* '}'
ServiceMember ::= 'fn' Identifier ParamList ReturnType? Block
```
### 6.6 Functions
```ebnf
FunctionDecl ::= 'fn' Identifier ParamList ReturnType? ElseFallback? Block
ParamList ::= '(' Param (',' Param)* ')'
Param ::= 'mut'? Identifier ':' TypeRef
ReturnType ::= ':' TypeRef
ElseFallback ::= 'else' Expr
```
## 7. Type syntax
```ebnf
TypeRef ::= TypePrimary
TypePrimary ::= SimpleType | GenericType | TupleType
SimpleType ::= Identifier
GenericType ::= Identifier '<' TypeRef (',' TypeRef)* '>'
TupleType ::= '(' TypeRef ',' TypeRef (',' TypeRef){0,4} ')'
```
Runtime alignment:
- Tuple type arity in v0 Core is 2..6.
- This aligns with runtime multi-return slot limits.
## 8. Statements and blocks
```ebnf
Block ::= '{' Stmt* TailExpr? '}'
Stmt ::= LetStmt | ReturnStmt | IfStmt | ForStmt | ExprStmt
TailExpr ::= Expr
LetStmt ::= 'let' 'mut'? Identifier (':' TypeRef)? '=' Expr ';'
ReturnStmt ::= 'return' Expr? ';'
ExprStmt ::= Expr ';'
IfStmt ::= 'if' Expr Block ('else' (IfStmt | Block))?
ForStmt ::= 'for' Identifier 'in' RangeExpr Block
RangeExpr ::= '[' Expr? '..' Expr? ']'
```
Notes:
- `if` is a statement in v0 Core.
- `when` is an expression.
- `break` and `continue` are deferred from v0 Core syntax.
## 9. Expression grammar and precedence
Assignment is not an expression in v0 Core.
```ebnf
Expr ::= WhenExpr
WhenExpr ::= 'when' OrExpr 'then' Expr 'else' Expr | OrExpr
OrExpr ::= AndExpr ('||' AndExpr)*
AndExpr ::= EqualityExpr ('&&' EqualityExpr)*
EqualityExpr ::= CompareExpr (('==' | '!=') CompareExpr)?
CompareExpr ::= CastExpr (('<' | '<=' | '>' | '>=') CastExpr)?
CastExpr ::= AddExpr ('as' TypeRef)*
AddExpr ::= MulExpr (('+' | '-') MulExpr)*
MulExpr ::= UnaryExpr (('*' | '/' | '%') UnaryExpr)*
UnaryExpr ::= ('!' | '-') UnaryExpr | CallExpr
CallExpr ::= PrimaryExpr ('(' ArgList? ')')*
ArgList ::= Expr (',' Expr)*
Literal ::= IntLit | FloatLit | BoundedLit | StringLit | BoolLit
BoolLit ::= 'true' | 'false'
PrimaryExpr ::= Literal | Identifier | GroupExpr | Block
GroupExpr ::= '(' Expr ')'
```
Non-associative constraints:
- `a < b < c` is invalid.
- `a == b == c` is invalid.
## 10. Runtime authority alignment constraints
These are hard constraints for frontend and syntax decisions:
- Runtime is deterministic and frame-synchronized.
- `FRAME_SYNC` is runtime safepoint and not surface syntax.
- Heap semantics are GC-based at runtime authority level.
- Host interaction is via `SYSCALL` in bytecode and host ABI mapping.
- Syscalls are callable but not first-class.
Syntax implications for v0 Core:
- No RC/HIP/gate-specific syntax is active.
- No closure literal syntax in v0 Core.
- No coroutine syntax (`spawn`, `yield`, `sleep`) in v0 Core.
## 11. Deferred syntax (explicitly out of v0 Core)
Deferred for later profiles:
- heap-specialized syntax: `alloc`, `borrow`, `mutate`, `peek`, `take`, `weak`
- first-class closure/lambda surface syntax
- coroutine surface syntax
- pattern matching
- macro system
These words stay reserved so later profiles do not break source compatibility.
## 12. Conformance requirements for frontend rewrite
A v0 Core frontend is conformant if:
- tokenization follows this lexical spec,
- barrel parsing and validation follows Section 5,
- parsing follows this grammar and precedence,
- parser is deterministic,
- each token and AST node keeps stable spans,
- forbidden constructs produce deterministic diagnostics.
## 13. Minimal canonical examples
### 13.1 Function
```pbs
fn sum(a: int, b: int): int {
return a + b;
}
```
### 13.2 Local function + barrel visibility
```pbs
fn clamp(x: int, lo: int, hi: int): int {
if x < lo {
return lo;
}
if x > hi {
return hi;
}
return x;
}
```
```barrel
mod fn clamp;
```
### 13.3 Imports
```pbs
import @core:math;
import { Vec2 as V2 } from @core:math;
```

21
docs/specs/pbs/README.md Normal file
View File

@ -0,0 +1,21 @@
# PBS Specifications (Current)
This directory contains the current PBS specifications for frontend and language work.
## Authority model
- Runtime authority: `docs/specs/hardware/topics/chapter-2.md`, `chapter-3.md`, `chapter-9.md`, `chapter-12.md`, `chapter-16.md`.
- Bytecode authority: `docs/specs/bytecode/ISA_CORE.md`.
- Legacy reference only: `docs/specs/pbs_old/*`.
When a rule conflicts with runtime or bytecode authority, runtime and bytecode authority win.
## Purpose
- Define a stable syntax contract for PBS.
- Support a full frontend rewrite (lexer, parser, AST, lowering).
- Keep grammar deterministic and implementation-friendly.
## Files
- `PBS - Language Syntax Specification v0.md`: normative syntax and grammar for PBS v0 Core profile, including mandatory `mod.barrel` visibility rules.

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,321 @@
# 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.**

View File

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

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 ::= Visibility? 'fn' Identifier ParamList ReturnType? ElseFallback? Block
```
Toplevel `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 **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,175 @@
# 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.
---

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,359 @@
# 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<Vector> = 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<Vector> = box(Vector.ZERO);
let b: Box<Vector> = 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

View File

@ -0,0 +1,55 @@
package p.studio.compiler.pbs;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.IRFunction;
import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.lexer.PbsLexer;
import p.studio.compiler.pbs.parser.PbsParser;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public final class PbsFrontendCompiler {
public ReadOnlyList<IRFunction> compileFile(
final FileId fileId,
final String source,
final String sourceLabel,
final BuildingIssueSink issues) {
final var tokens = PbsLexer.lex(source, sourceLabel, issues);
final var ast = PbsParser.parse(tokens, fileId, sourceLabel, issues);
validateFunctionNames(ast, sourceLabel, issues);
return lowerFunctions(fileId, ast);
}
private void validateFunctionNames(
final PbsAst.File ast,
final String sourceLabel,
final BuildingIssueSink issues) {
final Set<String> names = new HashSet<>();
for (final var fn : ast.functions()) {
if (names.add(fn.name())) {
continue;
}
issues.report(builder -> builder
.error(true)
.message("[PBS:E_RESOLVE_DUPLICATE_SYMBOL] Duplicate function '%s' at %s:[%d,%d)"
.formatted(fn.name(), sourceLabel, fn.span().getStart(), fn.span().getEnd())));
}
}
private ReadOnlyList<IRFunction> lowerFunctions(final FileId fileId, final PbsAst.File ast) {
final var functions = new ArrayList<IRFunction>(ast.functions().size());
for (final var fn : ast.functions()) {
functions.add(new IRFunction(
fileId,
fn.name(),
fn.parameters().size(),
fn.returnType() != null,
fn.span()));
}
return ReadOnlyList.wrap(functions);
}
}

View File

@ -0,0 +1,129 @@
package p.studio.compiler.pbs.ast;
import p.studio.compiler.source.Span;
import p.studio.utilities.structures.ReadOnlyList;
public final class PbsAst {
private PbsAst() {
}
public record File(
ReadOnlyList<FunctionDecl> functions,
Span span) {
}
public record FunctionDecl(
String name,
ReadOnlyList<Parameter> parameters,
TypeRef returnType,
Expression elseFallback,
Block body,
Span span) {
}
public record Parameter(
String name,
boolean mutable,
TypeRef typeRef,
Span span) {
}
public record TypeRef(
String name,
Span span) {
}
public record Block(
ReadOnlyList<Statement> statements,
Span span) {
}
public sealed interface Statement permits LetStatement, ReturnStatement, ExpressionStatement {
Span span();
}
public record LetStatement(
String name,
boolean mutable,
TypeRef explicitType,
Expression initializer,
Span span) implements Statement {
}
public record ReturnStatement(
Expression value,
Span span) implements Statement {
}
public record ExpressionStatement(
Expression expression,
Span span) implements Statement {
}
public sealed interface Expression permits IdentifierExpr,
IntLiteralExpr,
FloatLiteralExpr,
BoundedLiteralExpr,
StringLiteralExpr,
BoolLiteralExpr,
UnaryExpr,
BinaryExpr,
CallExpr,
GroupExpr {
Span span();
}
public record IdentifierExpr(
String name,
Span span) implements Expression {
}
public record IntLiteralExpr(
long value,
Span span) implements Expression {
}
public record FloatLiteralExpr(
double value,
Span span) implements Expression {
}
public record BoundedLiteralExpr(
int value,
Span span) implements Expression {
}
public record StringLiteralExpr(
String value,
Span span) implements Expression {
}
public record BoolLiteralExpr(
boolean value,
Span span) implements Expression {
}
public record UnaryExpr(
String operator,
Expression expression,
Span span) implements Expression {
}
public record BinaryExpr(
String operator,
Expression left,
Expression right,
Span span) implements Expression {
}
public record CallExpr(
Expression callee,
ReadOnlyList<Expression> arguments,
Span span) implements Expression {
}
public record GroupExpr(
Expression expression,
Span span) implements Expression {
}
}

View File

@ -0,0 +1,242 @@
package p.studio.compiler.pbs.lexer;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public final class PbsLexer {
private static final Map<String, PbsTokenKind> KEYWORDS = buildKeywords();
private final String source;
private final String sourceLabel;
private final BuildingIssueSink issues;
private final ArrayList<PbsToken> tokens = new ArrayList<>();
private int start;
private int current;
private PbsLexer(final String source, final String sourceLabel, final BuildingIssueSink issues) {
this.source = source == null ? "" : source;
this.sourceLabel = sourceLabel == null ? "<unknown>" : sourceLabel;
this.issues = issues;
}
public static ReadOnlyList<PbsToken> lex(
final String source,
final String sourceLabel,
final BuildingIssueSink issues) {
final var lexer = new PbsLexer(source, sourceLabel, issues);
return lexer.lexInternal();
}
private ReadOnlyList<PbsToken> lexInternal() {
while (!isAtEnd()) {
start = current;
scanToken();
}
tokens.add(new PbsToken(PbsTokenKind.EOF, "", current, current));
return ReadOnlyList.wrap(tokens);
}
private void scanToken() {
final char c = advance();
switch (c) {
case ' ', '\r', '\t', '\n' -> {
// Deliberately ignored.
}
case '(' -> addToken(PbsTokenKind.LEFT_PAREN);
case ')' -> addToken(PbsTokenKind.RIGHT_PAREN);
case '{' -> addToken(PbsTokenKind.LEFT_BRACE);
case '}' -> addToken(PbsTokenKind.RIGHT_BRACE);
case '[' -> addToken(PbsTokenKind.LEFT_BRACKET);
case ']' -> addToken(PbsTokenKind.RIGHT_BRACKET);
case ',' -> addToken(PbsTokenKind.COMMA);
case ':' -> addToken(PbsTokenKind.COLON);
case ';' -> addToken(PbsTokenKind.SEMICOLON);
case '@' -> addToken(PbsTokenKind.AT);
case '+' -> addToken(PbsTokenKind.PLUS);
case '-' -> addToken(PbsTokenKind.MINUS);
case '*' -> addToken(PbsTokenKind.STAR);
case '%' -> addToken(PbsTokenKind.PERCENT);
case '.' -> {
if (match('.')) {
addToken(PbsTokenKind.DOT_DOT);
return;
}
report("E_LEX_INVALID_CHAR", "Unexpected '.'");
}
case '!' -> addToken(match('=') ? PbsTokenKind.BANG_EQUAL : PbsTokenKind.BANG);
case '=' -> addToken(match('=') ? PbsTokenKind.EQUAL_EQUAL : PbsTokenKind.EQUAL);
case '<' -> addToken(match('=') ? PbsTokenKind.LESS_EQUAL : PbsTokenKind.LESS);
case '>' -> addToken(match('=') ? PbsTokenKind.GREATER_EQUAL : PbsTokenKind.GREATER);
case '&' -> {
if (match('&')) {
addToken(PbsTokenKind.AND_AND);
return;
}
report("E_LEX_INVALID_CHAR", "Unexpected '&'");
}
case '|' -> {
if (match('|')) {
addToken(PbsTokenKind.OR_OR);
return;
}
report("E_LEX_INVALID_CHAR", "Unexpected '|'");
}
case '/' -> {
if (match('/')) {
// Line comment
while (!isAtEnd() && peek() != '\n') {
advance();
}
return;
}
addToken(PbsTokenKind.SLASH);
}
case '"' -> string();
default -> {
if (isDigit(c)) {
number();
return;
}
if (isIdentifierStart(c)) {
identifier();
return;
}
report("E_LEX_INVALID_CHAR", "Invalid character: '%s'".formatted(c));
}
}
}
private void identifier() {
while (!isAtEnd() && isIdentifierPart(peek())) {
advance();
}
final String text = source.substring(start, current);
final PbsTokenKind kind = KEYWORDS.getOrDefault(text, PbsTokenKind.IDENTIFIER);
addToken(kind);
}
private void number() {
while (!isAtEnd() && isDigit(peek())) {
advance();
}
var isFloat = false;
if (!isAtEnd() && peek() == '.' && peekNext() != '.') {
isFloat = true;
advance();
while (!isAtEnd() && isDigit(peek())) {
advance();
}
}
if (!isFloat && !isAtEnd() && peek() == 'b') {
advance();
addToken(PbsTokenKind.BOUNDED_LITERAL);
return;
}
addToken(isFloat ? PbsTokenKind.FLOAT_LITERAL : PbsTokenKind.INT_LITERAL);
}
private void string() {
while (!isAtEnd() && peek() != '"') {
if (peek() == '\\' && !isAtEnd()) {
advance();
if (!isAtEnd()) {
advance();
}
continue;
}
advance();
}
if (isAtEnd()) {
report("E_LEX_UNTERMINATED_STRING", "Unterminated string literal");
return;
}
// Closing quote.
advance();
addToken(PbsTokenKind.STRING_LITERAL);
}
private void addToken(final PbsTokenKind kind) {
final String lexeme = source.substring(start, current);
tokens.add(new PbsToken(kind, lexeme, start, current));
}
private boolean match(final char expected) {
if (isAtEnd()) return false;
if (source.charAt(current) != expected) return false;
current++;
return true;
}
private char advance() {
return source.charAt(current++);
}
private char peek() {
if (isAtEnd()) return '\0';
return source.charAt(current);
}
private char peekNext() {
if (current + 1 >= source.length()) return '\0';
return source.charAt(current + 1);
}
private boolean isAtEnd() {
return current >= source.length();
}
private boolean isDigit(final char c) {
return c >= '0' && c <= '9';
}
private boolean isIdentifierStart(final char c) {
return c == '_' || Character.isAlphabetic(c);
}
private boolean isIdentifierPart(final char c) {
return c == '_' || Character.isAlphabetic(c) || Character.isDigit(c);
}
private void report(final String code, final String message) {
issues.report(builder -> builder
.error(true)
.message("[PBS:%s] %s at %s:[%d,%d)".formatted(code, message, sourceLabel, start, current)));
}
private static Map<String, PbsTokenKind> buildKeywords() {
final var map = new HashMap<String, PbsTokenKind>();
map.put("import", PbsTokenKind.IMPORT);
map.put("from", PbsTokenKind.FROM);
map.put("as", PbsTokenKind.AS);
map.put("pub", PbsTokenKind.PUB);
map.put("mod", PbsTokenKind.MOD);
map.put("service", PbsTokenKind.SERVICE);
map.put("fn", PbsTokenKind.FN);
map.put("let", PbsTokenKind.LET);
map.put("mut", PbsTokenKind.MUT);
map.put("declare", PbsTokenKind.DECLARE);
map.put("struct", PbsTokenKind.STRUCT);
map.put("contract", PbsTokenKind.CONTRACT);
map.put("error", PbsTokenKind.ERROR);
map.put("if", PbsTokenKind.IF);
map.put("else", PbsTokenKind.ELSE);
map.put("when", PbsTokenKind.WHEN);
map.put("for", PbsTokenKind.FOR);
map.put("in", PbsTokenKind.IN);
map.put("return", PbsTokenKind.RETURN);
map.put("true", PbsTokenKind.TRUE);
map.put("false", PbsTokenKind.FALSE);
return map;
}
}

View File

@ -0,0 +1,8 @@
package p.studio.compiler.pbs.lexer;
public record PbsToken(
PbsTokenKind kind,
String lexeme,
int start,
int end) {
}

View File

@ -0,0 +1,62 @@
package p.studio.compiler.pbs.lexer;
public enum PbsTokenKind {
EOF,
IDENTIFIER,
INT_LITERAL,
FLOAT_LITERAL,
BOUNDED_LITERAL,
STRING_LITERAL,
// Keywords
IMPORT,
FROM,
AS,
PUB,
MOD,
SERVICE,
FN,
LET,
MUT,
DECLARE,
STRUCT,
CONTRACT,
ERROR,
IF,
ELSE,
WHEN,
FOR,
IN,
RETURN,
TRUE,
FALSE,
// Punctuation / operators
LEFT_PAREN,
RIGHT_PAREN,
LEFT_BRACE,
RIGHT_BRACE,
LEFT_BRACKET,
RIGHT_BRACKET,
COMMA,
COLON,
SEMICOLON,
AT,
DOT_DOT,
PLUS,
MINUS,
STAR,
SLASH,
PERCENT,
BANG,
BANG_EQUAL,
EQUAL,
EQUAL_EQUAL,
LESS,
LESS_EQUAL,
GREATER,
GREATER_EQUAL,
AND_AND,
OR_OR
}

View File

@ -0,0 +1,490 @@
package p.studio.compiler.pbs.parser;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.lexer.PbsToken;
import p.studio.compiler.pbs.lexer.PbsTokenKind;
import p.studio.compiler.source.Span;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
public final class PbsParser {
private final ArrayList<PbsToken> tokens;
private final FileId fileId;
private final String sourceLabel;
private final BuildingIssueSink issues;
private int current;
private PbsParser(
final ReadOnlyList<PbsToken> tokens,
final FileId fileId,
final String sourceLabel,
final BuildingIssueSink issues) {
this.tokens = new ArrayList<>();
for (final var token : tokens) {
this.tokens.add(token);
}
this.fileId = fileId;
this.sourceLabel = sourceLabel == null ? "<unknown>" : sourceLabel;
this.issues = issues;
}
public static PbsAst.File parse(
final ReadOnlyList<PbsToken> tokens,
final FileId fileId,
final String sourceLabel,
final BuildingIssueSink issues) {
return new PbsParser(tokens, fileId, sourceLabel, issues).parseFile();
}
private PbsAst.File parseFile() {
final var functions = new ArrayList<PbsAst.FunctionDecl>();
while (!isAtEnd()) {
if (match(PbsTokenKind.IMPORT)) {
parseAndDiscardImport();
continue;
}
if (match(PbsTokenKind.FN)) {
functions.add(parseFunction(previous()));
continue;
}
if (match(PbsTokenKind.MOD, PbsTokenKind.PUB)) {
report(previous(), "E_PARSE_VISIBILITY_IN_SOURCE",
"Visibility modifiers are barrel-only and cannot appear in .pbs declarations");
synchronizeTopLevel();
continue;
}
if (check(PbsTokenKind.EOF)) {
break;
}
report(peek(), "E_PARSE_UNEXPECTED_TOKEN", "Expected top-level declaration ('fn') or import");
synchronizeTopLevel();
}
final var eof = peek();
return new PbsAst.File(ReadOnlyList.wrap(functions), span(0, eof.end()));
}
private void parseAndDiscardImport() {
// Supports both forms:
// import @core:math;
// import { A, B as C } from @core:math;
if (match(PbsTokenKind.LEFT_BRACE)) {
while (!check(PbsTokenKind.RIGHT_BRACE) && !isAtEnd()) {
if (match(PbsTokenKind.IDENTIFIER)) {
if (match(PbsTokenKind.AS)) {
consume(PbsTokenKind.IDENTIFIER, "E_PARSE_EXPECTED_TOKEN",
"Expected alias identifier after 'as'");
}
match(PbsTokenKind.COMMA);
continue;
}
report(peek(), "E_PARSE_UNEXPECTED_TOKEN", "Invalid import item");
advance();
}
consume(PbsTokenKind.RIGHT_BRACE, "E_PARSE_EXPECTED_TOKEN", "Expected '}' in import list");
consume(PbsTokenKind.FROM, "E_PARSE_EXPECTED_TOKEN", "Expected 'from' in named import");
}
parseModuleRef();
consume(PbsTokenKind.SEMICOLON, "E_PARSE_EXPECTED_TOKEN", "Expected ';' after import");
}
private void parseModuleRef() {
consume(PbsTokenKind.AT, "E_PARSE_EXPECTED_TOKEN", "Expected '@' in module reference");
consume(PbsTokenKind.IDENTIFIER, "E_PARSE_EXPECTED_TOKEN", "Expected project identifier in module reference");
consume(PbsTokenKind.COLON, "E_PARSE_EXPECTED_TOKEN", "Expected ':' in module reference");
consume(PbsTokenKind.IDENTIFIER, "E_PARSE_EXPECTED_TOKEN", "Expected module identifier");
while (match(PbsTokenKind.SLASH)) {
consume(PbsTokenKind.IDENTIFIER, "E_PARSE_EXPECTED_TOKEN", "Expected module path segment after '/'");
}
}
private PbsAst.FunctionDecl parseFunction(final PbsToken fnToken) {
final var name = consume(PbsTokenKind.IDENTIFIER, "E_PARSE_EXPECTED_TOKEN", "Expected function name");
consume(PbsTokenKind.LEFT_PAREN, "E_PARSE_EXPECTED_TOKEN", "Expected '(' after function name");
final var parameters = new ArrayList<PbsAst.Parameter>();
if (!check(PbsTokenKind.RIGHT_PAREN)) {
do {
final var pStart = peek();
final boolean mutable = match(PbsTokenKind.MUT);
final var pName = consume(PbsTokenKind.IDENTIFIER, "E_PARSE_EXPECTED_TOKEN", "Expected parameter name");
consume(PbsTokenKind.COLON, "E_PARSE_EXPECTED_TOKEN", "Expected ':' after parameter name");
final var typeRef = parseTypeRef();
parameters.add(new PbsAst.Parameter(
pName.lexeme(),
mutable,
typeRef,
span(pStart.start(), typeRef.span().getEnd())));
} while (match(PbsTokenKind.COMMA));
}
consume(PbsTokenKind.RIGHT_PAREN, "E_PARSE_EXPECTED_TOKEN", "Expected ')' after parameter list");
PbsAst.TypeRef returnType = null;
if (match(PbsTokenKind.COLON)) {
returnType = parseTypeRef();
}
PbsAst.Expression elseFallback = null;
if (match(PbsTokenKind.ELSE)) {
elseFallback = parseExpression();
}
final var body = parseBlock();
return new PbsAst.FunctionDecl(
name.lexeme(),
ReadOnlyList.wrap(parameters),
returnType,
elseFallback,
body,
span(fnToken.start(), body.span().getEnd()));
}
private PbsAst.TypeRef parseTypeRef() {
final var identifier = consume(PbsTokenKind.IDENTIFIER, "E_PARSE_EXPECTED_TOKEN", "Expected type name");
return new PbsAst.TypeRef(identifier.lexeme(), span(identifier.start(), identifier.end()));
}
private PbsAst.Block parseBlock() {
final var leftBrace = consume(PbsTokenKind.LEFT_BRACE, "E_PARSE_EXPECTED_TOKEN", "Expected '{' to start block");
final var statements = new ArrayList<PbsAst.Statement>();
while (!check(PbsTokenKind.RIGHT_BRACE) && !isAtEnd()) {
statements.add(parseStatement());
}
final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "E_PARSE_EXPECTED_TOKEN", "Expected '}' to end block");
return new PbsAst.Block(ReadOnlyList.wrap(statements), span(leftBrace.start(), rightBrace.end()));
}
private PbsAst.Statement parseStatement() {
if (match(PbsTokenKind.LET)) {
return parseLetStatement(previous());
}
if (match(PbsTokenKind.RETURN)) {
return parseReturnStatement(previous());
}
return parseExpressionStatement();
}
private PbsAst.Statement parseLetStatement(final PbsToken letToken) {
final boolean mutable = match(PbsTokenKind.MUT);
final var name = consume(PbsTokenKind.IDENTIFIER, "E_PARSE_EXPECTED_TOKEN", "Expected variable name");
PbsAst.TypeRef explicitType = null;
if (match(PbsTokenKind.COLON)) {
explicitType = parseTypeRef();
}
consume(PbsTokenKind.EQUAL, "E_PARSE_EXPECTED_TOKEN", "Expected '=' in let statement");
final var initializer = parseExpression();
final var semicolon = consume(PbsTokenKind.SEMICOLON, "E_PARSE_EXPECTED_TOKEN", "Expected ';' after let statement");
return new PbsAst.LetStatement(
name.lexeme(),
mutable,
explicitType,
initializer,
span(letToken.start(), semicolon.end()));
}
private PbsAst.Statement parseReturnStatement(final PbsToken returnToken) {
PbsAst.Expression value = null;
if (!check(PbsTokenKind.SEMICOLON)) {
value = parseExpression();
}
final var semicolon = consume(PbsTokenKind.SEMICOLON, "E_PARSE_EXPECTED_TOKEN", "Expected ';' after return");
return new PbsAst.ReturnStatement(value, span(returnToken.start(), semicolon.end()));
}
private PbsAst.Statement parseExpressionStatement() {
final var expression = parseExpression();
final var semicolon = consume(PbsTokenKind.SEMICOLON, "E_PARSE_EXPECTED_TOKEN", "Expected ';' after expression");
return new PbsAst.ExpressionStatement(expression, span(expression.span().getStart(), semicolon.end()));
}
private PbsAst.Expression parseExpression() {
return parseOr();
}
private PbsAst.Expression parseOr() {
var expression = parseAnd();
while (match(PbsTokenKind.OR_OR)) {
final var operator = previous();
final var right = parseAnd();
expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
span(expression.span().getStart(), right.span().getEnd()));
}
return expression;
}
private PbsAst.Expression parseAnd() {
var expression = parseEquality();
while (match(PbsTokenKind.AND_AND)) {
final var operator = previous();
final var right = parseEquality();
expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
span(expression.span().getStart(), right.span().getEnd()));
}
return expression;
}
private PbsAst.Expression parseEquality() {
var expression = parseComparison();
if (match(PbsTokenKind.EQUAL_EQUAL, PbsTokenKind.BANG_EQUAL)) {
final var operator = previous();
final var right = parseComparison();
expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
span(expression.span().getStart(), right.span().getEnd()));
if (check(PbsTokenKind.EQUAL_EQUAL) || check(PbsTokenKind.BANG_EQUAL)) {
report(peek(), "E_PARSE_NON_ASSOC", "Chained equality is not allowed");
while (match(PbsTokenKind.EQUAL_EQUAL, PbsTokenKind.BANG_EQUAL)) {
parseComparison();
}
}
}
return expression;
}
private PbsAst.Expression parseComparison() {
var expression = parseTerm();
if (match(PbsTokenKind.LESS, PbsTokenKind.LESS_EQUAL, PbsTokenKind.GREATER, PbsTokenKind.GREATER_EQUAL)) {
final var operator = previous();
final var right = parseTerm();
expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
span(expression.span().getStart(), right.span().getEnd()));
if (check(PbsTokenKind.LESS) || check(PbsTokenKind.LESS_EQUAL)
|| check(PbsTokenKind.GREATER) || check(PbsTokenKind.GREATER_EQUAL)) {
report(peek(), "E_PARSE_NON_ASSOC", "Chained comparison is not allowed");
while (match(PbsTokenKind.LESS, PbsTokenKind.LESS_EQUAL, PbsTokenKind.GREATER, PbsTokenKind.GREATER_EQUAL)) {
parseTerm();
}
}
}
return expression;
}
private PbsAst.Expression parseTerm() {
var expression = parseFactor();
while (match(PbsTokenKind.PLUS, PbsTokenKind.MINUS)) {
final var operator = previous();
final var right = parseFactor();
expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
span(expression.span().getStart(), right.span().getEnd()));
}
return expression;
}
private PbsAst.Expression parseFactor() {
var expression = parseUnary();
while (match(PbsTokenKind.STAR, PbsTokenKind.SLASH, PbsTokenKind.PERCENT)) {
final var operator = previous();
final var right = parseUnary();
expression = new PbsAst.BinaryExpr(operator.lexeme(), expression, right,
span(expression.span().getStart(), right.span().getEnd()));
}
return expression;
}
private PbsAst.Expression parseUnary() {
if (match(PbsTokenKind.BANG, PbsTokenKind.MINUS)) {
final var operator = previous();
final var right = parseUnary();
return new PbsAst.UnaryExpr(
operator.lexeme(),
right,
span(operator.start(), right.span().getEnd()));
}
return parseCall();
}
private PbsAst.Expression parseCall() {
var expression = parsePrimary();
while (match(PbsTokenKind.LEFT_PAREN)) {
final var open = previous();
final var arguments = new ArrayList<PbsAst.Expression>();
if (!check(PbsTokenKind.RIGHT_PAREN)) {
do {
arguments.add(parseExpression());
} while (match(PbsTokenKind.COMMA));
}
final var close = consume(PbsTokenKind.RIGHT_PAREN, "E_PARSE_EXPECTED_TOKEN", "Expected ')' after arguments");
expression = new PbsAst.CallExpr(
expression,
ReadOnlyList.wrap(arguments),
span(expression.span().getStart(), close.end()));
// Avoid endless loops on malformed "f((" forms.
if (open.start() == close.start()) {
break;
}
}
return expression;
}
private PbsAst.Expression parsePrimary() {
if (match(PbsTokenKind.TRUE)) {
final var token = previous();
return new PbsAst.BoolLiteralExpr(true, span(token.start(), token.end()));
}
if (match(PbsTokenKind.FALSE)) {
final var token = previous();
return new PbsAst.BoolLiteralExpr(false, span(token.start(), token.end()));
}
if (match(PbsTokenKind.INT_LITERAL)) {
final var token = previous();
return new PbsAst.IntLiteralExpr(parseLongOrDefault(token.lexeme(), 0L), span(token.start(), token.end()));
}
if (match(PbsTokenKind.FLOAT_LITERAL)) {
final var token = previous();
return new PbsAst.FloatLiteralExpr(parseDoubleOrDefault(token.lexeme(), 0.0d), span(token.start(), token.end()));
}
if (match(PbsTokenKind.BOUNDED_LITERAL)) {
final var token = previous();
final var raw = token.lexeme().substring(0, Math.max(token.lexeme().length() - 1, 0));
return new PbsAst.BoundedLiteralExpr(parseIntOrDefault(raw, 0), span(token.start(), token.end()));
}
if (match(PbsTokenKind.STRING_LITERAL)) {
final var token = previous();
return new PbsAst.StringLiteralExpr(unescapeString(token.lexeme()), span(token.start(), token.end()));
}
if (match(PbsTokenKind.IDENTIFIER)) {
final var token = previous();
return new PbsAst.IdentifierExpr(token.lexeme(), span(token.start(), token.end()));
}
if (match(PbsTokenKind.LEFT_PAREN)) {
final var open = previous();
final var expression = parseExpression();
final var close = consume(PbsTokenKind.RIGHT_PAREN, "E_PARSE_EXPECTED_TOKEN", "Expected ')' after grouped expression");
return new PbsAst.GroupExpr(expression, span(open.start(), close.end()));
}
final var token = peek();
report(token, "E_PARSE_UNEXPECTED_TOKEN", "Unexpected token in expression: " + token.kind());
advance();
return new PbsAst.IntLiteralExpr(0L, span(token.start(), token.end()));
}
private void synchronizeTopLevel() {
while (!isAtEnd()) {
if (check(PbsTokenKind.FN) || check(PbsTokenKind.IMPORT)) {
return;
}
if (match(PbsTokenKind.SEMICOLON)) {
return;
}
advance();
}
}
private boolean match(final PbsTokenKind... kinds) {
for (final var kind : kinds) {
if (check(kind)) {
advance();
return true;
}
}
return false;
}
private PbsToken consume(final PbsTokenKind kind, final String code, final String message) {
if (check(kind)) {
return advance();
}
final var token = peek();
report(token, code, message + ", found " + token.kind());
if (!isAtEnd()) {
return advance();
}
return token;
}
private boolean check(final PbsTokenKind kind) {
if (isAtEnd()) return kind == PbsTokenKind.EOF;
return peek().kind() == kind;
}
private PbsToken advance() {
if (!isAtEnd()) current++;
return previous();
}
private boolean isAtEnd() {
return peek().kind() == PbsTokenKind.EOF;
}
private PbsToken peek() {
return tokens.get(current);
}
private PbsToken previous() {
return tokens.get(Math.max(current - 1, 0));
}
private Span span(final long start, final long end) {
return new Span(fileId, start, end);
}
private void report(final PbsToken token, final String code, final String message) {
issues.report(builder -> builder
.error(true)
.message("[PBS:%s] %s at %s:[%d,%d)".formatted(code, message, sourceLabel, token.start(), token.end())));
}
private long parseLongOrDefault(final String text, final long fallback) {
try {
return Long.parseLong(text);
} catch (NumberFormatException ignored) {
return fallback;
}
}
private int parseIntOrDefault(final String text, final int fallback) {
try {
return Integer.parseInt(text);
} catch (NumberFormatException ignored) {
return fallback;
}
}
private double parseDoubleOrDefault(final String text, final double fallback) {
try {
return Double.parseDouble(text);
} catch (NumberFormatException ignored) {
return fallback;
}
}
private String unescapeString(final String lexeme) {
if (lexeme.length() < 2) {
return "";
}
final var raw = lexeme.substring(1, lexeme.length() - 1);
final var sb = new StringBuilder(raw.length());
for (int i = 0; i < raw.length(); i++) {
final char c = raw.charAt(i);
if (c != '\\' || i + 1 >= raw.length()) {
sb.append(c);
continue;
}
final char next = raw.charAt(++i);
switch (next) {
case 'n' -> sb.append('\n');
case 'r' -> sb.append('\r');
case 't' -> sb.append('\t');
case '"' -> sb.append('"');
case '\\' -> sb.append('\\');
default -> sb.append(next);
}
}
return sb.toString();
}
}

View File

@ -4,22 +4,47 @@ import lombok.extern.slf4j.Slf4j;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.messages.FrontendPhaseContext;
import p.studio.compiler.models.IRBackend;
import p.studio.compiler.models.IRFunction;
import p.studio.compiler.pbs.PbsFrontendCompiler;
import p.studio.utilities.logs.LogAggregator;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
import java.util.Comparator;
@Slf4j
public class PBSFrontendPhaseService implements FrontendPhaseService {
private final PbsFrontendCompiler frontendCompiler = new PbsFrontendCompiler();
@Override
public IRBackend compile(final FrontendPhaseContext ctx, final LogAggregator logs, final BuildingIssueSink issues) {
final var functions = new ArrayList<IRFunction>();
for (final var pId : ctx.stack.reverseTopologicalOrder) {
for (final var fId : ctx.fileTable.getFiles(pId)) {
final var fileIds = ctx.fileTable
.getFiles(pId)
.stream()
.sorted(Comparator.comparing(fId -> ctx.fileTable.get(fId).getCanonPath().toString()))
.toList();
for (final var fId : fileIds) {
final var sourceHandle = ctx.fileTable.get(fId);
if (!sourceHandle.getFilename().endsWith(".pbs")) {
continue;
}
sourceHandle.readUtf8().ifPresentOrElse(
utf8Content -> logs.using(log).info("File content: %s".formatted(utf8Content)),
() -> issues.report(builder -> builder
.error(true)
.message("Failed to read file content: %s".formatted(sourceHandle.toString()))));
utf8Content -> functions.addAll(frontendCompiler
.compileFile(fId, utf8Content, sourceHandle.getCanonPath().toString(), issues)
.stream()
.toList()),
() -> issues.report(builder -> builder
.error(true)
.message("Failed to read file content: %s".formatted(sourceHandle.toString()))));
}
}
return new IRBackend();
logs.using(log).debug("PBS frontend lowered %d function(s) to IR".formatted(functions.size()));
return new IRBackend(ReadOnlyList.wrap(functions));
}
}

View File

@ -0,0 +1,49 @@
package p.studio.compiler.pbs;
import org.junit.jupiter.api.Test;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.source.identifiers.FileId;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PbsFrontendCompilerTest {
@Test
void shouldLowerFunctionsToIr() {
final var source = """
fn a(): int {
return 1;
}
fn b(x: int): int {
return x;
}
""";
final var issues = BuildingIssueSink.empty();
final var compiler = new PbsFrontendCompiler();
final var functions = compiler.compileFile(new FileId(0), source, "compile-test.pbs", issues);
assertTrue(issues.isEmpty(), "Valid program should not report issues");
assertEquals(2, functions.size());
assertEquals("a", functions.get(0).name());
assertEquals(0, functions.get(0).parameterCount());
assertEquals("b", functions.get(1).name());
assertEquals(1, functions.get(1).parameterCount());
}
@Test
void shouldReportDuplicateFunctionNames() {
final var source = """
fn a(): int { return 1; }
fn a(): int { return 2; }
""";
final var issues = BuildingIssueSink.empty();
final var compiler = new PbsFrontendCompiler();
compiler.compileFile(new FileId(0), source, "compile-test.pbs", issues);
assertTrue(issues.hasErrors());
}
}

View File

@ -0,0 +1,29 @@
package p.studio.compiler.pbs.lexer;
import org.junit.jupiter.api.Test;
import p.studio.compiler.messages.BuildingIssueSink;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PbsLexerTest {
@Test
void shouldLexFunctionTokens() {
final var source = "fn sum(a: int, b: int): int { return a + b; }";
final var issues = BuildingIssueSink.empty();
final var tokens = PbsLexer.lex(source, "test.pbs", issues);
assertEquals(PbsTokenKind.FN, tokens.get(0).kind());
assertEquals(PbsTokenKind.IDENTIFIER, tokens.get(1).kind());
assertEquals(PbsTokenKind.LEFT_PAREN, tokens.get(2).kind());
assertEquals(PbsTokenKind.RETURN, tokens.stream()
.filter(t -> t.kind() == PbsTokenKind.RETURN)
.findFirst()
.orElseThrow()
.kind());
assertEquals(PbsTokenKind.EOF, tokens.getLast().kind());
assertTrue(issues.isEmpty(), "Lexer should not report issues for valid input");
}
}

View File

@ -0,0 +1,37 @@
package p.studio.compiler.pbs.parser;
import org.junit.jupiter.api.Test;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.lexer.PbsLexer;
import p.studio.compiler.source.identifiers.FileId;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PbsParserTest {
@Test
void shouldParseSingleFunction() {
final var source = """
fn sum(a: int, b: int): int {
return a + b;
}
""";
final var issues = BuildingIssueSink.empty();
final var tokens = PbsLexer.lex(source, "parser-test.pbs", issues);
final PbsAst.File ast = PbsParser.parse(tokens, new FileId(0), "parser-test.pbs", issues);
assertTrue(issues.isEmpty(), "Parser should not report issues for valid function");
assertEquals(1, ast.functions().size());
final var fn = ast.functions().getFirst();
assertEquals("sum", fn.name());
assertEquals(2, fn.parameters().size());
assertEquals("int", fn.returnType().name());
assertEquals(1, fn.body().statements().size());
assertInstanceOf(PbsAst.ReturnStatement.class, fn.body().statements().getFirst());
}
}

View File

@ -1,4 +1,19 @@
package p.studio.compiler.models;
import p.studio.utilities.structures.ReadOnlyList;
public class IRBackend {
private final ReadOnlyList<IRFunction> functions;
public IRBackend() {
this(ReadOnlyList.empty());
}
public IRBackend(final ReadOnlyList<IRFunction> functions) {
this.functions = functions == null ? ReadOnlyList.empty() : functions;
}
public ReadOnlyList<IRFunction> getFunctions() {
return functions;
}
}

View File

@ -0,0 +1,12 @@
package p.studio.compiler.models;
import p.studio.compiler.source.Span;
import p.studio.compiler.source.identifiers.FileId;
public record IRFunction(
FileId fileId,
String name,
int parameterCount,
boolean hasExplicitReturnType,
Span span) {
}