integrated 14 into specs

This commit is contained in:
bQUARKz 2026-03-03 16:11:39 +00:00
parent 1cacbd4f9a
commit 4b44674907
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
16 changed files with 2455 additions and 856 deletions

View File

@ -141,5 +141,3 @@ This workshop should produce:
- `docs/pbs/specs/13. Conformance Test Specification.md` - `docs/pbs/specs/13. Conformance Test Specification.md`
- `docs/pbs/specs/14. Name Resolution and Module Linking Specification.md` - `docs/pbs/specs/14. Name Resolution and Module Linking Specification.md`
- `docs/pbs/agendas/11. Diagnostics Agenda.md` - `docs/pbs/agendas/11. Diagnostics Agenda.md`
- `docs/pbs/agendas/14.4. Name Resolution Workshop 4 - Linking Phase Boundary.md`

View File

@ -114,5 +114,3 @@ Rationale:
- `docs/pbs/specs/12. IR and Lowering Specification.md` - `docs/pbs/specs/12. IR and Lowering Specification.md`
- `docs/pbs/specs/15. Bytecode and PBX Mapping Specification.md` - `docs/pbs/specs/15. Bytecode and PBX Mapping Specification.md`
- `docs/pbs/agendas/12. IR and Lowering Agenda.md` - `docs/pbs/agendas/12. IR and Lowering Agenda.md`
- `docs/pbs/agendas/14.4. Name Resolution Workshop 4 - Linking Phase Boundary.md`

View File

@ -1,117 +0,0 @@
# PBS Name Resolution and Module Linking Agenda
Status: Active
## Purpose
Drive the closure of `14. Name Resolution and Module Linking Specification.md` so frontend implementation can proceed without inventing lookup or linking rules.
## Context
This is the main unresolved frontend-facing spec. The current skeleton still leaves open:
- exact lookup order across locals, top-level declarations, imports, builtin shells, and reserved surfaces,
- shadowing and duplicate rules by namespace,
- import alias and barrel-export matching behavior,
- cross-module callable and overload visibility,
- and the resolution model for builtin and host-backed stdlib shells.
Without this closure, parser and AST work can proceed only as exploratory implementation rather than normative frontend work.
## Decisions To Produce
1. Decide deterministic lookup order for every relevant namespace.
2. Decide shadowing, duplicate, and ambiguity rules for local, module, and imported names.
3. Decide import alias resolution and barrel matching rules in enough detail for implementation.
4. Decide the resolution model for builtin shells, builtin constants, intrinsic members, and host owners.
5. Decide which rejections belong to static semantics and which belong to a distinct linking phase.
## Core Questions
1. What is the exact lookup order in value position, type position, callable position, and host-owner position?
2. Do shadowing rules differ between locals, imports, top-level declarations, and builtin surfaces?
3. How are callable overload sets formed across local module declarations and imports?
4. When two imported modules make the same name visible, what is the exact ambiguity and rejection rule?
5. Are reserved stdlib shells resolved as ordinary visible declarations with metadata, or do they have any special priority?
## Proposed Workshop Sequence
### Workshop 1: Scope, Lookup, and Imports
Purpose:
- close module scope construction,
- close lookup order by namespace,
- and close import alias and ambiguity rules.
Reference:
- `docs/pbs/agendas/14.1. Name Resolution Workshop 1 - Scope, Lookup, and Imports.md`
### Workshop 2: Builtin Shells and Host Owners
Purpose:
- close how reserved stdlib shells enter resolution,
- and close lookup and collision policy for builtin types, builtin constants, intrinsic members, and host owners.
Expected decisions:
- reserved-shell visibility model,
- builtin and host-owner lookup interaction,
- and canonical identity versus local aliasing boundaries.
### Workshop 3: Callable Sets and Cross-Module Linking
Purpose:
- close overload-set formation across module boundaries,
- and decide ambiguity and rejection rules for imported callable sets.
Expected decisions:
- callable-set merge rules,
- barrel-matching consequences for imported overloads,
- and cross-module ambiguity failures.
### Workshop 4: Linking Phase Boundary
Purpose:
- decide which rules belong to static semantics versus a distinct linking phase.
Expected decisions:
- phase split or collapse,
- rejection ownership by phase,
- and inputs needed by `11`, `12`, and frontend conformance.
## Expected Spec Material
The resulting spec work should be able to add or close sections for:
- scope construction,
- namespace-specific lookup order,
- import and alias resolution,
- barrel export matching,
- overload-set visibility across modules,
- builtin and host-shell resolution,
- linking-phase versus static-phase rejection boundaries,
- and deterministic cross-module failure cases.
## Non-Goals
- Redefining grammar already fixed in core syntax.
- Designing runtime loader behavior.
- Freezing one symbol-table implementation.
- Designing IDE auto-import behavior.
## Inputs
- `docs/pbs/specs/3. Core Syntax Specification.md`
- `docs/pbs/specs/4. Static Semantics Specification.md`
- `docs/pbs/specs/5. Manifest, Stdlib, and SDK Resolution Specification.md`
- `docs/pbs/specs/6.1. Intrinsics and Builtin Types Specification.md`
- `docs/pbs/specs/8. Stdlib Environment Packaging and Loading Specification.md`
- `docs/pbs/specs/14. Name Resolution and Module Linking Specification.md`
- `docs/pbs/specs/18. Standard Library Surface Specification.md`

View File

@ -1,184 +0,0 @@
# PBS Name Resolution Workshop 1
Status: Active
## Purpose
Run the first focused discussion for `14. Name Resolution and Module Linking Specification.md` on the smallest set of decisions that unlocks normative frontend work:
- scope construction,
- namespace-specific lookup order,
- import visibility,
- and ambiguity rejection.
## Why This Slice First
This slice is the best first cut because it does not require full lowering closure, but it does unblock:
- parser and binder architecture,
- symbol-table modeling,
- deterministic frontend diagnostics,
- and the later discussion of overload visibility and reserved stdlib shells.
It should intentionally avoid trying to close every linking edge case in one meeting.
## Proposed Meeting Order
1. Confirm already-settled inputs that are not up for debate.
2. Close scope-construction rules inside one module.
3. Close lookup order by namespace.
4. Close import visibility and alias rules.
5. Close ambiguity and duplicate rejection rules.
6. Record deferred items for a second workshop.
## Already-Settled Inputs To Reconfirm
The meeting should start by explicitly reaffirming:
- `mod.barrel` is the single source of module visibility.
- Imports target modules, not files.
- Only `pub` names are importable from another module.
- PBS has distinct type, value, callable, and host-owner namespaces.
- Builtin simple types `int`, `float`, `bool`, and `str` are always present in type position.
- Reserved stdlib spaces resolve from the selected stdlib environment, not from ordinary dependencies.
These points already come from existing specs and should not be reopened in this workshop.
## Decisions To Produce
1. Scope construction inside a module.
2. Lookup order in value, type, callable, and host-owner position.
3. Import alias behavior and collisions.
4. Ambiguity rules when multiple imports expose the same visible name.
5. The phase boundary between syntax, static semantics, and linking for these failures.
## Candidate Decisions
### 1. Scope Construction Inside One Module
Candidate direction:
- File-local top-level declarations are collected first from all `.pbs` files in the module.
- `mod.barrel` is then applied as a visibility filter over the already-collected top-level declarations.
- Top-level declaration availability inside the same module does not depend on source-file order.
- Local block scopes nest normally over module scope.
Rationale:
- This matches the existing module model.
- It prevents file-order-dependent lookup.
- It keeps barrel as a visibility mechanism, not as the source of declaration existence.
### 2. Lookup Order By Namespace
Candidate direction:
Value position:
- nearest local bindings,
- parameters,
- visible `declare const`,
- visible imported values,
- service singleton values introduced by visible service declarations.
Type position:
- nearest type declarations visible in module scope,
- visible imported type declarations,
- builtin simple types.
Callable position:
- nearest callable declarations visible in module scope,
- then visible imported callable declarations,
- with overload sets formed after visibility filtering.
Host-owner position:
- visible host owners in module scope,
- then visible imported host owners.
Open point for discussion:
- whether builtin simple types should be treated as implicit outermost scope or as a final fallback outside ordinary visible declarations.
### 3. Import Alias Rules
Candidate direction:
- `import @project:mod;` imports the module itself as a module-qualified reference surface only if another spec later defines such a surface.
- For current v1 name resolution, named imports are the only source of imported visible names.
- `import { X } from ...;` introduces `X` in the matching namespace of the exported declaration.
- `import { X as Y } from ...;` introduces only `Y` locally.
- Alias spelling affects only the local visible name, never canonical builtin or host identity.
Rationale:
- Keeps current source behavior conservative.
- Avoids inventing module-as-value semantics accidentally.
- Matches the existing distinction between source-visible spelling and canonical metadata identity.
### 4. Collision And Ambiguity Rules
Candidate direction:
- Two local declarations in the same namespace that produce the same non-overload identity are duplicates.
- A local declaration and an imported declaration with the same visible name in the same namespace are a deterministic conflict unless one rule explicitly allows shadowing.
- Two imported declarations with the same visible name in the same namespace are a deterministic ambiguity unless they are the same declaration reached through the same canonical module resolution.
- Callable overload sets may merge only when their callable identities remain distinct after tuple-shape identity rules are applied.
- Non-callable namespaces do not merge by name.
Open point for discussion:
- whether local declarations should fully shadow imports in value, type, callable, and host-owner namespaces, or whether PBS should reject such shadowing to keep lookup simpler for beginners.
### 5. Diagnostic Phase Boundary
Candidate direction:
- malformed import syntax remains syntax-phase.
- unresolved module path remains manifest/import-resolution phase.
- import of non-exported symbol remains linking-phase or manifest/import-resolution-adjacent phase, but not syntax-phase.
- duplicate local declarations remain static-semantics phase.
- ambiguous visible names after import resolution remain linking-phase unless the project later collapses linking into static semantics explicitly.
Rationale:
- This preserves a clean split between parsing, declaration validity, and cross-module resolution.
- It gives `11. Diagnostics Specification.md` a concrete input rather than forcing that spec to invent phases in isolation.
## Questions To Resolve In The Room
1. Do local top-level declarations shadow imported names, or is any such collision rejected?
2. Are parameters and local `let` bindings one combined nearest-scope value layer, or should parameters be modeled separately for diagnostics only?
3. Can imported overloads and local overloads merge into one callable set, or must that be rejected?
4. Is `import @project:module;` currently just side-effect-free validation, or should it create any usable source-visible surface in v1?
5. Do builtin simple types behave like reserved final fallback names or like ordinary always-visible type declarations?
## Expected Outputs
This workshop should produce:
1. a decision record for scope and lookup order,
2. a decision record for import alias and ambiguity policy,
3. a concrete update plan for `14. Name Resolution and Module Linking Specification.md`,
4. and a list of deferred issues for Workshop 2.
## Explicit Deferrals For Workshop 2
The following topics should be deferred unless they become unavoidable:
- reserved stdlib shell priority versus ordinary imports,
- member lookup on builtin receivers,
- contract and method dispatch lookup details,
- barrel matching of callable overload sets across module boundaries,
- and whether linking is its own normative phase or is folded fully into static semantics.
## Inputs
- `docs/pbs/specs/3. Core Syntax Specification.md`
- `docs/pbs/specs/4. Static Semantics Specification.md`
- `docs/pbs/specs/5. Manifest, Stdlib, and SDK Resolution Specification.md`
- `docs/pbs/specs/14. Name Resolution and Module Linking Specification.md`
- `docs/pbs/specs/18. Standard Library Surface Specification.md`
- `docs/pbs/agendas/14. Name Resolution and Module Linking Agenda.md`

View File

@ -1,159 +0,0 @@
# PBS Name Resolution Workshop 2
Status: Active
## Purpose
Run the second focused discussion for `14. Name Resolution and Module Linking Specification.md` on reserved stdlib shells and host-owner resolution:
- builtin type shells,
- builtin constants,
- intrinsic member surfaces,
- and `declare host` owners imported from reserved stdlib modules.
## Why This Slice Second
This slice should come after Workshop 1 because it builds on already-closed rules for:
- module scope construction,
- ordinary import visibility,
- aliasing,
- and namespace-specific lookup order.
It also prepares the ground for later lowering by making the resolution contract explicit without prematurely defining artifact shape.
## Proposed Meeting Order
1. Reconfirm already-settled ownership and stdlib-loading rules.
2. Close the visibility model for builtin and host shells.
3. Close lookup behavior for builtin types, builtin constants, and host owners.
4. Close member resolution on builtin receivers.
5. Close collision and ambiguity rules involving reserved shells.
6. Record deferred callable-linking issues for Workshop 3.
## Already-Settled Inputs To Reconfirm
The meeting should explicitly reaffirm:
- reserved stdlib project spaces include `@core:*` and `@sdk:*`,
- reserved stdlib spaces resolve only from the selected stdlib environment,
- source-visible builtin names resolve through imported builtin shell declarations,
- builtin intrinsic members are not imported as free functions,
- canonical builtin and host identities come from metadata rather than local alias spelling,
- and host-owner namespace remains distinct from type, value, and callable namespaces.
These are already fixed elsewhere and should not be reopened here.
## Decisions To Produce
1. Visibility rules for builtin shells and host owners imported from reserved stdlib modules.
2. Lookup rules for builtin types, builtin constants, intrinsic members, and host owners.
3. Collision and ambiguity rules when reserved shells overlap with ordinary user-visible declarations.
4. Diagnostic ownership for reserved-shell resolution failures.
5. Deferred boundary between source resolution and later lowering.
## Candidate Decisions
### 1. Reserved Shells Enter Resolution As Ordinary Visible Declarations
Candidate direction:
- Imported builtin type shells become visible in the type namespace like other imported type declarations.
- Imported builtin constants become visible in the value namespace like other imported constants.
- Imported host owners become visible in the host-owner namespace like other imported host declarations.
- Reserved metadata changes canonical lowering identity, not the ordinary namespace they enter.
Rationale:
- This keeps the source model uniform.
- It avoids a hidden parallel lookup system just for reserved stdlib surfaces.
- It matches the current specs that describe shells as imported declarations with metadata.
### 2. Builtin Simple Types Remain Separate From Imported Builtin Shells
Candidate direction:
- `int`, `float`, `bool`, and `str` remain always-available builtin simple types.
- Imported builtin shells such as `Vec2` or `Color` are ordinary visible type declarations and do not share special priority with builtin simple types.
- User-authored declarations still cannot reuse builtin simple type names.
Rationale:
- Separates the small predeclared core from stdlib-delivered builtin shells.
- Prevents `@core:*` imports from acting like implicit language keywords.
### 3. Intrinsic Members Resolve Through Receiver Type, Never Through Free Lookup
Candidate direction:
- Builtin intrinsic methods are resolved only during member lookup on a builtin-typed receiver.
- They do not appear in top-level callable lookup.
- They do not become visible through named import by method name.
- Alias changes to the builtin type affect only source spelling of the owner type, not intrinsic canonical identity.
Rationale:
- This matches the existing builtin and intrinsics spec.
- It prevents accidental confusion between member semantics and free functions.
### 4. Collision Policy For Reserved Shells
Candidate direction:
- Namespace collisions are handled by the same visibility rules used for ordinary declarations.
- A user-visible imported builtin type colliding with another visible type name is a deterministic conflict unless an already-closed shadowing rule says otherwise.
- A host owner colliding with a type or value name is not a conflict by itself because host-owner namespace is distinct.
- Duplicate canonical builtin identities in one resolved environment remain rejection cases even if local aliases differ.
Open point for discussion:
- whether conflicts involving reserved shells should be rejected more aggressively than ordinary import conflicts to preserve beginner clarity.
### 5. Diagnostic Ownership
Candidate direction:
- failure to resolve the reserved module remains manifest/import-resolution phase,
- import of a non-exported builtin or host shell remains linking or resolution-phase rejection,
- member lookup failure on a builtin receiver remains static-semantics or linking-phase depending on the final phase model,
- duplicate canonical builtin or host identities in one resolved environment remain deterministic compile-time rejection.
Rationale:
- This keeps module loading, visibility, and member resolution separate.
- It gives `11` a concrete discussion input later.
## Questions To Resolve In The Room
1. Should reserved shells be completely ordinary imported declarations at source level, or do any of them need special lookup priority?
2. Can an ordinary user declaration shadow an imported builtin shell, or should that always be rejected?
3. Is member lookup on builtin receivers purely a type-driven operation after receiver typing, or does name resolution need a separate reserved-shell step?
4. Should `@sdk:*` be allowed to expose builtin shells, or should builtin shells remain policy-preferred under `@core:*` only?
5. Which failures around canonical builtin identity belong to resolution, static semantics, or environment validation?
## Expected Outputs
This workshop should produce:
1. a decision record for reserved-shell visibility and lookup,
2. a decision record for collision policy involving builtin and host shells,
3. a concrete update plan for the reserved-shell sections of `14`,
4. and a deferred list for cross-module callable-set discussion in Workshop 3.
## Explicit Deferrals For Workshop 3
The following topics should be deferred unless unavoidable:
- imported callable-set merging across modules,
- overload resolution across ordinary and reserved callable surfaces,
- contract dispatch lookup,
- and whether linking is fully distinct from static semantics.
## Inputs
- `docs/pbs/specs/5. Manifest, Stdlib, and SDK Resolution Specification.md`
- `docs/pbs/specs/6.1. Intrinsics and Builtin Types Specification.md`
- `docs/pbs/specs/14. Name Resolution and Module Linking Specification.md`
- `docs/pbs/specs/18. Standard Library Surface Specification.md`
- `docs/pbs/agendas/14. Name Resolution and Module Linking Agenda.md`
- `docs/pbs/agendas/14.1. Name Resolution Workshop 1 - Scope, Lookup, and Imports.md`

View File

@ -1,160 +0,0 @@
# PBS Name Resolution Workshop 3
Status: Active
## Purpose
Run the third focused discussion for `14. Name Resolution and Module Linking Specification.md` on callable visibility and cross-module linking:
- imported callable sets,
- overload-set formation across module boundaries,
- barrel matching consequences for imported overloads,
- and deterministic ambiguity rejection.
## Why This Slice Third
This slice should come after Workshops 1 and 2 because it depends on already-closed rules for:
- scope construction,
- namespace-specific lookup order,
- import aliasing,
- reserved-shell visibility,
- and ordinary collision handling.
It is also the last major source-facing step before deciding whether linking is a separate normative phase.
## Proposed Meeting Order
1. Reconfirm callable identity and barrel visibility rules that are already fixed.
2. Close how imported callable declarations become visible.
3. Close overload-set formation across local and imported declarations.
4. Close ambiguity and conflict rules for cross-module callable sets.
5. Close barrel-matching consequences for imported overload visibility.
6. Record phase-boundary questions for Workshop 4.
## Already-Settled Inputs To Reconfirm
The meeting should explicitly reaffirm:
- callable identity ignores parameter labels and output labels,
- overloaded functions differ by input/output tuple shape rather than parameter spelling,
- only `pub` callable entries are importable from another module,
- each exported overload must appear explicitly in `mod.barrel`,
- and `mod.barrel` matches callables by callable identity rather than by label spelling alone.
These points are already fixed and should not be reopened here.
## Decisions To Produce
1. Visibility rules for imported callable declarations and callable sets.
2. Merge or no-merge policy between local and imported overloads.
3. Ambiguity and rejection rules for callable sets visible from multiple modules.
4. Barrel consequences for callable visibility across modules.
5. Diagnostic ownership for callable-linking failures.
## Candidate Decisions
### 1. Imported Callables Enter Ordinary Callable Lookup
Candidate direction:
- `import { f } from @project:mod;` introduces the exported callable set named `f` into local callable lookup.
- If the source module exports multiple overloads of `f`, the imported surface initially brings in that visible overload set, not only one declaration.
- Alias spelling changes only the local visible callable name, not callable identity.
Rationale:
- This keeps imported callables aligned with existing callable namespace rules.
- It avoids inventing per-overload import syntax before it is needed.
### 2. Local And Imported Overloads Do Not Merge Implicitly
Candidate direction:
- A local callable set and an imported callable set with the same visible name do not silently merge.
- Such a collision is a deterministic ambiguity unless both sets resolve to the same canonical underlying declarations.
- Overload merging is allowed only within one declaration origin set already sanctioned by module visibility and barrel rules.
Rationale:
- Silent merging across modules makes beginner reasoning harder.
- It also makes diagnostics and compatibility more fragile.
- Rejecting cross-origin merges keeps overload sets traceable.
Open point for discussion:
- whether there is any narrow exception for identical re-exported declarations reached through more than one import path.
### 3. Cross-Module Callable Ambiguity Is Rejected Before Overload Resolution
Candidate direction:
- If two different imported modules make the same callable name visible locally, and they do not denote the same canonical declarations, the program is rejected before ordinary overload resolution.
- Overload resolution should not be used as a tie-breaker between different imported origin sets.
- Callable ambiguity is resolved structurally by import visibility, not opportunistically by the later callsite.
Rationale:
- This keeps lookup deterministic and simple.
- It avoids context-sensitive import ambiguity that changes across callsites.
### 4. Barrel Visibility Defines The Imported Callable Set Boundary
Candidate direction:
- Only overloads exported through the source module's `mod.barrel` are part of the importable callable set.
- Non-exported local overloads in the source module do not participate in imported callable visibility.
- A named import of `f` from another module is therefore a request for that module's exported `f` set, filtered by barrel visibility.
Rationale:
- This makes cross-module callable linking a direct consequence of barrel policy rather than an extra ad hoc rule.
### 5. Diagnostic Ownership
Candidate direction:
- duplicate callable declarations inside one module remain static-semantics rejection,
- failure of a barrel callable entry to resolve inside its own module remains a module-linking or static-semantics-adjacent rejection depending on the final phase split,
- cross-module callable-name ambiguity remains linking-phase rejection,
- and overload-resolution failure at a callsite remains static semantics rather than module linking.
Rationale:
- This preserves a useful distinction between visible-set formation and callsite selection.
## Questions To Resolve In The Room
1. Should local and imported callable sets ever merge automatically under the same name?
2. Should two imported callable sets with the same name be rejected immediately, or only when actually referenced?
3. If the same declaration becomes reachable through two import paths, should that still be rejected or canonicalized as one visible origin?
4. Is there any need for per-overload import control in v1, or is barrel-level export enough?
5. Where exactly is the line between callable-set formation and ordinary overload resolution?
## Expected Outputs
This workshop should produce:
1. a decision record for imported callable-set visibility,
2. a decision record for callable ambiguity and merge policy,
3. a concrete update plan for the callable-linking sections of `14`,
4. and a narrow carry-forward list for phase-boundary cleanup in Workshop 4.
## Explicit Deferrals For Workshop 4
The following topics should be deferred unless unavoidable:
- whether linking is a formally distinct phase or folded into static semantics,
- diagnostic naming and code stability,
- source-to-artifact mapping concerns,
- and backend consequences of callable linking.
## Inputs
- `docs/pbs/specs/3. Core Syntax Specification.md`
- `docs/pbs/specs/4. Static Semantics Specification.md`
- `docs/pbs/specs/14. Name Resolution and Module Linking Specification.md`
- `docs/pbs/agendas/14. Name Resolution and Module Linking Agenda.md`
- `docs/pbs/agendas/14.1. Name Resolution Workshop 1 - Scope, Lookup, and Imports.md`
- `docs/pbs/agendas/14.2. Name Resolution Workshop 2 - Builtin Shells and Host Owners.md`

View File

@ -1,144 +0,0 @@
# PBS Name Resolution Workshop 4
Status: Active
## Purpose
Run the fourth focused discussion for `14. Name Resolution and Module Linking Specification.md` on phase ownership:
- what belongs to syntax,
- what belongs to static semantics,
- what belongs to module/linking,
- and whether PBS keeps linking as a distinct normative phase at all.
## Why This Slice Last
This slice should come last because it is mostly a boundary-setting exercise over decisions already made in Workshops 1 through 3.
Trying to close it earlier would force abstract phase arguments before the actual failure classes were concrete.
## Proposed Meeting Order
1. Reconfirm the concrete failure categories identified in earlier workshops.
2. Group those failures by source-only, module-resolution, and cross-module visibility concerns.
3. Decide whether PBS names linking as a distinct normative phase.
4. Decide how this boundary feeds diagnostics, conformance, and frontend implementation claims.
5. Record any residual editorial cleanup for the spec integration pass.
## Failure Classes To Sort
This workshop should explicitly sort at least the following classes:
- malformed import syntax,
- unresolved module path,
- import of non-exported symbol,
- duplicate local declarations,
- ambiguous imported names,
- callable-set ambiguity across modules,
- builtin-receiver member lookup failure,
- duplicate canonical builtin or host identities in one resolved environment,
- and barrel entries that fail to resolve against module declarations.
## Decisions To Produce
1. Whether linking is a distinct normative phase in PBS v1.
2. Ownership of the major failure classes already identified.
3. The minimum phase vocabulary needed by diagnostics and conformance.
4. The frontend implementation consequences of the chosen phase model.
## Candidate Decisions
### 1. Keep Linking As A Distinct Normative Phase
Candidate direction:
- syntax owns grammar and token-shape failures,
- static semantics owns declaration validity, typing, and callsite selection,
- linking owns cross-file and cross-module visible-set formation and barrel/import matching.
Rationale:
- This gives a clean home to failures that are neither purely syntactic nor ordinary local typing.
- It matches the practical compiler architecture implied by module loading and barrel visibility.
- It gives `11` and `13` a usable phase vocabulary.
Alternative to discuss:
- collapse linking into static semantics and treat "linking" only as an implementation detail.
### 2. Module-Visibility And Import Failures Belong To Linking
Candidate direction:
- unresolved exported-name lookup after the target module is loaded belongs to linking,
- ambiguous imported visible names belong to linking,
- callable-set ambiguity across modules belongs to linking,
- and unresolved barrel entries belong to linking or module-linking rather than to syntax.
Rationale:
- These failures arise from assembling visible declarations across files or modules, not from parsing isolated declarations.
### 3. Callsite Selection Remains Static Semantics
Candidate direction:
- once visible callable sets are formed, overload resolution at a callsite remains static semantics,
- member lookup driven by the known receiver type also remains static semantics unless it fails earlier due to unresolved imported shell visibility.
Rationale:
- This keeps typing and callable selection together.
- It prevents linking from becoming a catch-all bucket for any failure that mentions imports.
### 4. Diagnostics And Conformance Need Only A Small Stable Vocabulary
Candidate direction:
- the minimum normative phase vocabulary is:
syntax,
static semantics,
manifest/import resolution,
linking,
and host-binding/capability admission.
- tools may subdivide internally, but they should map back to that stable external vocabulary.
Rationale:
- This is enough for source-facing determinism without overfitting to one compiler pipeline.
## Questions To Resolve In The Room
1. Is there real value in naming linking as its own normative phase, or does that add conceptual weight without enough payoff?
2. Should unresolved barrel entries be considered module-internal static invalidity or linking invalidity?
3. Should builtin-receiver member lookup failure always stay in static semantics once receiver type is known?
4. Do diagnostics and conformance need the word `linking`, or only a broader `resolution` bucket?
5. Does the chosen phase model help or hinder future lowering and verifier specs?
## Expected Outputs
This workshop should produce:
1. a decision record for the PBS phase model around name resolution and linking,
2. a sorted matrix of failure classes by phase,
3. a concrete update plan for `14`,
4. and explicit follow-ups for `11` and `13`.
## Explicit Deferrals
The following topics should be deferred unless unavoidable:
- detailed diagnostic code design,
- artifact-level failure surfacing,
- verifier and loader phase internals,
- and optimizer or backend pipeline naming.
## Inputs
- `docs/pbs/specs/11. Diagnostics Specification.md`
- `docs/pbs/specs/13. Conformance Test Specification.md`
- `docs/pbs/specs/14. Name Resolution and Module Linking Specification.md`
- `docs/pbs/agendas/14. Name Resolution and Module Linking Agenda.md`
- `docs/pbs/agendas/14.1. Name Resolution Workshop 1 - Scope, Lookup, and Imports.md`
- `docs/pbs/agendas/14.2. Name Resolution Workshop 2 - Builtin Shells and Host Owners.md`
- `docs/pbs/agendas/14.3. Name Resolution Workshop 3 - Callable Sets and Cross-Module Linking.md`

View File

@ -0,0 +1,168 @@
# Name Resolution - Builtin Shells and Host Owners Decision
Status: Proposed
Cycle: Initial name-resolution closure pass
## 1. Context
PBS v1 needs a deterministic rule for how reserved stdlib shells participate in source-level name resolution.
The open questions were:
- whether builtin shells and host owners enter lookup as ordinary imported declarations or as special compiler-only names,
- whether builtin intrinsic methods participate in free callable lookup,
- whether reserved shells receive special collision priority,
- whether builtin shells belong under `@core:*` or `@sdk:*` by policy,
- and which phase owns failures related to builtin member lookup and canonical identity duplication.
Important fixed inputs already existed:
- reserved stdlib project spaces are resolved from the selected stdlib environment,
- source-visible builtin names resolve through imported builtin shell declarations,
- canonical builtin and host identities come from metadata rather than local spelling,
- builtin intrinsic methods are not imported as free functions,
- and host-owner namespace remains distinct from type, value, and callable namespaces.
## 2. Decision
PBS v1 adopts the following baseline for builtin shells and host-owner resolution:
1. Source-visible declarations exposed by reserved builtin shells are imported into their ordinary corresponding namespaces.
2. This means builtin-exposed types enter type lookup as imported type declarations, builtin-exposed constants enter value lookup as imported values, and host owners enter host-owner lookup as imported host-owner declarations.
3. `declare builtin type` itself remains a reserved stdlib/toolchain declaration form rather than ordinary user-authored source syntax.
4. Reserved-shell metadata exists to drive canonical identity and later lowering, not to create special source-level lookup priority.
5. Builtin simple types `int`, `float`, `bool`, and `str` remain separate always-available reserved type names and are not treated as ordinary imported builtin shells.
6. Builtin intrinsic methods never participate in free lookup.
7. Builtin intrinsic methods are resolved only through member lookup on a receiver already known to have the corresponding builtin type.
8. Reserved builtin shells and host owners follow the same collision policy as ordinary imported declarations: same visible name in the same namespace is an error unless another already-closed rule explicitly permits otherwise.
9. By policy, builtin shells are preferred under `@core:*`.
10. Member lookup failure on an already-typed builtin receiver belongs to static semantics.
11. Duplicate canonical builtin identities or duplicate canonical host identities in one resolved environment belong normatively to linking rather than to syntax or ordinary static semantics.
## 3. Reserved Shells As Imported Declarations
Reserved shells are source-visible through ordinary import and visibility rules.
This means:
- the user imports the shell from a reserved stdlib module,
- the imported shell becomes visible in the matching namespace,
- and source-level lookup treats that visible declaration like other imported declarations of the same namespace.
The compiler still recognizes the reserved metadata attached to those declarations, but that metadata does not create a parallel hidden namespace.
## 4. Builtin Simple Types Versus Imported Builtin Shells
PBS distinguishes:
- the tiny predeclared builtin simple-type set: `int`, `float`, `bool`, `str`,
- and imported builtin shells such as `Vec2`, `Color`, or similar stdlib-exposed types.
Rules:
- builtin simple types remain always available without import,
- imported builtin shells are not promoted to that same status,
- and the two categories must not be conflated in source-level lookup.
## 5. Intrinsic Member Lookup
Builtin intrinsic members are resolved only through receiver-member lookup.
Rules:
- they do not appear in top-level callable lookup,
- they are not introduced by import as free functions,
- and aliasing the owner builtin type changes only the local visible owner spelling, not the intrinsic's canonical identity.
This keeps member semantics uniform and avoids special free-function treatment for reserved surfaces.
## 6. Collision Policy
Reserved shells receive no special collision privilege.
Rules:
- if an imported builtin shell declaration and another visible declaration produce the same visible name in the same namespace, the program is rejected under the ordinary collision rules already chosen,
- if a host owner shares a spelling with a type or value, that alone is not a conflict because host-owner namespace remains distinct,
- and reserved-shell status does not authorize silent shadowing.
## 7. Reserved Project-Space Policy
For source-level policy:
- builtin shells are preferred under `@core:*`,
- host-facing capability surfaces remain the natural fit for `@sdk:*`.
This is a policy direction for clarity and separation of concerns.
It does not change the underlying ownership model governed by other specs.
## 8. Failure Ownership Baseline
The current baseline is:
- failure to resolve a reserved stdlib module remains manifest/import resolution,
- import of a non-exported reserved shell remains linking,
- member lookup failure on an already-typed builtin receiver remains static semantics,
- duplicate canonical builtin or host identities in one resolved environment remain linking failures.
Implementations may internally perform some of these checks during environment validation, but the normative external phase ownership is linking where the linking-phase decision assigns it.
## 9. Invariants
- Reserved-shell metadata does not create special source-level lookup priority.
- Builtin intrinsic methods are never free functions in source-level lookup.
- Builtin simple types remain separate from stdlib-delivered builtin shells.
- Host owners remain in the host-owner namespace only.
- Ordinary collision rules apply to reserved-shell imports unless a later explicit rule says otherwise.
- Canonical identity conflicts are linking problems, not syntax problems.
## 10. Explicit Non-Decisions
This decision record does not yet close:
- the full stdlib-surface policy of `18. Standard Library Surface Specification.md`,
- and the final lowering consequences in `12. IR and Lowering Specification.md`.
## 11. Spec Impact
This decision should feed at least:
- `docs/pbs/specs/14. Name Resolution and Module Linking Specification.md`
- `docs/pbs/specs/18. Standard Library Surface Specification.md`
- `docs/pbs/specs/11. Diagnostics Specification.md`
It should also constrain future work in:
- `docs/pbs/specs/12. IR and Lowering Specification.md`
## 12. Validation Notes
The intended baseline is:
- reserved builtin shells are imported like ordinary declarations into the matching namespace,
- builtin methods are found only through receiver-member lookup,
- builtin simple types remain always available without import,
- `@core:*` is the preferred home for builtin shells,
- and duplicate canonical builtin or host identities are rejected at linking time.
Illustrative examples:
```pbs
import { Vec2 } from @core:math;
```
`Vec2` becomes an imported visible type declaration with builtin metadata.
```pbs
import { Vec2 } from @core:math;
Vec2.zero()
```
If `zero` is a builtin intrinsic member, it resolves through builtin member lookup on `Vec2`-typed receiver use and not through free callable lookup by the name `zero`.
```pbs
import { Vec2 } from @core:math;
declare const Vec2: int = 1;
```
This is rejected as an ordinary same-namespace collision rather than resolved by reserved-shell priority.

View File

@ -0,0 +1,173 @@
# Name Resolution - Callable Sets and Cross-Module Linking Decision
Status: Proposed
Cycle: Initial name-resolution closure pass
## 1. Context
PBS v1 needs a deterministic rule for how callable names cross module boundaries.
The open questions were:
- what exactly `import { f } from @project:path;` imports when `f` has multiple exported signatures,
- whether callable sets from different origins may merge,
- whether same-name imported callables from different origins are tolerated until callsite analysis,
- whether local and imported callable names may coexist under one visible function name,
- and how `mod.barrel` bounds the callable set that becomes importable.
Earlier closure already fixed important inputs:
- `mod.barrel` is the single source of module visibility,
- only `pub` names may be imported from another module,
- local function names and imported function names with the same visible name are already being treated as collisions in this closure pass,
- and callable identity is determined by callable shape rather than parameter-label spelling alone.
## 2. Decision
PBS v1 adopts the following baseline for callable sets and cross-module linking:
1. `import { f } from @project:path;` imports the exported callable set named `f` from the target module.
2. The imported callable set for `f` consists only of those overload signatures of `f` that are exported through that module's `mod.barrel`.
3. Non-exported overloads in the target module do not participate in the imported callable set.
4. Two imports of the same visible callable name are tolerated when they resolve to the same canonical underlying callable-set origin after module resolution.
5. Such same-origin duplicate imports are redundant but not errors.
6. Two imports of the same visible callable name from different canonical origins are rejected immediately.
7. The program must not wait until a callsite to reject different-origin callable-name collisions.
8. A module-local function name and an imported function name with the same visible name are rejected immediately.
9. Automatic merging of callable sets across different origins is not permitted in this closure pass.
10. Ordinary overload resolution occurs only after one unambiguous visible callable set has already been formed.
## 3. Imported Callable Set
The meaning of:
```pbs
import { f } from @project:path;
```
is:
- resolve the target module,
- collect the exported callable entries named `f` from that module,
- form the callable set consisting only of those exported overloads,
- and make that callable set visible locally under the imported visible name.
This import does not:
- reach non-exported overloads,
- merge with hidden module-internal overloads,
- or import one overload at a time independently from the exported callable name.
## 4. Same-Origin Versus Different-Origin Imports
### 4.1 Same origin
If the same visible callable name is imported more than once and both imports resolve to the same canonical underlying callable-set origin:
- the duplicate import is tolerated,
- but it remains redundant.
### 4.2 Different origin
If the same visible callable name is imported from different canonical origins:
- the program is rejected immediately,
- and one of the imports must be removed or aliased.
The implementation must not:
- merge the callable sets,
- defer the conflict to overload resolution,
- or allow callsite context to decide which imported origin wins.
## 5. Local Versus Imported Callable Names
If a module-local function name and an imported function name are the same visible callable name:
- the program is rejected immediately,
- and the implementation must not merge the local and imported callable sets.
This preserves deterministic visibility and keeps callable origin traceable.
## 6. Barrel Boundary
`mod.barrel` defines the exact boundary of the callable set that may cross module boundaries.
Rules:
- only overloads exported through `mod.barrel` are part of the importable callable set,
- overloads with the same source-level function name but not exported through `mod.barrel` are invisible to importing modules,
- and imported callable-set visibility is therefore a direct consequence of the source module's barrel contract.
## 7. Overload Resolution Boundary
This decision deliberately separates:
- callable-set formation,
- and overload resolution at a callsite.
The implementation must first form one unambiguous visible callable set.
Only then may ordinary overload resolution operate on the candidate signatures of that set.
Callsite context must not be used to resolve ambiguity between different imported callable origins.
## 8. Invariants
- Imported callable visibility is bounded by barrel export.
- Different callable origins must not merge automatically.
- Same-origin duplicate imports may be tolerated as redundancy, not as new callable contributions.
- Local and imported function names do not coexist under one merged visible callable name in this closure pass.
- Overload resolution happens after visible callable-set formation, not instead of it.
## 9. Explicit Non-Decisions
This decision record does not yet close:
- per-overload import syntax,
- aliasing policy for callable imports beyond the already-closed generic alias rules,
- and any lowering consequences in `12. IR and Lowering Specification.md`.
## 10. Spec Impact
This decision should feed at least:
- `docs/pbs/specs/14. Name Resolution and Module Linking Specification.md`
- `docs/pbs/specs/11. Diagnostics Specification.md`
- `docs/pbs/specs/13. Conformance Test Specification.md`
It should also constrain future work in:
- `docs/pbs/specs/12. IR and Lowering Specification.md`
## 11. Validation Notes
The intended baseline is:
- callable imports operate on exported callable names,
- all exported overloads of that name come together from one module,
- same-origin duplicate imports are tolerated,
- different-origin same-name imports are rejected immediately,
- and local/import callable collisions are also rejected immediately.
Illustrative examples:
```pbs
import { f } from @a:m;
import { f } from @a:m;
```
This is tolerated as redundant same-origin duplication.
```pbs
import { f } from @a:m;
import { f } from @b:n;
```
This is rejected immediately because the visible callable names match but the canonical origins differ.
```pbs
fn f(a: int) -> int { ... }
import { f } from @a:m;
```
This is rejected as a local-versus-import callable collision.

View File

@ -0,0 +1,200 @@
# Name Resolution - Linking Phase Boundary Decision
Status: Proposed
Cycle: Initial name-resolution closure pass
## 1. Context
PBS v1 needs an explicit phase boundary around source-level name resolution so that:
- frontend implementation does not invent phase ownership,
- diagnostics can attribute failures consistently,
- conformance can test rejection classes coherently,
- and later lowering work does not have to guess whether a failure belongs to syntax, linking, or typing.
Earlier closure already established rules for:
- module scope construction,
- import naming and aliasing,
- builtin shell visibility,
- callable-set visibility across modules.
The remaining open question was how to classify the failure classes that arise while assembling visible declarations across files and modules.
## 2. Decision
PBS v1 adopts the following normative phase baseline for name resolution and related frontend failures:
1. `syntax` is a distinct normative phase.
2. `manifest/import resolution` is a distinct normative phase.
3. `linking` is a distinct normative phase.
4. `static semantics` is a distinct normative phase.
5. `syntax` owns malformed import grammar and other token-shape or grammar-shape failures.
6. `manifest/import resolution` owns failures to resolve project space, reserved stdlib space, dependency graph, module path, or environment source.
7. `linking` owns assembly of visible declarations across files and modules.
8. `linking` therefore owns:
- import of non-exported names,
- unresolved barrel entries against module declarations,
- collisions between visible imported names from different canonical origins,
- local-versus-import visibility collisions,
- callable-origin conflicts across module boundaries,
- and duplicate canonical builtin or host identities in one resolved environment.
9. `static semantics` owns declaration validity, type checking, member lookup once the receiver type is known, and overload resolution within an already-formed visible callable set.
10. Member lookup failure on an already-typed builtin receiver belongs to `static semantics`.
11. Ambiguity between different imported origins must be rejected in `linking` before any callsite analysis.
## 3. Phase Roles
### 3.1 Syntax
`syntax` owns failures that arise before module or declaration assembly.
Examples:
- malformed import declaration shape,
- malformed alias syntax,
- malformed declaration grammar,
- unexpected tokens.
### 3.2 Manifest and Import Resolution
`manifest/import resolution` owns failures of locating and loading the source of a module or reserved environment.
Examples:
- unresolved `@project:*` dependency target,
- unresolved reserved stdlib module,
- invalid project-space selection,
- failure to load the module source from the selected environment.
### 3.3 Linking
`linking` owns the assembly of the visible declaration space after modules have been found and loaded.
Examples:
- imported name is not exported by the target module,
- `mod.barrel` entry does not resolve against declarations of the module,
- two imports expose the same visible name from different canonical origins,
- a local visible declaration collides with an imported visible declaration,
- callable-origin conflicts across module boundaries,
- duplicate canonical builtin identities in one resolved environment,
- duplicate canonical host identities in one resolved environment.
### 3.4 Static Semantics
`static semantics` owns validation and typing of the already-linked visible program.
Examples:
- duplicate local declarations,
- invalid declaration validity conditions,
- type mismatch,
- member lookup on a known receiver type,
- overload resolution inside one already-visible callable set.
## 4. Linking As A Real Normative Phase
PBS keeps `linking` as a distinct normative phase rather than collapsing it fully into `static semantics`.
Reasoning:
- some failures arise only when assembling visible declarations across files and modules,
- those failures are neither pure grammar failures nor ordinary local typing failures,
- and naming `linking` gives diagnostics and conformance a stable vocabulary for them.
This does not require every implementation to expose one identical internal compiler pass graph.
It only requires the externally visible failure ownership to map back to this normative phase model.
## 5. Barrel Matching And Visible Assembly
`mod.barrel` matching belongs to `linking`.
This means:
- failure of a barrel item to resolve to a declaration of the module is not merely a syntax issue,
- and it is not deferred until later typing or lowering.
Barrel matching is part of assembling the module's visible declaration contract.
## 6. Imported-Origin Conflicts
Conflicts between different imported origins are rejected in `linking` immediately.
This means:
- the implementation must not wait for a specific callsite or use-site,
- overload resolution is not allowed to rescue ambiguity between different imported origins,
- and visible declaration conflicts are resolved structurally before later typing work proceeds.
## 7. Builtin Receiver Lookup
Member lookup on a builtin receiver whose type is already known remains `static semantics`.
The reason is:
- by that point the relevant shell visibility work has already completed,
- and the remaining question is ordinary typed member validity rather than module assembly.
## 8. Invariants
- The phase model exposed to users and conformance must remain deterministic.
- `linking` remains the phase for visible declaration assembly across module boundaries.
- Callsite analysis must not be used to resolve imported-origin conflicts.
- `static semantics` operates on an already-linked visible declaration space.
- Syntax and manifest/import resolution remain narrower than linking and do not absorb visible-name conflicts that arise only after module loading.
## 9. Explicit Non-Decisions
This decision record does not yet close:
- the full diagnostics-code taxonomy,
- whether implementations expose one explicit user-facing `linking` label or a mapped equivalent wording class,
- the final artifact-facing phase vocabulary after lowering and verification specs close,
- and the exact implementation architecture of one compiler pipeline.
## 10. Spec Impact
This decision should feed at least:
- `docs/pbs/specs/14. Name Resolution and Module Linking Specification.md`
- `docs/pbs/specs/11. Diagnostics Specification.md`
- `docs/pbs/specs/13. Conformance Test Specification.md`
It should also constrain future work in:
- `docs/pbs/specs/12. IR and Lowering Specification.md`
- `docs/pbs/decisions/Name Resolution - Scope, Lookup, and Imports Decision.md`
- `docs/pbs/decisions/Name Resolution - Builtin Shells and Host Owners Decision.md`
- `docs/pbs/decisions/Name Resolution - Callable Sets and Cross-Module Linking Decision.md`
## 11. Validation Notes
The intended phase split is:
- `syntax` parses declaration and import shape,
- `manifest/import resolution` finds and loads modules,
- `linking` assembles visible names across modules and barrels,
- `static semantics` validates and types the already-linked visible program.
Illustrative examples:
```pbs
import { Missing } from @a:m;
```
If `@a:m` resolves successfully but does not export `Missing`, this is a `linking` failure.
```pbs
import { f } from @a:m;
import { f } from @b:n;
```
If the canonical origins differ, this is a `linking` failure and is rejected before any callsite analysis.
```pbs
import { Vec2 } from @core:math;
value.zero()
```
If `value` is already known to have builtin type `Vec2` and `.zero` is not a valid member on that receiver type, the failure is `static semantics`.

View File

@ -0,0 +1,270 @@
# Name Resolution - Scope, Lookup, and Imports Decision
Status: Proposed
Cycle: Initial name-resolution closure pass
## 1. Context
PBS v1 needs a deterministic frontend-visible rule for:
- how module scope is formed,
- how lookup works by namespace,
- what imports actually introduce into local scope,
- how collisions between local and imported names are handled,
- and which failures belong to syntax, manifest/import resolution, static semantics, or linking.
Existing specs already fix important inputs:
- `mod.barrel` is the single source of module visibility,
- imports target modules, not files,
- only `pub` names may be imported from another module,
- PBS has distinct type, value, callable, and host-owner namespaces,
- builtin simple types `int`, `float`, `bool`, and `str` are always available in type position,
- and reserved stdlib project spaces resolve only from the selected stdlib environment.
The remaining goal of this decision is to close the minimum name-resolution baseline needed for normative frontend work around ordinary scope construction, ordinary lookup, and import naming.
## 2. Decision
PBS v1 adopts the following baseline for scope construction, lookup, and imports:
1. Top-level declarations of a module are collected across all `.pbs` files in the module before visibility filtering is applied.
2. `mod.barrel` is a visibility filter over existing module declarations, not the source of declaration existence.
3. Module-internal top-level availability does not depend on source-file order.
4. Local block scopes nest normally over module scope.
5. In value position, lookup prefers the nearest lexical value binding before any module-level or imported value.
6. Parameters and local `let` bindings participate in the same nearest lexical value-scope layer for lookup purposes.
7. In type position, visible module-local type declarations are considered before imported type declarations, and builtin simple types remain always-available reserved type names outside ordinary import competition.
8. In callable position, visible module-local callable declarations are considered before imported callable declarations.
9. In host-owner position, visible module-local host owners are considered before imported host owners.
10. The local visible name introduced by an import is always the post-alias name when an alias is present.
11. `import { X } from @project:path;` introduces the imported exported name `X` into the matching namespace.
12. `import { X as Y } from @project:path;` introduces only the local visible name `Y` into the matching namespace.
13. Alias spelling changes only the local visible name, never canonical builtin identity or canonical host identity.
14. `import { * } from @project:path;` is the whole-module import form for bringing the target module's exported names into local visibility under their exported names.
15. A collision between a module-local declaration and an imported visible name in the same namespace is a deterministic error rather than silent shadowing.
16. A collision between two imported visible names in the same namespace is not an error only when both imports denote the same canonical underlying declaration after module resolution.
17. If two imported visible names in the same namespace come from different canonical underlying declarations, the program is rejected and one of the imports must be aliased or removed.
18. `import { * } from @project:path;` does not create a first-class module object, module namespace value, or other source-visible binding by itself; it only introduces the exported names of the target module.
19. A module-local function and an imported function with the same visible name produce a deterministic error in this closure pass.
20. Non-callable namespaces do not merge by name.
## 3. Scope Construction
### 3.1 Module collection
For one module:
- the compiler collects top-level declarations from all `.pbs` files in that module,
- forms one module-level declaration space,
- then applies `mod.barrel` to determine `mod` and `pub` visibility.
This means:
- declaration existence is not derived from barrel entries,
- and module-internal declaration availability is not ordered by file traversal.
### 3.2 Lexical scope
Inside executable bodies:
- lexical block scope is nested normally,
- nearest local bindings win within value lookup,
- and lexical nesting remains independent from cross-module visibility.
## 4. Lookup By Namespace
### 4.1 Value position
Value-position lookup follows this order:
1. nearest local lexical bindings,
2. parameters in the current lexical function scope,
3. visible module-local values,
4. visible imported values.
For lookup purposes, parameters and local `let` bindings are one nearest lexical value layer.
The distinction between them may still matter for diagnostics wording.
### 4.2 Type position
Type-position lookup follows this order:
1. visible module-local type declarations,
2. visible imported type declarations,
3. builtin simple types `int`, `float`, `bool`, and `str` as always-available reserved type names.
Builtin simple types are not treated as ordinary imported declarations and do not participate in ordinary import competition.
### 4.3 Callable position
Callable-position lookup follows this order:
1. visible module-local callables,
2. visible imported callables.
If a visible module-local function name and a visible imported function name are the same, the program is rejected in this closure pass rather than merged.
### 4.4 Host-owner position
Host-owner lookup follows this order:
1. visible module-local host owners,
2. visible imported host owners.
Host-owner lookup remains separate from type, value, and callable lookup.
## 5. Import Surface
### 5.1 Named import
`import { X } from @project:path;`:
- resolves the target module,
- checks that `X` is exported and importable from that module,
- and introduces `X` into the corresponding namespace locally.
### 5.2 Aliased import
`import { X as Y } from @project:path;`:
- resolves the same exported declaration as the non-aliased form,
- but introduces only `Y` as the local visible name.
Alias spelling does not change canonical identity governed elsewhere.
### 5.3 Whole-module import
`import { * } from @project:path;`:
- validates and resolves the target module,
- introduces the target module's exported visible names under their exported spellings,
- but does not create a module-valued binding,
- does not create a namespace object,
- and does not authorize qualified member access by itself.
If PBS later wants module-object or namespace-qualified source semantics, that must be added explicitly rather than inferred from this form.
## 6. Collision Policy
### 6.1 Local versus imported
If a module-local declaration and an imported declaration produce the same visible name in the same namespace:
- the program is rejected,
- and the implementation must not silently shadow the imported declaration or the local declaration.
This includes function names.
In this closure pass, a local function and an imported function with the same visible name are rejected rather than merged.
### 6.2 Imported versus imported
If two imports produce the same visible name in the same namespace:
- the program is not rejected if both imports resolve to the same canonical underlying declaration after module resolution,
- but the duplicate import is still redundant,
- and the program is rejected if the imports resolve to different canonical underlying declarations.
### 6.3 Namespace separation
Names in different namespaces do not collide merely by spelling.
For example:
- a host owner and a type declaration do not collide by spelling alone,
- because host-owner namespace remains distinct from type namespace.
## 7. Relationship To Later Name-Resolution Decisions
This decision is intentionally limited to:
- ordinary scope construction,
- ordinary namespace lookup,
- import naming,
- and ordinary collision policy.
Later name-resolution decisions close:
- reserved builtin shells and host owners,
- callable-set visibility across modules,
- and the final phase boundary between syntax, manifest/import resolution, linking, and static semantics.
## 8. Invariants
- Name lookup must be deterministic.
- Module-internal top-level declaration availability must not depend on file order.
- `mod.barrel` remains a visibility mechanism rather than a declaration source.
- Imports must not invent first-class module-object semantics accidentally.
- The effective visible name of an import is always the post-alias name when an alias is present.
- Builtin simple types remain a reserved always-available core type set, distinct from ordinary imported declarations.
- Implementations must not assume silent local-over-import shadowing.
- Implementations must not merge local and imported function names automatically.
## 9. Explicit Non-Decisions
This decision record does not yet close:
- reserved-shell-specific lookup and collision details,
- callable-set import behavior across module boundaries beyond the local-versus-import collision baseline,
- and backend-facing lowering consequences of the resolved lookup model.
## 10. Spec Impact
This decision should feed at least:
- `docs/pbs/specs/14. Name Resolution and Module Linking Specification.md`
- `docs/pbs/specs/11. Diagnostics Specification.md`
- `docs/pbs/specs/13. Conformance Test Specification.md`
It should also constrain future work in:
- `docs/pbs/specs/12. IR and Lowering Specification.md`
- `docs/pbs/decisions/Name Resolution - Builtin Shells and Host Owners Decision.md`
- `docs/pbs/decisions/Name Resolution - Callable Sets and Cross-Module Linking Decision.md`
- `docs/pbs/decisions/Name Resolution - Linking Phase Boundary Decision.md`
## 11. Validation Notes
The intended baseline is:
- all top-level declarations in a module exist before barrel filtering,
- lookup is namespace-specific and deterministic,
- the effective visible import name is the alias name when an alias is present,
- whole-module import through `import { * } from @project:path;` introduces exported names rather than a module object,
- local/import collisions are rejected rather than shadowed,
- and builtin simple types remain reserved always-available names outside ordinary import competition.
Illustrative examples:
```pbs
import { Foo } from @a:m;
declare const Foo: int = 1;
```
This is rejected as a local-versus-import collision in the value namespace.
```pbs
import { f } from @a:m;
import { f } from @b:n;
```
This is rejected because the visible imported names match but the canonical origins differ.
```pbs
fn f(a: int) -> int { ... }
import { f } from @a:m;
```
This is rejected as a local-versus-import collision in callable position.
```pbs
import { * } from @a:m;
```
This introduces the exported visible names of `@a:m`, but does not introduce a source-visible module object in v1.
```pbs
import { A as aaa } from @a:m;
```
The visible local declaration name is `aaa`, not `A`.

View File

@ -1,29 +1,37 @@
# PBS Name Resolution and Module Linking Specification # PBS Name Resolution and Module Linking Specification
Status: Draft v0 (Skeleton) Status: Draft v1 (Normative)
Applies to: scope formation, name lookup, imports, exports, barrel visibility, cross-module resolution, and semantic linking of PBS programs Applies to: scope formation, name lookup, imports, exports, barrel visibility, cross-module resolution, and semantic linking of PBS programs
## 1. Purpose ## 1. Purpose
This document will define the normative name-resolution and semantic-linking model for PBS. This document defines the normative name-resolution and semantic-linking model for PBS v1.
Its purpose is to make the visible declaration space of a PBS program deterministic enough that:
- frontend implementations do not invent lookup or collision rules,
- diagnostics can classify failures consistently,
- conformance can test source-facing rejection classes,
- and lowering begins from an already-linked visible program.
## 2. Scope ## 2. Scope
This document is intended to define: This document defines:
- scope formation and lookup order, - module scope construction,
- namespace separation and collision rules, - lookup order by namespace,
- import and export resolution, - import and alias resolution,
- barrel-mediated visibility and module linking, - barrel-mediated visibility,
- cross-file and cross-module symbol resolution, - reserved stdlib shell resolution for builtin and host-backed surfaces,
- resolution of reserved stdlib shells for host-backed and VM-owned surfaces, - callable visibility across modules,
- linkage-time rejection conditions before lowering. - and linking-time rejection conditions before lowering.
This document does not define: This document does not define:
- runtime execution behavior, - runtime execution behavior,
- optimizer or backend internals, - optimizer or backend internals,
- full PBX binary linking. - full PBX binary linking,
- or loader/runtime artifact behavior after lowering.
## 3. Authority and Precedence ## 3. Authority and Precedence
@ -34,8 +42,10 @@ Normative precedence:
3. `5. Manifest, Stdlib, and SDK Resolution Specification.md` 3. `5. Manifest, Stdlib, and SDK Resolution Specification.md`
4. `6. VM-owned vs Host-backed.md` 4. `6. VM-owned vs Host-backed.md`
5. `6.1. Intrinsics and Builtin Types Specification.md` 5. `6.1. Intrinsics and Builtin Types Specification.md`
6. `18. Standard Library Surface Specification.md` 6. `6.2. Host ABI Binding and Loader Resolution Specification.md`
7. This document 7. `8. Stdlib Environment Packaging and Loading Specification.md`
8. `18. Standard Library Surface Specification.md`
9. This document
If a rule here conflicts with higher-precedence specs, it is invalid. If a rule here conflicts with higher-precedence specs, it is invalid.
@ -48,6 +58,7 @@ This document depends on, at minimum:
- `5. Manifest, Stdlib, and SDK Resolution Specification.md` - `5. Manifest, Stdlib, and SDK Resolution Specification.md`
- `6. VM-owned vs Host-backed.md` - `6. VM-owned vs Host-backed.md`
- `6.1. Intrinsics and Builtin Types Specification.md` - `6.1. Intrinsics and Builtin Types Specification.md`
- `6.2. Host ABI Binding and Loader Resolution Specification.md`
- `8. Stdlib Environment Packaging and Loading Specification.md` - `8. Stdlib Environment Packaging and Loading Specification.md`
- `18. Standard Library Surface Specification.md` - `18. Standard Library Surface Specification.md`
@ -63,97 +74,300 @@ The following inputs are already fixed elsewhere and must not be contradicted he
- Reserved stdlib/interface modules may expose compile-time-only shells for both host-backed and VM-owned surfaces. - Reserved stdlib/interface modules may expose compile-time-only shells for both host-backed and VM-owned surfaces.
- Source-visible builtin names are resolved through imported builtin shell declarations rather than by hardcoded source spelling alone. - Source-visible builtin names are resolved through imported builtin shell declarations rather than by hardcoded source spelling alone.
- Canonical builtin identity and canonical intrinsic identity are governed by builtin metadata rather than by the imported PBS-visible declaration name alone. - Canonical builtin identity and canonical intrinsic identity are governed by builtin metadata rather than by the imported PBS-visible declaration name alone.
- Canonical host identity is governed by host metadata rather than by imported owner spelling alone.
## 6. Initial Section Targets ## 6. Module Scope Construction
At minimum, the completed document should contain normative sections for: ### 6.1 Module collection
1. scope construction, For one module:
2. lookup order by namespace,
3. import and alias resolution,
4. barrel export matching and linking,
5. reserved stdlib shell resolution for builtin and host-backed surfaces,
6. duplicate and shadowing rules,
7. cross-module resolution failures.
## 7. TODO - the compiler collects top-level declarations from all `.pbs` files in that module,
- forms one module-level declaration space,
The following items remain to be closed in future agenda discussion. - and only then applies `mod.barrel` to determine `mod` and `pub` visibility.
- Exact lookup order across local bindings, top-level declarations, imports, and reserved intrinsic surfaces.
- Whether shadowing rules differ by namespace or declaration kind.
- Whether semantic linking is fully part of static semantics or split into a distinct phase contract.
- Exact rejection model for ambiguous cross-module overload visibility and barrel-linked callable sets.
- Whether stdlib/interface-module linking imposes extra restrictions beyond ordinary modules.
- Whether builtin shell declarations may be synthesized by the toolchain in addition to being provided by stdlib modules.
## 8. Non-Goals
- Reopening the already-settled import surface syntax.
- Defining runtime loader behavior.
- Freezing one compiler symbol-table implementation.
## 9. Exit Criteria
This document is ready to move beyond skeleton status only when:
1. name lookup is deterministic across all relevant scopes,
2. barrel and module linking behavior is explicit,
3. reserved stdlib shell resolution is explicit for host-backed and VM-owned surfaces,
4. cross-module ambiguity and failure cases are normatively defined,
5. the document no longer relies on unresolved `TODO` items for ordinary v1 resolution behavior.
## 10. Reserved Stdlib Shell Resolution
Reserved stdlib modules may expose compile-time-only declarations that do not
behave like ordinary user-authored implementation bodies.
Rules: Rules:
- `declare host` surfaces exported from reserved stdlib modules resolve in the host-owner namespace. - declaration existence is not derived from barrel entries,
- `declare builtin type` surfaces exported from reserved stdlib modules resolve in the type namespace. - module-internal top-level declaration availability does not depend on source-file order,
- builtin constants exported from reserved stdlib modules resolve in the value namespace. - and `mod.barrel` is a visibility filter over existing declarations rather than a declaration source.
- builtin intrinsic methods are not imported as free functions; they are resolved through the imported builtin type declaration of the receiver type.
- canonical host identity is not derived from the imported host owner spelling,
- and canonical builtin/intrinsic identity is not derived solely from the imported builtin type spelling.
Example: ### 6.2 Lexical scope
- importing `Vec2` from `@core:math` may introduce the PBS-visible type name `Vec2`, Inside executable bodies:
- while the declaration's metadata may still lower that type to canonical builtin identity `("vec2", 1)`.
## 11. Import and Barrel Rules for Builtin Shells - lexical block scope nests normally over module scope,
- nearest local bindings win within value lookup,
- and lexical nesting remains independent from cross-module visibility.
Builtin shells follow ordinary module visibility rules at the source level and ## 7. Lookup Order By Namespace
special metadata rules at lowering time.
Lookup in PBS remains namespace-based.
### 7.1 Value position
Value-position lookup follows this order:
1. nearest local lexical bindings,
2. parameters in the current lexical function scope,
3. visible module-local values,
4. visible imported values.
For lookup purposes, parameters and local `let` bindings participate in one nearest lexical value layer.
### 7.2 Type position
Type-position lookup follows this order:
1. visible module-local type declarations,
2. visible imported type declarations,
3. builtin simple types `int`, `float`, `bool`, and `str` as always-available reserved type names.
Builtin simple types:
- are not treated as ordinary imported declarations,
- do not participate in ordinary import competition,
- and remain always available without import.
### 7.3 Callable position
Callable-position lookup follows this order:
1. visible module-local callables,
2. visible imported callables.
Callable lookup operates only on already-formed visible callable sets as defined later in this document.
### 7.4 Host-owner position
Host-owner lookup follows this order:
1. visible module-local host owners,
2. visible imported host owners.
Host-owner lookup remains separate from type, value, and callable lookup.
## 8. Import Surface And Naming
### 8.1 Effective local imported name
The local visible name introduced by an import is always the post-alias name when an alias is present.
Rules: Rules:
- a builtin type, builtin constant, or host owner may be imported only if it is exported through the target module's `mod.barrel`, - `import { X } from @project:path;` introduces the visible local name `X`,
- aliasing an imported builtin type changes only the source-visible local name, not the canonical builtin identity declared by metadata, - `import { X as Y } from @project:path;` introduces only the visible local name `Y`,
- aliasing an imported builtin constant changes only the local binding name, not the canonical builtin constant identity declared by metadata, - and alias spelling changes only the local visible name, never canonical builtin identity or canonical host identity.
- aliasing an imported host owner changes only the source-visible local name, not the canonical host identity declared by `Host(...)`,
- barrel matching for builtin declarations is declaration-based rather than executable-body based,
- and resolution must complete before lowering begins.
## 12. Lookup Order for Builtin and Intrinsic Surfaces ### 8.2 Named import
Lookup remains namespace-based. `import { X } from @project:path;`:
- resolves the target module,
- checks that `X` is exported and importable from that module,
- and introduces `X` into the corresponding namespace locally.
### 8.3 Whole-module import
`import { * } from @project:path;`:
- resolves the target module,
- introduces the target module's exported visible names under their exported spellings,
- but does not create a module-valued binding,
- does not create a namespace object,
- and does not authorize qualified member access by itself.
If PBS later wants module-object or namespace-qualified source semantics, that must be added explicitly rather than inferred from this import form.
## 9. Collision And Ambiguity Rules
### 9.1 Local versus imported
If a module-local declaration and an imported declaration produce the same visible name in the same namespace:
- the program is rejected,
- and the implementation must not silently shadow either declaration.
This includes function names.
A module-local function and an imported function with the same visible name are rejected rather than merged.
### 9.2 Imported versus imported
If two imports produce the same visible name in the same namespace:
- the program is not rejected only when both imports resolve to the same canonical underlying declaration after module resolution,
- such same-origin duplication is redundant but tolerated,
- and the program is rejected if the imports resolve to different canonical underlying declarations.
### 9.3 Namespace separation
Names in different namespaces do not collide merely by spelling.
For example:
- a host owner and a type declaration do not collide by spelling alone,
- because host-owner namespace remains distinct from type namespace.
### 9.4 Non-callable names do not merge
Non-callable namespaces do not merge by name.
## 10. Callable Sets And Cross-Module Linking
### 10.1 Imported callable set
`import { f } from @project:path;` imports the exported callable set named `f` from the target module.
Rules: Rules:
- type-position lookup considers visible builtin type declarations alongside other visible type declarations, - the imported callable set for `f` consists only of those overload signatures of `f` that are exported through that module's `mod.barrel`,
- value-position lookup considers visible builtin constants alongside other visible values, - non-exported overloads in the target module do not participate in the imported callable set,
- callable lookup does not treat builtin intrinsic members as top-level callable declarations, - and import operates on the exported callable name rather than on one overload at a time.
- member lookup on a builtin-typed receiver considers builtin projection fields first as field-like surfaces of that builtin declaration,
- method lookup on a builtin-typed receiver considers builtin intrinsic member signatures declared by that builtin shell,
- and host-owner lookup remains separate from builtin type lookup.
This preserves the distinction between: ### 10.2 Same-origin versus different-origin callable imports
- imported builtin type shells, If the same visible callable name is imported more than once:
- imported host owners,
- and compiler-recognized intrinsic method surfaces on existing core forms such as `optional` and enums. - same-origin duplicate imports are tolerated as redundancy when they resolve to the same canonical callable-set origin,
- different-origin imports are rejected immediately,
- and the implementation must not wait until a callsite to reject different-origin callable-name collisions.
### 10.3 Local versus imported callable names
If a module-local function name and an imported function name are the same visible callable name:
- the program is rejected immediately,
- and the implementation must not merge the local and imported callable sets.
### 10.4 Overload-resolution boundary
PBS separates:
- callable-set formation,
- and overload resolution at a callsite.
Rules:
- the implementation must first form one unambiguous visible callable set,
- only then may ordinary overload resolution operate on the candidate signatures of that set,
- and callsite context must not be used to resolve ambiguity between different imported callable origins.
## 11. Reserved Stdlib Shell Resolution
### 11.1 Reserved shells as imported declarations
Source-visible declarations exposed by reserved builtin shells are imported into their ordinary corresponding namespaces.
This means:
- builtin-exposed types enter type lookup as imported type declarations,
- builtin-exposed constants enter value lookup as imported values,
- and host owners enter host-owner lookup as imported host-owner declarations.
`declare builtin type` itself remains a reserved stdlib/toolchain declaration form rather than ordinary user-authored source syntax.
### 11.2 Builtin simple types versus imported builtin shells
PBS distinguishes:
- the predeclared builtin simple-type set `int`, `float`, `bool`, `str`,
- and imported builtin shells such as `Vec2` or `Color`.
Imported builtin shells are not promoted to the same always-available status as builtin simple types.
### 11.3 Intrinsic member lookup
Builtin intrinsic methods:
- never participate in free lookup,
- are not introduced by import as free functions,
- and are resolved only through member lookup on a receiver already known to have the corresponding builtin type.
Alias changes to the imported builtin type affect only the local visible owner spelling, not the canonical builtin or intrinsic identity.
### 11.4 Collision policy for reserved shells
Reserved builtin shells and host owners follow the same collision policy as ordinary imported declarations.
Rules:
- same visible name in the same namespace is an error unless another already-closed rule explicitly permits otherwise,
- reserved-shell status does not authorize silent shadowing,
- and host owners do not collide with types or values merely by spelling because host-owner namespace remains separate.
### 11.5 Reserved project-space policy
For source-level policy:
- builtin shells are preferred under `@core:*`,
- and host-facing capability surfaces remain the natural fit for `@sdk:*`.
### 11.6 Canonical identity
Canonical builtin identity and canonical host identity are not derived from local alias spelling.
Duplicate canonical builtin identities or duplicate canonical host identities in one resolved environment are rejected as linking failures.
## 12. Linking Phase Boundary And Deterministic Failures
### 12.1 Normative phase baseline
PBS v1 adopts the following normative phase baseline for name resolution and related frontend failures:
1. `syntax`
2. `manifest/import resolution`
3. `linking`
4. `static semantics`
### 12.2 Syntax
`syntax` owns malformed import grammar and other token-shape or grammar-shape failures.
### 12.3 Manifest and import resolution
`manifest/import resolution` owns failures to resolve:
- project space,
- reserved stdlib space,
- dependency graph target,
- module path,
- or environment source.
### 12.4 Linking
`linking` owns assembly of visible declarations across files and modules.
At minimum, `linking` owns:
1. import of non-exported names,
2. unresolved barrel entries against module declarations,
3. collisions between visible imported names from different canonical origins,
4. local-versus-import visibility collisions,
5. callable-origin conflicts across module boundaries,
6. duplicate canonical builtin identities in one resolved environment,
7. duplicate canonical host identities in one resolved environment.
### 12.5 Static semantics
`static semantics` owns:
1. duplicate local declarations,
2. declaration validity,
3. type checking,
4. member lookup once the receiver type is known,
5. overload resolution within an already-formed visible callable set.
Member lookup failure on an already-typed builtin receiver therefore belongs to `static semantics`.
### 12.6 Imported-origin conflicts are rejected before callsite analysis
Conflicts between different imported origins are rejected in `linking` immediately.
The implementation must not:
- wait for a specific callsite,
- use overload resolution to rescue imported-origin ambiguity,
- or defer visible declaration conflicts to later typing work.
## 13. Deterministic Resolution Failures ## 13. Deterministic Resolution Failures
@ -162,8 +376,30 @@ At minimum, a conforming implementation must reject:
1. import of a non-exported builtin type shell, 1. import of a non-exported builtin type shell,
2. import of a non-exported builtin constant shell, 2. import of a non-exported builtin constant shell,
3. import of a non-exported host owner shell, 3. import of a non-exported host owner shell,
4. ambiguous cross-module visibility of builtin declarations after barrel resolution, 4. import of any non-exported name from a resolved module,
5. duplicate visible builtin type declarations that claim the same canonical builtin identity in one resolved environment, 5. unresolved barrel entries against module declarations,
6. duplicate visible builtin constants that claim the same canonical builtin constant identity in one resolved environment, 6. local-versus-import same-namespace collisions,
7. member lookup on a builtin receiver where the imported builtin shell does not declare the requested field or intrinsic member, 7. imported same-name collisions from different canonical origins,
8. any resolution path that attempts to derive host or builtin canonical identity from alias spelling alone. 8. local-versus-import function-name collisions,
9. duplicate visible builtin type declarations that claim the same canonical builtin identity in one resolved environment,
10. duplicate visible builtin constants that claim the same canonical builtin constant identity in one resolved environment,
11. duplicate visible host owners that claim the same canonical host identity in one resolved environment,
12. member lookup on a builtin receiver where the imported builtin shell does not declare the requested field or intrinsic member,
13. any resolution path that attempts to derive host or builtin canonical identity from alias spelling alone.
## 14. Non-Goals
- Reopening the already-settled import surface syntax.
- Defining runtime loader behavior.
- Freezing one compiler symbol-table implementation.
- Freezing one internal compiler pass graph.
## 15. Exit Criteria
This document is ready to move beyond its current draft status only when:
1. name lookup is deterministic across all relevant scopes,
2. barrel and module linking behavior is explicit,
3. reserved stdlib shell resolution is explicit for host-backed and VM-owned surfaces,
4. cross-module callable visibility and ambiguity behavior are normatively defined,
5. and the phase ownership of source-facing name-resolution failures is explicit enough for diagnostics and conformance.

View File

@ -0,0 +1,537 @@
# PR — lsp-base (LSP MVP)
**Branch:** `pr-08-lsp-mvp`
## Briefing
Queremos um **LSP mínimo funcional** que já permita trabalhar PBS no VSCode com:
* erros aparecendo (diagnostics)
* navegação básica (goto definition)
* visão estrutural (document/workspace symbols)
Regras-chave (MVP):
* `didChange` será **full-text**
* rebuild será **coarse** (recompila o projeto inteiro) sempre que qualquer arquivo muda
* sem incremental analysis ainda
* comentários extensivos com exemplos se necessário e em inglês sempre
Este PR não inclui semantic tokens nem completion (virão nos PRs seguintes).
---
## Alvo (Features)
### LSP endpoints
* ✅ `initialize`
* ✅ `textDocument/didOpen`
* ✅ `textDocument/didChange` (FULL)
* ✅ `textDocument/didClose`
* ✅ `textDocument/documentSymbol`
* ✅ `workspace/symbol`
* ✅ `textDocument/definition`
* ✅ `textDocument/publishDiagnostics`
### Modelo de build
* `AnalysisDb` em memória
* `FileDb` (uri → texto)
* `rebuild()` recompila **workspace inteiro** e produz um snapshot
---
## Design (como deve funcionar)
### 1) AnalysisDb: estado e snapshot
**Objetivo:** separar “estado de arquivos” de “resultado de análise”.
Estruturas recomendadas:
```rust
pub struct AnalysisDb {
pub file_db: FileDb,
pub revision: u64,
pub active_rebuild: Option<RebuildHandle>,
pub last_good: Option<AnalysisSnapshot>,
}
pub struct AnalysisSnapshot {
pub diagnostics: Vec<Diagnostic>,
pub symbols: SymbolIndex, // index por Project/Module (coarse)
pub node_to_symbol: NodeToSymbol, // para definition
pub def_index: DefIndex, // opcional se você já tiver
pub ast: AstArena, // opcional (mas útil para nodes)
}
```
> Observação: use os tipos reais do seu compiler. O importante é ter um “snapshot” único que o LSP consulta.
### 2) Fluxo de rebuild
* `didOpen/didChange`:
* atualizar `file_db` com texto atual
* incrementar `revision`
* disparar `request_rebuild()` (coalescing)
* `request_rebuild()`:
* cancela rebuild anterior se houver
* agenda um rebuild assíncrono (tokio task)
* no fim, se não estiver cancelado e se `revision` não mudou, grava `last_good` e publica diagnostics
### 3) Diagnostics
* O compiler já deve produzir diagnostics com `Span { file, start, end }` em bytes.
* Para publicar, converter:
* `Span``lsp_types::Range` via `TextIndex` (já existe do refactor)
Regra:
* Diagnóstico sem `Span` (ou span inválido) deve ser **ignorado** ou degradado para range 0..0.
### 4) Definition
Ao receber `textDocument/definition`:
1. converter `Position` → byte offset (com `TextIndex`)
2. encontrar `NodeId` no ponto
3. resolver `NodeId -> SymbolId` via `node_to_symbol`
4. pegar `Symbol.decl_span`
5. converter `decl_span``Location`
> Se `NodeId` não existir ou não resolver símbolo: retornar `None`.
### 5) documentSymbol / workspaceSymbol
* `documentSymbol`: filtrar símbolos cujo `decl_span.file` == arquivo da request.
* `workspaceSymbol`: busca textual simples (contains/case-insensitive) nos nomes de símbolos (coarse) e retorna top N.
---
## Tarefas de implementação (checklist técnico)
### Capabilities em `initialize`
Declarar no server:
* `textDocumentSync: Full`
* `definitionProvider: true`
* `documentSymbolProvider: true`
* `workspaceSymbolProvider: true`
* `diagnosticProvider`: use publishDiagnostics (push)
### didOpen
* upsert texto
* request_rebuild
### didChange (Full)
* receber texto completo do doc
* upsert texto
* request_rebuild
### didClose
* remover do file_db (ou marcar fechado)
* publicar diagnostics vazios para o arquivo fechado
### Conversions
* `uri <-> FileId`: FileDb precisa mapear URI para FileId estável.
* `Span -> Range`: usar `TextIndex` do texto atual do arquivo.
* `Position -> byte`: usar `TextIndex`.
### Node lookup
Você precisa de uma função no snapshot (ou util) tipo:
* `fn node_at(file: FileId, byte: u32) -> Option<NodeId>`
MVP aceitável:
* se você ainda não tiver um índice de nodes por span, pode:
* usar AST arena e fazer busca linear na árvore (aceitável no coarse MVP)
---
## Test Harness (mínimo, mas real)
### Opção A (preferida): tests com `tower-lsp` client in-process
Criar um teste `tests/lsp_mvp.rs`:
* sobe o backend LSP em memória
* envia:
* `initialize`
* `didOpen` com um fixture PBS (2 arquivos)
* espera `publishDiagnostics` (pode interceptar via `Client` mock)
* chama `definition` em uma posição de uso e verifica que retorna `Location` do `decl_span`
**Aceite:**
* definition retorna arquivo correto
* range bate com span convertido
### Opção B (mais simples): unit tests nos adaptadores
Se o harness LSP demorar, pelo menos criar:
* `span_to_range_uses_utf16()`
* `position_to_byte_roundtrip()`
* `definition_resolves_to_decl_span()` usando snapshot fake.
---
## Checklist de aceite (obrigatório)
* [ ] `cargo test -q` passa no workspace
* [ ] VSCode: abrir arquivo `.pbs` mostra diagnostics (pelo menos 1 erro sintático)
* [ ] VSCode: `Go to Definition` funciona para símbolo resolvido
* [ ] VSCode: Outline mostra `documentSymbol`
* [ ] Mudanças em um arquivo disparam rebuild e atualizam diagnostics
* [ ] Unicode: diagnostics não ficam “desalinhados” (teste manual com `ação`/emoji)
---
## Fora de escopo (explicitamente)
* semantic tokens
* completion
* references/rename
* hover/signatureHelp
* incremental analysis e cancelation avançada
---
# PR — lsp-hightlight-base (Semantic Tokens — lexer-first)
**Branch:** `pr-12a-lsp-semantic-lexer`
## Briefing
Queremos **highlight no VSCode via LSP**, sem depender de resolver e sem TextMate.
Estratégia:
* Implementar `textDocument/semanticTokens/full`.
* Gerar tokens **lexer-first**: comments/strings/numbers/keywords/identifiers.
* Converter spans (bytes) para LSP positions (UTF-16) usando `TextIndex`.
* comentários extensivos com exemplos se necessário e em inglês sempre
Isso entrega um highlight “bom o suficiente” e muito estável, mesmo com arquivo com erro de parse.
---
## Alvo (Features)
* ✅ `textDocument/semanticTokens/full`
* ✅ `SemanticTokensLegend` consistente
* ✅ tokens derivados do lexer
Opcional (não obrigatório neste PR):
* `semanticTokens/range`
* `semanticTokens/full/delta`
---
## Design
### 1) Legend fixa
Escolher um conjunto pequeno de token types:
* `comment`
* `string`
* `number`
* `keyword`
* `operator` (opcional)
* `variable` (para identifiers genéricos)
> Não inventar muitos tipos agora; fácil expandir depois.
### 2) Fonte de tokens
Implementar uma função no analysis/compiler layer (ou no lsp crate) que, dado:
* `FileId`
* `text: &str`
retorna `Vec<(Span, TokenType, TokenModifiers)>`.
**Importante:**
* Spans são em bytes.
* Devem ser **não sobrepostos** e preferencialmente em ordem.
### 3) Conversão para formato LSP (deltas)
LSP semantic tokens usa encoding em deltas:
* `deltaLine`, `deltaStartChar`, `length`, `tokenType`, `tokenModifiers`
Algoritmo:
1. Converter `Span.start` e `Span.end` em `(line, utf16_col)`.
2. Calcular `length` em UTF-16 units para o trecho (start..end).
3. Ordenar por `(line, col)`.
4. Emitir deltas.
Regra:
* Se `Span` cruza linhas, **quebrar** em múltiplos tokens por linha (MVP seguro).
### 4) Robustez
* Token inválido (end < start, ou fora do texto) deve ser ignorado.
* Se o arquivo não estiver no `FileDb`, retornar tokens vazios.
---
## Tarefas de implementação
### Server capabilities
No `initialize`, anunciar:
* `semanticTokensProvider` com:
* `legend`
* `full: true`
* `range: false` (por enquanto)
### Handler
Implementar `semantic_tokens_full(params)`:
* pegar `uri`
* buscar texto no `file_db`
* gerar tokens do lexer
* converter com `TextIndex`
* retornar `SemanticTokensResult::Tokens`
### Lexer tokens
Se você já tem lexer com spans:
* mapear tokens para os tipos (keyword/string/comment/number/identifier)
* keywords: pode ser por enum do token ou por tabela
Se o lexer não marca keyword vs ident:
* fallback: parse por string e classifica keywords via `HashSet<&'static str>`.
---
## Testes
### Unit tests (obrigatórios)
1. `semantic_tokens_legend_is_stable()`
* garante que legend não muda sem intenção.
2. `semantic_tokens_are_sorted_and_non_negative()`
* gera tokens em um fixture com 2 linhas
* valida que deltas nunca ficam negativos e que ordem é válida.
3. `semantic_tokens_unicode_length_utf16()`
* texto com `ação🙂`
* valida que `length` corresponde a UTF-16 (emoji conta como 2).
### Teste manual (aceite)
* Abrir `.pbs` no VSCode
* Verificar:
* strings e comentários coloridos
* keywords coloridas
* números coloridos
* identifiers coloridos (mesmo que genérico)
---
## Checklist de aceite
* [ ] `cargo test -q` passa
* [ ] VSCode: arquivos PBS ficam coloridos via LSP (sem TextMate)
* [ ] Unicode não quebra offsets
* [ ] Arquivo com erro de parse ainda tem highlight (lexer-first)
---
## Fora de escopo
* semantic tokens semântico (type vs fn vs var) — virá em `PR-12b`
* `range`/`delta`
* completion
---
# PR — lsp-completion-base (Completion mínimo)
**Branch:** `pr-11a-lsp-completion-min`
## Briefing
Queremos autocomplete **mínimo mas útil** para conseguir escrever SDK/ECS em PBS sem fricção.
Princípio:
* Não depende de scope facts, nem type facts.
* Usa somente:
* keywords/builtins
* símbolos top-level do módulo atual
* exports/imports visíveis no projeto (coarse)
Isso é suficiente para começar a programar e evoluir o LSP depois.
* comentários extensivos com exemplos se necessário e em inglês sempre
---
## Alvo (Features)
* ✅ `textDocument/completion`
* ✅ `completionItem/resolve` (opcional; pode retornar item já completo)
---
## Design
### 1) Buckets e ordenação
Retornar `CompletionList` com itens nesta prioridade:
1. Keywords (`fn`, `let`, `mutate`, `declare`, `struct`, `storage`, `if`, `else`, `for`, `return`, etc.)
2. Builtins/funções globais (ex.: `alloc`, `box`, `unbox`, `range`, etc. — conforme spec do PBS)
3. Símbolos do módulo atual (top-level)
4. Símbolos “workspace visible” (exports/imports), limitado (ex.: top 200)
**Regra de ranking simples:**
* locals (não teremos) > módulo atual > workspace
### 2) Contexto
Para completion mínimo, só precisamos:
* `uri` do documento
* `position` (para pegar prefixo)
Prefixo:
* converter `Position` -> byte
* extrair texto até o cursor e identificar o “token parcial” (regex simples `[A-Za-z_][A-Za-z0-9_]*$`)
### 3) Fonte dos símbolos
Usar o `AnalysisSnapshot` (produzido na PR-08) para expor:
* `fn symbols_in_file(file: FileId) -> Vec<SymbolId>`
* `fn symbols_in_module(project: ProjectId, module: ModuleId) -> Vec<SymbolId>`
* `fn workspace_symbols() -> impl Iterator<Item = (name, kind, decl_span)>`
MVP aceitável:
* `workspace_symbols()` pode ser um vetor “flatten” pré-calculado no snapshot.
### 4) CompletionItem
Mapeamento para `CompletionItemKind`:
* functions -> `FUNCTION`
* types -> `CLASS` (ou `STRUCT` se quiser)
* modules -> `MODULE`
* variables/constants -> `VARIABLE` / `CONSTANT`
Campos:
* `label`: nome
* `detail`: (opcional) `"fn"/"type"/"module"`
* `sortText`: prefixado para impor bucket (ex.: `"1_"`, `"2_"`)
* `filterText`: label
---
## Tarefas de implementação
### Capabilities
No `initialize`:
* `completionProvider: { resolveProvider: false, triggerCharacters: [".", ":"]? }`
> Para completion mínimo, nem precisa trigger chars. Pode deixar default.
### Handler `completion`
* buscar texto e `TextIndex`
* calcular prefixo
* montar lista de candidatos por bucket
* filtrar por prefixo (case-sensitive ou insensitive; escolha e documente)
* limitar tamanho
* retornar `CompletionResponse::List`
### Builtins/keywords
Criar tabelas estáticas em `prometeu-lsp`:
* `static KEYWORDS: &[&str] = ...`
* `static BUILTINS: &[&str] = ...`
---
## Testes
### Unit tests (obrigatórios)
1. `completion_extracts_prefix()`
* valida regex de prefixo
2. `completion_includes_keywords()`
* chama handler com texto vazio e posição 0
* garante que `fn` aparece
3. `completion_filters_by_prefix()`
* prefixo `"ra"` deve sugerir `range` (se builtin)
4. (se possível) `completion_includes_module_symbols()`
* usando snapshot fake ou fixture compilado pela infra da PR-08
### Teste manual (aceite)
* Abrir arquivo PBS e digitar `ra` -> aparece `range`
* Digitar nome de tipo/fn do SDK -> aparece suggestion
---
## Checklist de aceite
* [ ] `cargo test -q` passa
* [ ] VSCode: autocomplete sugere keywords e builtins
* [ ] VSCode: autocomplete sugere símbolos do módulo atual
* [ ] Não trava com rebuild coarse
---
## Fora de escopo
* locals em scope
* members (`obj.`)
* signatureHelp
* hover

View File

@ -0,0 +1,219 @@
# LSP Roadmap — do “base usable” até “LSP completo”
> Este documento é o mapa incremental pós `lsp-base`, `lsp-hightlight-base`, `lsp-completion-base`.
> A ideia é manter o LSP evoluindo sem rework, enquanto SDK/ECS/Packer avançam em paralelo.
---
## Estado atual (após os 3 PRs base)
### ✅ Já temos
* Diagnostics (publishDiagnostics)
* Definition (goto definition)
* DocumentSymbol / WorkspaceSymbol
* Semantic tokens (lexer-first)
* Completion mínimo (keywords/builtins/top-level/module/workspace)
### ❌ Ainda não temos
* references / rename
* hover / signatureHelp
* completion com locals em scope
* semantic tokens semântico (type vs fn vs var)
* incremental analysis real (por arquivo / cancelation)
* debug map pc→span integrado com traps
* code actions / formatting
---
## Próximos PRs recomendados (ordem sugerida)
## PR-09 — References + Rename (seguro)
### Objetivo
Habilitar `references`, `prepareRename`, `rename` com regras de segurança.
### Pré-requisitos
* `RefIndex` completo (SymbolId -> usos em spans)
* `NodeToSymbol` estável
* `TriviaIndex` (comments/strings) para não renomear spans proibidos
### Regras de segurança
* Não renomear símbolo não-resolvido
* Não renomear em comentário/string
* Renomear deve gerar `WorkspaceEdit` apenas para spans válidos
### Testes
* Fixture com 2 arquivos: rename atualiza todas as refs
* Fixture: rename em comentário não altera nada
---
## PR-10 — Hover + SignatureHelp
### Objetivo
* `hover`: mostrar tipo + docstring (docstring opcional)
* `signatureHelp`: para call nodes
### Pré-requisitos
* Type facts básicos: `NodeId -> TypeId`
* Modelo de assinatura: para functions (params, retorno)
### Testes
* Hover em símbolo mostra tipo
* SignatureHelp em chamada mostra params
---
## PR-11b — Completion com locals em scope
### Objetivo
Autocomplete útil para código real:
* locals/params
* shadowing correto
### Pré-requisitos
* Binder/Scope facts:
* `Position -> ScopeId`
* `ScopeId -> bindings (name -> SymbolId/TypeId)`
### Testes
* Dentro de bloco, sugere variável local
* Shadowing: sugere a mais interna
---
## PR-12b — Semantic tokens resolver-backed (semântico)
### Objetivo
Melhorar highlight:
* diferenciar `type` vs `function` vs `variable` vs `parameter` vs `module`
### Pré-requisitos
* `NodeAtPosition` confiável
* `NodeToSymbol` confiável
* `SymbolKind` consistente
### Testes
* Identificador de tipo recebe token `type`
* Função recebe token `function`
---
## PR-13 — Formatting + Code Actions (opcional)
### Objetivo
* `textDocument/formatting` (nem que seja formatter simples/estável)
* code actions básicas:
* organizar imports
* criar stub de função/struct
### Pré-requisitos
* AST pretty printer (ou formatter incremental)
---
## PR-14 — Incremental analysis + cancelation (de verdade)
### Objetivo
Sair do rebuild coarse:
* recompilar somente arquivo/módulo afetado
* cancelamento real de builds longos
* snapshots por versão
### Pré-requisitos
* Graph de dependências por módulo
* Cache de parse/resolve por arquivo
* `revision` por doc
### Testes
* Editar arquivo A não recompila projeto inteiro
* Cancel: digitar rápido não aplica resultados velhos
---
## PR-15 — Debug map (pc→span) + integração com traps
### Objetivo
Quando a VM gerar trap/runtime error:
* mapear `pc` para `Span`
* mostrar erro com localização exata
### Pré-requisitos
* SourceMap gerado no backend bytecode
* runtime expõe `pc`/contexto
### Testes
* programa que gera trap aponta para linha/col corretos
---
# Backlog adicional (nice to have)
* `workspace/didChangeWatchedFiles` (reagir a mudanças fora do editor)
* `textDocument/codeLens` (ex.: run test / run main)
* `textDocument/inlayHint`
* `workspace/diagnostic` pull-mode (se quiser)
* semanticTokens `range` e `delta` para performance
---
## Interação com SDK/ECS/Packer
### Com os 3 PRs base, você já consegue:
* escrever SDK/ECS em PBS com feedback imediato
* navegar rapidamente pelo código
* manter arquivos grandes com highlight
* usar completion para nomes de APIs
### Enquanto isso, em paralelo:
* packer pode evoluir (não depende do LSP)
* quando packer estabilizar, podemos adicionar code actions/codelens para “build/pack/run” direto no VSCode
---
## Definição de “LSP completo” (para Prometeu view-ready)
Para chamar de "completo" (na sua visão de produto):
* ✅ diagnostics
* ✅ definition
* ✅ symbols
* ✅ highlight semântico
* ✅ completion com scope + members
* ✅ hover + signatureHelp
* ✅ references + rename
* ✅ incremental + cancel
* ✅ debug map integrado com traps
> A ordem acima é incremental por valor percebido e por dependências.

View File

View File

@ -0,0 +1,564 @@
# Prometeu Packer (prometeu-packer) — Specification (Draft)
> **Status:** Draft / Community-facing
>
> This document specifies the **Prometeu Packer**, the tooling responsible for **asset management** and producing two build artifacts:
>
> * `build/assets.pa` — the ROM payload containing packed asset bytes
> * `build/asset_table.json` — a machine-readable descriptor of the packed assets
>
> The Packer is deliberately **agnostic of cartridge building**. A separate **Cartridge Builder** (outside the Packer) consumes `build/asset_table.json` to generate the final `cartridge/manifest.json`, copy `assets.pa` to the cartridge directory, and zip the cartridge into a distributable format.
---
## 1. Goals and Non-Goals
### 1.1 Goals
1. **Be the guardian of sanity** in a constantly mutating `assets/` workspace.
* Users may be disorganized.
* The directory may contain WIP, junk, unused files, duplicates, outdated exports.
* The Packer must help users identify and fix mistakes.
2. Provide a robust, deterministic, **tooling-grade** asset pipeline.
* Stable asset identity.
* Deterministic packing order.
* Reproducible output bytes.
3. Support both **raw (direct) assets** and **virtual assets**.
* Raw assets: the payload in ROM is exactly the source bytes.
* Virtual assets: the payload is derived from multiple inputs via a build pipeline (e.g., PNG + palettes → `TILES` payload).
4. Produce an output descriptor (`build/asset_table.json`) suitable for:
* a Cartridge Builder to generate the runtime manifest
* CI checks
* a future IDE / GUI tooling
5. Provide an extensive **diagnostics chain** (doctor) with structured error codes and suggested fixes.
### 1.2 Non-Goals
* The Packer **does not**:
* generate `cartridge/manifest.json`
* decide preload slots
* copy files into `cartridge/`
* compile PBS bytecode
* zip cartridges
These responsibilities belong to a separate **Cartridge Builder**.
---
## 2. Repository / Project Layout (Golden Pipeline)
The Prometeu project uses the following canonical structure:
* `src/` — PBS scripts
* `assets/` — mutable asset workspace (WIP allowed)
* `build/` — generated artifacts and caches
* `cartridge/` — final cartridge directory (produced by Cartridge Builder)
* `prometeu.json` — project description (dependencies, version, etc.)
* `sdk/` — SDK/tooling and libraries
The Packer owns the asset workspace metadata under:
* `assets/.prometeu/` — Packer control directory (registry, cache, quarantine)
---
## 3. Crate Topology
### 3.1 Crates
* **`prometeu-packer`**
* **lib**: `prometeu_packer_core`
* **bin**: `prometeu-packer`
* a thin CLI wrapper delegating to `prometeu_packer_core::run()`
* **`prometeu` dispatcher**
* provides a wrapper command **`prometeup`** (or integrated subcommand)
* delegates to `prometeu-packer` for packer operations
### 3.2 Design Principle
Treat the Packer like the compiler: core library + CLI wrapper.
* The core library enables future integrations (IDE, GUI, watch mode) without shelling out.
* CLI is a stable interface for users and CI.
---
## 4. Mental Model: A “Git-like” Asset Workspace
The Packer treats `assets/` like a **dirty working tree**.
* `assets/` can contain *anything*.
* Only the assets registered in the Packer registry are considered part of the build.
This is analogous to Git:
* working tree (chaos) vs index (truth)
The **source of truth** for “what counts” is the registry:
* `assets/.prometeu/index.json`
---
## 5. Core Concepts
### 5.1 Managed Asset
A **managed asset** is an entry in `assets/.prometeu/index.json` pointing to an **asset root directory** that contains an anchor file:
* `asset.json` (the asset specification)
Everything else is free-form.
### 5.2 Asset Identity
Each asset has stable identity:
* `asset_id: u32` — stable within the project (used by runtime/tooling)
* `asset_uuid: string` — globally unique stable identifier (useful for IDE and future migrations)
Names and paths may change, but identity remains.
### 5.3 Asset Types (Bank Targets)
Assets ultimately target a **bank type** in the runtime:
* `TILES`
* `SOUNDS`
* (future) `SPRITESHEET`, `MAP`, `FONT`, `RAW_BLOB`, etc.
The Packer does **not** define bank memory semantics. It defines the *ROM payload* and its metadata.
### 5.4 Raw vs Virtual Assets
* **Raw assets**: ROM payload equals the source bytes.
* **Virtual assets**: ROM payload is derived from input(s) via deterministic build steps.
Examples:
* PNG + palette files → `TILES` payload (indexed pixels + packed palettes)
* WAV → PCM16LE payload
* Multiple PNGs → atlas spritesheet
---
## 6. Directory Structure and Control Files
### 6.1 Workspace
`assets/` is a mutable workspace:
* users may create nested organization trees
* junk files are allowed
### 6.2 Control Directory
The Packer stores its truth + tools state in:
```
assets/
.prometeu/
index.json
cache/
fingerprints.json
build-cache.json
quarantine/
...
```
* `index.json` — registry of managed assets
* `cache/` — fingerprints and incremental build cache
* `quarantine/` — optional area to move detected junk (only by explicit user action)
---
## 7. Asset Specification: `asset.json`
`asset.json` describes:
1. **the output ROM payload** expected by runtime
2. **the build pipeline** (for virtual assets)
3. **metadata** needed by runtime/builder
This spec is modular: **each asset format** (e.g. `TILES/indexed_v1`) has its own dedicated specification document.
### 7.1 Common Fields (All Assets)
* `schema_version`
* `name`
* `type` (bank type)
* `codec` (e.g. `RAW`; future: compression)
* `inputs` (for virtual assets)
* `output` (format + required metadata)
* `build` (optional pipeline configuration)
### 7.2 Virtual Asset Pipeline Declaration
Virtual assets must be declared in a way that is:
* deterministic
* fully materialized (no silent inference)
* explicit about defaults (defaults may exist, but must be written into `asset.json` or build outputs)
---
## 8. Build Artifacts Produced by the Packer
### 8.1 `build/assets.pa`
**`assets.pa`** is the ROM asset payload used by the runtime.
**Definition:** a contiguous binary blob where each managed asset contributes a payload region.
#### Key Properties
* Deterministic asset order (by `asset_id`)
* Offsets are recorded in `build/asset_table.json`
* Alignment rules (configurable by packer, default: no alignment unless required by a format)
**Note:** `assets.pa` is intentionally simple.
* No internal header is required.
* The authoritative structure comes from the descriptor (`asset_table.json`).
Future versions may introduce chunk tables, but v1 keeps ROM simple.
### 8.2 `build/asset_table.json`
**`asset_table.json`** is the canonical descriptor output of the Packer.
It contains:
* `assets_pa` file info (size, hash)
* `asset_table[]` entries describing each payload slice
* optional diagnostics/warnings
#### Asset Table Entry
An entry describes a ROM slice and its runtime meaning:
* `asset_id` — stable u32
* `asset_uuid` — stable UUID string
* `asset_name` — stable user-facing name
* `bank_type` — e.g. `TILES`, `SOUNDS`
* `offset` — byte offset in `assets.pa`
* `size` — bytes stored in ROM
* `decoded_size` — bytes after decode (equal to `size` when `codec=RAW`)
* `codec``RAW` (future: compression)
* `metadata` — format-specific metadata needed by runtime/builder
Additional tooling fields:
* `source_root` — path to asset dir
* `inputs` — resolved input paths
* `source_hashes` — stable fingerprints of inputs
`asset_table.json` is machine-readable and designed for:
* cartridge builder consumption
* IDE visualization
* debugging
---
## 9. Determinism Rules
1. Asset packing order MUST be deterministic.
* Default: increasing `asset_id`
2. All derived outputs MUST be deterministic.
* No random seeds unless explicitly declared
* Any seed must be written to output metadata
3. Default values MUST be materialized.
* If the packer infers something, it must be written into `asset.json` (via `--fix`) or recorded in build outputs.
---
## 10. Diagnostics and the “Sanity Guardian” Chain
The Packer provides structured diagnostics:
* `code` — stable diagnostic code
* `severity``error | warning | info`
* `path` — affected file
* `message` — human friendly
* `help` — extended context
* `fixes[]` — suggested automated or manual fixes
### 10.1 Diagnostic Classes
1. **Registered Errors** (break build)
* registry entry missing anchor file
* `asset.json` invalid
* missing inputs
* format/metadata mismatch
2. **Workspace Warnings** (does not break build)
* orphaned `asset.json` (not registered)
* unused large files
* duplicate inputs by hash
3. **Policy Hints** (optional)
* naming conventions
* missing preview
### 10.2 `doctor` Modes
* `doctor` (default) — validate registry only (fast)
* `doctor --workspace` — deep scan workspace (slow)
---
## 11. Incremental Build, Cache, and Fingerprints
The Packer maintains fingerprints of inputs:
* size
* mtime
* strong hash (sha256)
Stored in:
* `assets/.prometeu/cache/fingerprints.json`
This enables:
* detecting changes
* rebuild only what changed
* producing stable reports
The cache must never compromise determinism.
---
## 12. Quarantine and Garbage Collection
### 12.1 Quarantine
The Packer can optionally move suspected junk to:
* `assets/.prometeu/quarantine/`
Rules:
* Quarantine is **never automatic** without user consent.
* Packer must explain exactly what will be moved.
### 12.2 Garbage Collection (`gc`)
The Packer can report unused files:
* files not referenced by any registered asset
* orphaned asset dirs
Actions:
* list candidates
* optionally move to quarantine
* never delete without explicit user request
---
## 13. CLI Commands (Comprehensive)
> The CLI is a stable interface; all commands are implemented by calling `prometeu_packer_core`.
### 13.1 `prometeu packer init`
Creates the control directory and initial registry:
* creates `assets/.prometeu/index.json`
* creates caches directory
### 13.2 `prometeu packer add <path> [--name <name>] [--type <TILES|SOUNDS|...>]`
Registers a new managed asset.
* does not require moving files
* can create an asset root directory if desired
* generates `asset.json` with explicit defaults
* allocates `asset_id` and `asset_uuid`
Variants:
* `add --dir` creates a dedicated asset root dir
* `add --in-place` anchors next to the file
### 13.3 `prometeu packer adopt`
Scans workspace for unregistered `asset.json` anchors and offers to register them.
* default: dry-run list
* `--apply` registers them
### 13.4 `prometeu packer forget <name|id|uuid>`
Removes an asset from the registry without deleting files.
Useful for WIP and cleanup.
### 13.5 `prometeu packer rm <name|id|uuid> [--delete]`
Removes the asset from the registry.
* default: no deletion
* `--delete` can remove the asset root dir (dangerous; must confirm in UI tooling, or require a force flag in CLI)
### 13.6 `prometeu packer list`
Lists managed assets:
* id, uuid, name
* type
* status (ok/error)
### 13.7 `prometeu packer show <name|id|uuid>`
Shows detailed information:
* resolved inputs
* metadata
* fingerprints
* last build summary
### 13.8 `prometeu packer doctor [--workspace] [--strict] [--fix]`
Runs diagnostics:
* `--workspace` deep scan
* `--strict` warnings become errors
* `--fix` applies safe automatic fixes (materialize defaults, normalize paths)
### 13.9 `prometeu packer build [--out build/assets.pa] [--table build/asset_table.json]`
Builds:
* `build/assets.pa`
* `build/asset_table.json`
Key behaviors:
* validates registry before packing
* packs assets deterministically
* for virtual assets, runs build pipelines
* records all offsets and metadata
### 13.10 `prometeu packer watch`
Watches registered inputs and registry changes.
* emits events (future)
* rebuilds incrementally
`watch` is optional in v0 but recommended.
### 13.11 `prometeu packer gc [--workspace] [--quarantine]`
Reports unused files.
* default: report only
* `--quarantine` moves candidates to quarantine
### 13.12 `prometeu packer quarantine <path> [--restore]`
Moves or restores files into/from quarantine.
---
## 14. Virtual Assets (Deep Explanation)
Virtual assets are a major capability.
### 14.1 Why Virtual Assets
* Most runtime formats should be derived from human-friendly authoring formats.
* Example:
* author uses `source.png` and palette files
* runtime expects indexed pixels + packed RGB565 palettes
### 14.2 Virtual Asset Contract
* Inputs are explicit.
* Build steps are deterministic.
* Outputs match a well-defined runtime payload format.
### 14.3 Examples of Future Virtual Assets
* `TILES/indexed_v1`: PNG + palette files → indexed pixels + packed palettes
* `SOUNDS/pcm16le_v1`: WAV → PCM16LE
* `SPRITESHEET/atlas_v1`: multiple PNG frames → atlas + metadata
Each `output.format` must have its own dedicated spec.
---
## 15. Integration with Cartridge Builder
The Cartridge Builder should:
1. Compile PBS into bytecode (e.g. `program.pbc` / `program.pbx`)
2. Call `prometeu packer build`
3. Consume `build/asset_table.json` and produce `cartridge/manifest.json`
4. Copy artifacts into `cartridge/`
5. Zip the cartridge into a distributable format (`.crt` / `.rom` / `.pro`)
The packer never touches `cartridge/`.
---
## 16. Compatibility and Versioning
* `assets/.prometeu/index.json` has `schema_version`
* `asset.json` has `schema_version`
* `build/asset_table.json` has `schema_version`
The Packer must be able to migrate older schema versions or emit actionable diagnostics.
---
## 17. Security and Trust Model
* The Packer is offline tooling.
* It must never execute untrusted scripts.
* It should treat external inputs as untrusted data.
---
## 18. Implementation Notes (Non-Normative)
* Rust implementation with a core crate + CLI wrapper.
* Prefer structured JSON serde models.
* Use stable diagnostic codes.
* Keep the build deterministic.
---
## Appendix A — Glossary
* **ROM (`assets.pa`)**: packed asset payload used by runtime
* **Descriptor (`asset_table.json`)**: mapping from logical assets to ROM slices
* **Managed asset**: registered asset with stable identity and anchor file
* **Virtual asset**: derived asset built from multiple inputs
* **Quarantine**: safe area for suspected junk
* **Doctor**: diagnostic command to keep sanity