956 lines
24 KiB
Markdown
956 lines
24 KiB
Markdown
# Prometeu — PR Plan (Arena-Driven) para um LSP Completo
|
||
|
||
---
|
||
|
||
# **PR-00.X — Reorganização estrutural do workspace (pré–Arena / pré–LSP)**
|
||
|
||
> **Objetivo macro:** separar *modelo/ABI*, *execução*, *kernel* e *tooling* antes de qualquer refator arena-driven.
|
||
>
|
||
> **Regra de ouro:** nenhuma mudança de comportamento. Apenas **movimentação de código + ajuste de dependências**.
|
||
>
|
||
> **Modelo final alvo:**
|
||
>
|
||
> * `prometeu-bytecode` ✅ (fica)
|
||
> * `prometeu-abi` ✅ (novo — ex-`prometeu-core` sem execução)
|
||
> * `prometeu-vm` ✅ (novo)
|
||
> * `prometeu-kernel` ✅ (novo)
|
||
> * `prometeu-runtime-desktop` ✅ (fica)
|
||
> * `prometeu-compiler` ✅ (fica)
|
||
> * `prometeu` (CLI) ✅ (fica agregando)
|
||
|
||
---
|
||
|
||
> **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.)
|
||
|
||
---
|
||
|
||
## Visão de arquitetura alvo
|
||
|
||
### Camadas
|
||
|
||
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.
|
||
|
||
### 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).
|
||
|
||
---
|
||
|
||
# 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):**
|
||
|
||
## 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.
|
||
|
||
---
|
||
|
||
# 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).
|
||
|
||
---
|
||
|
||
|
||
# Série PR-00.X — Reestruturação de crates (Arena-Driven + LSP-ready)
|
||
|
||
> **Objetivo da série PR-00.X:** reorganizar o workspace em crates com fronteiras claras, **sem alterar comportamento**, preparando o terreno para o Prometeu Arena-Driven (compiler/analysis) e um LSP completo depois.
|
||
>
|
||
> **Novo layout alvo (travado):**
|
||
>
|
||
> * `prometeu-bytecode` (fica)
|
||
> * `prometeu-abi` (novo nome para o atual `prometeu-core` depurado) = **types + model + protocols** (sem execução)
|
||
> * `prometeu-vm` (novo) = execução da VM
|
||
> * `prometeu-kernel` (novo) = OS + FS + syscalls (implementa interface para a VM)
|
||
> * `prometeu-runtime-desktop` (fica) = host desktop
|
||
> * `prometeu-compiler` (fica)
|
||
> * `prometeu` (CLI) (fica)
|
||
>
|
||
> **Regra de ouro:** 1 PR = 1 fronteira. Nada de mover tudo de uma vez.
|
||
>
|
||
> **Regras para Junie (serie 00.X):**
|
||
>
|
||
> 1. **Não mudar lógica**; apenas mover, ajustar imports, e deixar testes verdes.
|
||
> 2. Se algo exigir mudança de API pública, criar `TODO(PR-00.Y)` e parar.
|
||
> 3. Sempre manter o workspace compilável a cada PR.
|
||
|
||
---
|
||
|
||
## PR-00.3 — Extrair `prometeu-kernel` (OS + FS + syscalls)
|
||
|
||
**Branch:** `pr-00-3-prometeu-kernel`
|
||
|
||
### Objetivo
|
||
|
||
Mover `prometeu_os/*` + `fs/*` (e qualquer dispatcher de syscalls) para `crates/prometeu-kernel/`.
|
||
|
||
### Passos prescritivos
|
||
|
||
1. Criar crate `crates/prometeu-kernel/`.
|
||
2. Mover:
|
||
|
||
* `crates/prometeu-core/src/prometeu_os/**` → `crates/prometeu-kernel/src/**`
|
||
* `crates/prometeu-core/src/fs/**` → `crates/prometeu-kernel/src/fs/**`
|
||
3. Garantir que o kernel **depende** de:
|
||
|
||
* `prometeu-abi`
|
||
* `prometeu-vm`
|
||
4. Garantir que a VM chama syscalls via interface implementada pelo kernel.
|
||
5. Manter shim temporário no `prometeu-core` (se existir): `pub mod prometeu_os { pub use prometeu_kernel::*; }`
|
||
|
||
### Critérios de aceite
|
||
|
||
* `cargo test` verde.
|
||
* Sem mudança comportamental (mesmo conjunto de syscalls e traps).
|
||
|
||
---
|
||
|
||
## PR-00.4 — Depurar `prometeu-core`: remover execução e virar apenas compat layer
|
||
|
||
**Branch:** `pr-00-4-deprecate-prometeu-core`
|
||
|
||
### Objetivo
|
||
|
||
Eliminar o conteúdo executável restante do `prometeu-core`, deixando-o como reexport/compat por um curto período.
|
||
|
||
### Passos
|
||
|
||
* `hardware` e `firmware/hub` ficam onde estiverem por enquanto (podem virar PR-00.5/00.6 se necessário)
|
||
* `prometeu-core` passa a reexportar:
|
||
|
||
* `prometeu-abi`
|
||
* `prometeu-vm`
|
||
* `prometeu-kernel`
|
||
|
||
### Critérios
|
||
|
||
* Zero imports internos `crate::virtual_machine` etc. fora da facade.
|
||
|
||
---
|
||
|
||
## PR-00.5 — (Opcional) Extrair `prometeu-firmware` (hub/boot)
|
||
|
||
**Branch:** `pr-00-5-prometeu-firmware`
|
||
|
||
### Objetivo
|
||
|
||
Mover `firmware/*` + `prometeu_hub/*` para `crates/prometeu-firmware/`.
|
||
|
||
### Critérios
|
||
|
||
* runtime desktop depende de `prometeu-firmware`.
|
||
|
||
---
|
||
|
||
## PR-00.6 — Remover `prometeu-core` e migrar consumidores
|
||
|
||
**Branch:** `pr-00-6-remove-prometeu-core`
|
||
|
||
### Objetivo
|
||
|
||
Apagar `prometeu-core` após migração total.
|
||
|
||
### Critérios
|
||
|
||
* Nenhum crate depende de `prometeu-core`.
|
||
|
||
---
|
||
|
||
## 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`
|
||
|
||
---
|
||
|
||
|
||
---
|
||
|
||
# Sequência recomendada (travada)
|
||
|
||
1. PR-00.1 (`prometeu-abi` + compat)
|
||
2. PR-00.2 (`prometeu-vm`)
|
||
3. PR-00.3 (`prometeu-kernel`)
|
||
4. PR-00.4 (depurar `prometeu-core`)
|
||
5. PR-00.5 (opcional firmware)
|
||
6. PR-00.6 (remover core)
|
||
|
||
> Após PR-00.X, iniciar a trilha Arena-Driven (PR-01..PR-07) no compiler/analysis.
|