prepare prs

This commit is contained in:
bQUARKz 2026-02-03 15:35:36 +00:00
parent f9120e740b
commit 747ac81da2
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 825 additions and 7 deletions

View File

@ -1,10 +1,39 @@
# PRs for Junie — Compiler Dependency Resolution & Linking Pipeline
## Visão de arquitetura alvo
> Goal: Move dependency resolution + linking orchestration into **prometeu_compiler** so that the compiler produces a **single fully-linked bytecode blob**, and the VM/runtime only **loads + executes**.
### Camadas
## Non-goals (for this PR set)
1. **FileDB**: texto, URI/path, line-index, snapshots.
2. **Lexer/Parser**: produz **AstArena** (NodeId + spans por nó).
3. **Binder/Resolver**: produz **SymbolArena** + índices (def/ref).
4. **Typecheck**: produz **TypeArena** + facts (node→type, symbol→type).
5. **Analysis Export**: `analysis.json` (full) + `symbols.json` (leve e estável).
6. **LSP Server**: consome `AnalysisDb` e responde requests.
* No lockfile format (yet)
* No registry (yet)
* No advanced SAT solver: first iteration is deterministic and pragmatic
* No incremental compilation (yet)
### IDs padronizados (newtypes)
* `FileId(u32)`
* `NodeId(u32)`
* `NameId(u32)` (interner)
* `SymbolId(u32)`
* `TypeId(u32)`
* `ProjectId(u32)`
* `ModuleId(u32)`
### Invariantes
* AST é **imutável** após construção (normativo na spec PBS).
* Nenhuma fase expõe referências diretas entre nós; apenas **IDs**.
* IDs externos são aceitos, mas sempre passam por **validate/resolve** (API checked).
---
## Regras para Junie (para reduzir vai-e-volta)
1. Não “inventar design”. Se algo não estiver especificado, **criar TODO** e parar.
2. Não mudar formatação/estilo fora do escopo.
3. Todo novo tipo público precisa de doc-comment curta e exemplo.
4. Toda mudança de JSON precisa:
* `schema_version` bump (se não for backward)
* teste de snapshot
5. Cada PR deve deixar `cargo test` verde.

View File

@ -0,0 +1,789 @@
# Prometeu — PR Plan (Arena-Driven) para um LSP Completo
> **Meta do Nilton / Junie workflow**: PRs extremamente prescritivos. A Junie só implementa; você revisa o design **antes** do código.
>
> **Regra de ouro:** cada PR abaixo vem com:
>
> 1. Arquivos/alvos exatos
> 2. Estruturas e assinaturas obrigatórias
> 3. Esquemas JSON (quando aplicável)
> 4. Testes (golden + unit)
> 5. Critérios de aceite “binário” (passou/não passou)
---
## Estado atual (confirmado pelo repo)
Workspace (Archive.zip) tem crates:
* `prometeu/`
* `prometeu-bytecode/`
* `prometeu-core/`
* `prometeu-compiler/` (bin `src/main.rs`, deps: `serde`, `serde_json`, `anyhow`, `clap`, e família `oxc_*`).
**Saída atual existente:** `symbols.json` (schema_version 0), exportando símbolos por projeto, com `decl_span` em `line/col` e paths absolutos. (Vamos evoluir isso no PR-07.)
---
# Templates prescritivos para PR (usar em TODAS)
## Template de descrição (copiar/colar)
* **Motivação:**
* **Mudança de modelo de dados:**
* **APIs novas / alteradas:**
* **Arquivos tocados:**
* **Testes adicionados/atualizados:**
* **Riscos & rollback:**
* **Checklist de aceite (binário):**
---
# PRs (detalhados)
## PR-00 — Infra: crates de análise + lsp (estrutura do workspace)
**Branch:** `pr-00-analysis-lsp-foundations`
### Decisão travada (LSP)
* **Biblioteca escolhida:** `tower-lsp`
* **Motivo:** reduzir boilerplate, acelerar MVP e minimizar vai-e-volta. Cancelamento/incremental será feito **por cima** (tasks async + cancel tokens).
### Objetivo
Adicionar crates novas sem mexer no pipeline existente.
### Entregas obrigatórias
1. **Novo crate:** `prometeu-analysis/`
* `Cargo.toml` (library)
* `src/lib.rs`
2. **Novo crate:** `prometeu-lsp/`
* `Cargo.toml` (bin)
* `src/main.rs`
3. Workspace root: ajustar `Cargo.toml` (se existir no root) para incluir members.
### Dependências (fixas)
* `prometeu-analysis`: `serde`, `serde_json`
* `prometeu-lsp`:
* `tower-lsp = "0.20"` (ou versão estável atual)
* `tokio` (full)
* `prometeu-analysis` (path)
### Esqueleto obrigatório (`prometeu-lsp/src/main.rs`)
```rust
use tower_lsp::{LspService, Server};
use tokio::sync::RwLock;
use std::sync::Arc;
#[derive(Default)]
struct AnalysisDb {
// FileDB, AstArena, SymbolArena, TypeArena, Diagnostics
}
struct Backend {
db: Arc<RwLock<AnalysisDb>>,
}
#[tower_lsp::async_trait]
impl tower_lsp::LanguageServer for Backend {
async fn initialize(&self, _: tower_lsp::lsp_types::InitializeParams)
-> tower_lsp::jsonrpc::Result<tower_lsp::lsp_types::InitializeResult> {
Ok(tower_lsp::lsp_types::InitializeResult {
capabilities: tower_lsp::lsp_types::ServerCapabilities {
text_document_sync: Some(tower_lsp::lsp_types::TextDocumentSyncCapability::Kind(
tower_lsp::lsp_types::TextDocumentSyncKind::FULL,
)),
// Declaramos capacidades desde já para evitar churn posterior.
definition_provider: Some(true.into()),
document_symbol_provider: Some(true.into()),
workspace_symbol_provider: Some(true.into()),
hover_provider: Some(true.into()),
references_provider: Some(true.into()),
rename_provider: Some(tower_lsp::lsp_types::OneOf::Left(true)),
completion_provider: Some(tower_lsp::lsp_types::CompletionOptions {
resolve_provider: Some(false),
trigger_characters: Some(vec![".".into(), ":".into()]),
..Default::default()
}),
semantic_tokens_provider: Some(
tower_lsp::lsp_types::SemanticTokensServerCapabilities::SemanticTokensOptions(
tower_lsp::lsp_types::SemanticTokensOptions {
legend: tower_lsp::lsp_types::SemanticTokensLegend {
// preenchido no PR-12
token_types: vec![],
token_modifiers: vec![],
},
full: Some(tower_lsp::lsp_types::SemanticTokensFullOptions::Bool(true)),
range: None,
..Default::default()
},
),
),
..Default::default()
},
..Default::default()
})
}
async fn initialized(&self, _: tower_lsp::lsp_types::InitializedParams) {}
}
#[tokio::main]
async fn main() {
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
let (service, socket) = LspService::new(|_| Backend { db: Arc::new(RwLock::new(AnalysisDb::default())) });
Server::new(stdin, stdout, socket).serve(service).await;
}
```
### Contrato exato do AnalysisDb (travado)
> **Regra:** o LSP nunca recalcula segurando `RwLock` por muito tempo. Toda análise pesada roda fora do lock e depois faz swap.
Criar arquivo: `prometeu-lsp/src/analysis_db.rs`
```rust
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio_util::sync::CancellationToken;
use prometeu_analysis::FileDB;
#[derive(Default)]
pub struct AnalysisDb {
pub file_db: FileDB,
// Os campos abaixo serão conectados conforme PR-03/04/05 (podem começar como None)
// pub ast: Option<AstArena>,
// pub symbols: Option<SymbolArena>,
// pub types: Option<TypeArena>,
// pub diagnostics: Vec<Diagnostic>,
/// Incrementa a cada rebuild concluído com sucesso
pub revision: u64,
/// Cancel token do último rebuild em progresso (se houver)
pub active_rebuild: Option<CancellationToken>,
}
pub type SharedDb = Arc<RwLock<AnalysisDb>>;
```
No `main.rs`, **substituir** o `AnalysisDb` local pelo import acima (ou manter local e delegar, mas sem duplicar).
### Modelo de cancelamento e coalescing (travado)
> Objetivo: quando chegam múltiplos `didChange` em sequência, cancelar o rebuild anterior e rodar apenas o último.
Adicionar dependência fixa em `prometeu-lsp/Cargo.toml`:
* `tokio-util`
Criar arquivo: `prometeu-lsp/src/rebuild.rs`
```rust
use tokio_util::sync::CancellationToken;
use crate::analysis_db::SharedDb;
/// Solicita rebuild do projeto (coarse). Cancela rebuild anterior se em progresso.
/// Implementação inicial: apenas cria task e retorna.
pub async fn request_rebuild(db: SharedDb) {
// 1) lock curto: cancelar token anterior e instalar token novo
// 2) spawn task: roda análise fora do lock
// 3) lock curto: se token não cancelado, swap estado + revision++
}
```
Regras obrigatórias:
1. `request_rebuild` deve:
* cancelar `active_rebuild` anterior (se existir)
* instalar um token novo
* `tokio::spawn` uma task de rebuild
2. A task deve checar `token.is_cancelled()` em pontos seguros:
* antes de começar
* após parsing
* após resolver
* após typecheck
3. O lock (`RwLock`) deve ser segurado apenas:
* para trocar `active_rebuild`
* para aplicar o resultado final
### Critérios de aceite
* `cargo build -p prometeu-lsp` ok
* LSP inicializa em VS Code/Neovim sem crash
* Nenhuma feature além do declarado
---
## Política fixa de cancelamento e rebuild (tower-lsp)
### Objetivo
Garantir que edições rápidas não gerem backlog de análises e evitar race conditions.
### Decisão travada
* Usar `tokio_util::sync::CancellationToken`
* **Um token por rebuild**
* Qualquer `didChange`:
1. Cancela o rebuild anterior
2. Cria novo token
3. Agenda nova task de análise
### Estruturas obrigatórias
```rust
use tokio_util::sync::CancellationToken;
struct AnalysisController {
current: Option<CancellationToken>,
}
```
### Regras
* Nunca segurar `RwLock<AnalysisDb>` durante parsing/resolver/typecheck.
* Fluxo correto:
1. Clonar snapshot dos textos (FileDB)
2. Soltar lock
3. Rodar análise pesada
4. Re-adquirir lock e substituir estado
* Tasks devem checar `token.is_cancelled()` entre fases (parse / bind / type).
### Critérios de aceite
* Digitar rapidamente não acumula tasks.
* Apenas o último estado publica diagnostics.
---
## Contrato fechado do AnalysisDb (fonte de verdade do LSP)
### Objetivo
Eliminar ambiguidade sobre o que vive no estado compartilhado.
### Estrutura obrigatória
```rust
#[derive(Default)]
pub struct AnalysisDb {
pub files: FileDB,
pub ast: Option<AstArena>,
pub symbols: Option<SymbolArena>,
pub types: Option<TypeArena>,
pub type_facts: Option<TypeFacts>,
pub diagnostics: Vec<Diagnostic>,
}
```
### Regras
* `AnalysisDb` **nunca** armazena estado parcialmente válido.
* Se uma fase falhar:
* `ast/symbols/types` podem ficar `None`
* `diagnostics` deve estar preenchido
* LSP handlers **não** rodam análise: apenas leem `AnalysisDb`.
### Leitura segura
* Todos os handlers (`definition`, `hover`, `references`, etc.) devem:
* adquirir `db.read()`
* lidar com `Option::None` retornando `null` / resposta vazia
### Escrita segura
* Apenas a task de rebuild escreve no `AnalysisDb`.
* Escrita sempre substitui o estado inteiro (swap lógico).
### Critérios de aceite
* Nenhum handler pode panic por `None`.
* Rebuild substitui estado de forma atômica (do ponto de vista do LSP).
---
## PR-01 — FileDB + LineIndex (base do LSP e spans)
**Branch:** `pr-01-filedb`
### Objetivo
Criar uma base de arquivos com IDs estáveis na sessão e conversão offset<->(line,col).
### Arquivos / módulos
* `prometeu-analysis/src/file_db.rs`
* `prometeu-analysis/src/span.rs`
* `prometeu-analysis/src/ids.rs`
* `prometeu-analysis/src/lib.rs` (re-exports)
### Estruturas obrigatórias
```rust
// prometeu-analysis/src/ids.rs
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct FileId(pub u32);
// prometeu-analysis/src/span.rs
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Span {
pub file: FileId,
pub start: u32, // byte offset
pub end: u32, // byte offset, exclusive
}
// prometeu-analysis/src/file_db.rs
pub struct FileDB {
// map uri->id, id->uri/text/line_index
}
pub struct LineIndex {
// stores line start offsets
}
```
### Regras
* Spans são **byte offsets UTF-8** (`start` inclusive, `end` exclusive) — alinhado ao Canonical Addenda.
* `LineIndex` deve lidar com `
` (LF). (CRLF pode ser normalizado na entrada.)
### APIs obrigatórias
```rust
impl FileDB {
pub fn upsert(&mut self, uri: &str, text: String) -> FileId;
pub fn file_id(&self, uri: &str) -> Option<FileId>;
pub fn uri(&self, id: FileId) -> &str;
pub fn text(&self, id: FileId) -> &str;
pub fn line_index(&self, id: FileId) -> &LineIndex;
}
impl LineIndex {
pub fn offset_to_line_col(&self, offset: u32) -> (u32, u32);
pub fn line_col_to_offset(&self, line: u32, col: u32) -> Option<u32>;
}
```
### Testes obrigatórios
* `tests/file_db_line_index.rs` (unit)
* roundtrip offset->(l,c)->offset
* bordas (start/end, EOF)
### Critérios de aceite
* Testes passam.
* Nenhuma mudança no output do `prometeu-compiler` ainda.
---
## PR-02 — NameInterner (NameId) e eliminação de String no hot path
**Branch:** `pr-02-name-interner`
### Objetivo
Trocar identificadores em AST/resolver de `String` para `NameId`.
### Arquivos
* `prometeu-analysis/src/interner.rs`
* Ajustar frontend PBS no `prometeu-compiler` (ver lista de toques abaixo).
### Estruturas obrigatórias
```rust
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct NameId(pub u32);
pub struct NameInterner {
// bidirectional: string->id, id->string
}
```
### APIs obrigatórias
```rust
impl NameInterner {
pub fn new() -> Self;
pub fn intern(&mut self, s: &str) -> NameId;
pub fn resolve(&self, id: NameId) -> &str;
}
```
### Regras
* Interner é **session-local**.
* `resolve` deve retornar `&str` estável (armazenar `String` internamente).
### Touch points no `prometeu-compiler`
* Onde hoje existe `String` como nome de símbolo, token de identifier, etc:
* AST nodes de `Ident`
* Resolver scopes: `HashMap<NameId, _>`
### Testes
* Unit: intern/resolve, dedup
* Golden: manter comportamento de resolver/diags
### Critérios de aceite
* Redução de `String` em estruturas hot (resolver)
* Build e testes ok.
---
## PR-03 — AST Arena (NodeId) para PBS
**Branch:** `pr-03-ast-arena`
### Objetivo
Refatorar o AST PBS (atualmente recursivo) para uma arena `Vec` com `NodeId`.
### Alvos no repo
* `prometeu-compiler/src/frontends/pbs/*` (localizar AST/parser atuais)
### Estruturas obrigatórias
```rust
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct NodeId(pub u32);
pub struct AstArena {
pub nodes: Vec<NodeKind>,
pub spans: Vec<prometeu_analysis::Span>,
pub roots: Vec<NodeId>,
}
pub enum NodeKind {
File { imports: Vec<NodeId>, decls: Vec<NodeId> },
Import { /* ... */ },
FnDecl { name: NameId, /* ... */, body: NodeId },
// ... (conforme canonical AST do PBS)
}
```
### Regras
* `AstArena::push(kind, span) -> NodeId` sempre faz append.
* `spans.len() == nodes.len()` sempre.
### APIs obrigatórias
```rust
impl AstArena {
pub fn push(&mut self, kind: NodeKind, span: Span) -> NodeId;
pub fn kind(&self, id: NodeId) -> &NodeKind;
pub fn span(&self, id: NodeId) -> Span;
}
```
### Testes obrigatórios
* Parser golden tests: comparar JSON canonical (se você já tem) ou snapshots equivalentes.
* Unit: invariantes (push mantém alinhamento).
### Critérios de aceite
* Nenhum `Box<Node>` em AST PBS.
* Resolver e typechecker passam a consumir NodeId.
---
## PR-04 — SymbolArena + índices (defs/refs) + node→symbol
**Branch:** `pr-04-symbol-arena-index`
### Objetivo
Criar `SymbolArena` e índices para features de LSP sem traversal pesado.
### Estruturas obrigatórias
```rust
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct SymbolId(pub u32);
pub enum SymbolKind { Type, Value, Service, Function /* etc */ }
pub struct Symbol {
pub name: NameId,
pub kind: SymbolKind,
pub exported: bool,
pub module: ModuleId,
pub decl_span: Span,
}
pub struct SymbolArena { pub symbols: Vec<Symbol> }
pub struct DefIndex { /* HashMap<(ModuleId, NameId, Namespace), SymbolId> */ }
pub struct RefIndex { /* Vec<Vec<Span>> by SymbolId */ }
pub struct NodeToSymbol { /* Vec<Option<SymbolId>> by NodeId */ }
```
### APIs obrigatórias
* `insert_symbol(...) -> SymbolId` (falha se duplicate, com diag E_RESOLVE_DUPLICATE_SYMBOL)
* `record_ref(symbol_id, span)`
* `bind_node(node_id, symbol_id)`
### Testes
* Duplicate symbol
* Undefined identifier
* Visibility violations
* Determinismo (mesma input → mesma ordem IDs, quando possível)
---
## PR-05 — TypeArena + TypeFacts (node→type, symbol→type)
**Branch:** `pr-05-type-arena-facts`
### Objetivo
Dar base para hover, completion e erros de tipo.
### Estruturas obrigatórias
```rust
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct TypeId(pub u32);
pub enum TypeKind {
Primitive { name: NameId },
Struct { sym: SymbolId },
Optional { inner: TypeId },
Result { ok: TypeId, err: TypeId },
Array { inner: TypeId, len: Option<u32> },
// ... conforme v0
}
pub struct TypeArena { pub types: Vec<TypeKind> }
pub struct TypeFacts {
pub node_type: Vec<Option<TypeId>>, // index by NodeId
pub symbol_type: Vec<Option<TypeId>>, // index by SymbolId
}
```
### APIs obrigatórias
* `intern_type(TypeKind) -> TypeId` (dedup opcional; documentar)
* `set_node_type(NodeId, TypeId)`
* `set_symbol_type(SymbolId, TypeId)`
### Testes
* Hover type display snapshots.
---
## PR-06 — Diagnostics canonizados (E_* / W_*)
**Branch:** `pr-06-diagnostics`
### Objetivo
Diagnósticos estáveis e serializáveis, alinhados ao Canonical Addenda (codes).
### Estruturas obrigatórias
```rust
pub enum Severity { Error, Warning }
pub struct Diagnostic {
pub severity: Severity,
pub code: String,
pub message: String,
pub span: Span,
pub related: Vec<(String, Span)>,
}
```
### Regras
* Codes conforme Canonical Addenda (E_PARSE_*, E_RESOLVE_*, E_TYPE_*).
### Testes
* Golden diag JSON determinístico.
---
## PR-07 — Export: `symbols.json` v1 + `analysis.json` v0
**Branch:** `pr-07-analysis-export`
### Objetivo
Separar índice leve (`symbols.json`) e export completo (`analysis.json`).
### Símbolos (novo formato recomendado)
* **IMPORTANTE:** parar de gravar paths absolutos (usar URI relativo ao projeto quando possível).
`symbols.json` v1 (schema_version=1):
* `projects[]`:
* `project` (string)
* `project_dir` (string) (pode ficar absoluto por enquanto)
* `symbols[]`:
* `symbol_id` (u32) **OU** string estável atual (mantém compat)
* `name` (string)
* `kind` (string)
* `exported` (bool)
* `module_path` (string)
* `decl_span`: `{ file_uri, start:{line,col}, end:{line,col} }`
`analysis.json` (schema_version=0):
* `file_table`: [{file_id:u32, uri:string}]
* `symbols`: [{symbol_id:u32, name_id:u32, kind, exported, module_id, decl_span}]
* `refs`: [{symbol_id:u32, spans:[Span]}]
* `types`: [{type_id:u32, kind:...}]
* `facts`: { node_type: [...], symbol_type: [...] }
* `diagnostics`: [Diagnostic]
### Testes
* Snapshot de ambos JSONs.
---
## PR-08 — LSP MVP (diagnostics + symbols + goto definition)
**Branch:** `pr-08-lsp-mvp`
### Objetivo
LSP funcional mínimo usando `AnalysisDb` (em memória), recompilando quando arquivo muda.
### Features
* `initialize`
* `didOpen`/`didChange`/`didClose`
* `publishDiagnostics`
* `documentSymbol` / `workspaceSymbol`
* `definition`
### Regras
* `didChange`: por enquanto full-text (simplifica)
* Rebuild: coarse (projeto inteiro) inicialmente
### Testes
* Test harness: abrir doc e pedir definition.
---
## PR-09 — LSP: references + rename
**Branch:** `pr-09-lsp-refs-rename`
### Objetivo
Baseado em RefIndex e NodeToSymbol.
### Features
* `references`
* `prepareRename`
* `rename`
### Regras de segurança
* Não renomear se símbolo não-resolvido.
* Não renomear se span em comentário/string.
---
## PR-10 — LSP: hover + signatureHelp
**Branch:** `pr-10-lsp-hover-sighelp`
### Features
* `hover`: type + docstring (docstring opcional por enquanto)
* `signatureHelp`: para call nodes (se parser já marca)
---
## PR-11 — LSP: completion
**Branch:** `pr-11-lsp-completion`
### Buckets
* locals em scope (se você tiver scope facts)
* symbols do módulo
* exports importados
* members via type facts
---
## PR-12 — LSP: semantic tokens + highlights
**Branch:** `pr-12-lsp-semantic`
---
## PR-13 — LSP: formatting + code actions (opcional)
**Branch:** `pr-13-lsp-actions`
---
## PR-14 — Incremental analysis + cancelation
**Branch:** `pr-14-incremental`
---
## PR-15 — Debug map (pc→span) + integração com traps
**Branch:** `pr-15-debug-map`
---
# Ordem recomendada (para minimizar refator churn)
1. PR-00, PR-01
2. PR-02
3. PR-03
4. PR-04
5. PR-06
6. PR-05
7. PR-07
8. PR-08 → PR-12
9. PR-14
10. PR-15